1 package api
2
3 import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "crypto/x509/pkix"
8 "fmt"
9 "net"
10 "net/http"
11 "net/url"
12 "testing"
13
14 "github.com/go-test/deep"
15 "github.com/linkerd/linkerd2/controller/k8s"
16 k8sutils "github.com/linkerd/linkerd2/pkg/k8s"
17 )
18
19 func TestAPIServerAuth(t *testing.T) {
20 expectations := []struct {
21 k8sRes []string
22 clientCAPem string
23 allowedNames []string
24 usernameHeader string
25 groupHeader string
26 err error
27 }{
28 {
29 err: fmt.Errorf("failed to load [%s] config: configmaps %q not found", k8sutils.ExtensionAPIServerAuthenticationConfigMapName, k8sutils.ExtensionAPIServerAuthenticationConfigMapName),
30 },
31 {
32 k8sRes: []string{`
33 apiVersion: v1
34 kind: ConfigMap
35 metadata:
36 name: extension-apiserver-authentication
37 namespace: kube-system
38 data:
39 client-ca-file: 'client-ca-file'
40 requestheader-allowed-names: '["name1", "name2"]'
41 requestheader-client-ca-file: 'requestheader-client-ca-file'
42 requestheader-extra-headers-prefix: '["X-Remote-Extra-"]'
43 requestheader-group-headers: '["X-Remote-Group"]'
44 requestheader-username-headers: '["X-Remote-User"]'
45 `,
46 },
47 clientCAPem: "requestheader-client-ca-file",
48 allowedNames: []string{"name1", "name2"},
49 usernameHeader: "X-Remote-User",
50 groupHeader: "X-Remote-Group",
51 err: nil,
52 },
53 }
54
55 ctx := context.Background()
56 for i, exp := range expectations {
57 exp := exp
58
59 t.Run(fmt.Sprintf("%d parses the apiServerAuth ConfigMap", i), func(t *testing.T) {
60 k8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)
61 if err != nil {
62 t.Fatalf("NewFakeAPI returned an error: %s", err)
63 }
64
65 clientCAPem, allowedNames, usernameHeader, groupHeader, err := serverAuth(ctx, k8sAPI)
66
67 if err != nil && exp.err != nil {
68 if err.Error() != exp.err.Error() {
69 t.Errorf("apiServerAuth returned unexpected error: %q, expected: %q", err, exp.err)
70 }
71 } else if err != nil {
72 t.Fatalf("Unexpected error: %s", err)
73 } else if exp.err != nil {
74 t.Fatalf("Did not encounter expected error: %s", err)
75 }
76
77 if clientCAPem != exp.clientCAPem {
78 t.Errorf("apiServerAuth returned unexpected clientCAPem: %q, expected: %q", clientCAPem, exp.clientCAPem)
79 }
80 if diff := deep.Equal(allowedNames, exp.allowedNames); diff != nil {
81 t.Errorf("%v", diff)
82 }
83 if usernameHeader != exp.usernameHeader {
84 t.Errorf("apiServerAuth returned unexpected usernameHeader: %q, expected: %q", usernameHeader, exp.usernameHeader)
85 }
86 if groupHeader != exp.groupHeader {
87 t.Errorf("apiServerAuth returned unexpected groupHeader: %q, expected: %q", groupHeader, exp.groupHeader)
88 }
89 })
90 }
91 }
92
93 func TestValidate(t *testing.T) {
94 cert := testCertificate()
95 cert.Subject.CommonName = "name-any"
96
97 tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}
98
99 req := http.Request{TLS: &tls}
100
101 server := Server{}
102 if err := server.validate(&req); err != nil {
103 t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error())
104 }
105 }
106
107 func TestValidate_ClientAllowed(t *testing.T) {
108 cert := testCertificate()
109 cert.Subject.CommonName = "name-trusted"
110
111 tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}
112
113 req := http.Request{TLS: &tls}
114
115 server := Server{allowedNames: []string{"name-trusted"}}
116 if err := server.validate(&req); err != nil {
117 t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error())
118 }
119 }
120
121 func TestValidate_ClientAllowedViaSAN(t *testing.T) {
122 cert := testCertificate()
123 cert.Subject.CommonName = "name-any"
124
125 tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}
126
127 req := http.Request{TLS: &tls}
128
129 server := Server{allowedNames: []string{"linkerd.io"}}
130 if err := server.validate(&req); err != nil {
131 t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error())
132 }
133 }
134
135 func TestValidate_ClientNotAllowed(t *testing.T) {
136 cert := testCertificate()
137 cert.Subject.CommonName = "name-untrusted"
138
139 tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}
140
141 req := http.Request{TLS: &tls}
142
143 server := Server{allowedNames: []string{"name-trusted"}}
144 if err := server.validate(&req); err == nil {
145 t.Fatalf("Expected request to be rejected for %q", cert.Subject.CommonName)
146 }
147 }
148
149 func TestIsSubjectAlternateName(t *testing.T) {
150 testCases := []struct {
151 name string
152 expected bool
153 }{
154 {
155 name: "linkerd.io",
156 expected: true,
157 },
158 {
159 name: "root@localhost",
160 expected: true,
161 },
162 {
163 name: "192.168.1.1",
164 expected: true,
165 },
166 {
167 name: "http://localhost/api/test",
168 expected: true,
169 },
170 {
171 name: "mystique",
172 expected: false,
173 },
174 }
175
176 cert := testCertificate()
177 for _, tc := range testCases {
178 tc := tc
179 t.Run(tc.name, func(t *testing.T) {
180 actual := isSubjectAlternateName(&cert, tc.name)
181 if actual != tc.expected {
182 t.Fatalf("expected %t, but got %t", tc.expected, actual)
183 }
184 })
185 }
186 }
187
188 func testCertificate() x509.Certificate {
189 uri, _ := url.Parse("http://localhost/api/test")
190 cert := x509.Certificate{
191 Subject: pkix.Name{
192 CommonName: "linkerd-test",
193 },
194 DNSNames: []string{
195 "localhost",
196 "linkerd.io",
197 },
198 EmailAddresses: []string{
199 "root@localhost",
200 },
201 IPAddresses: []net.IP{
202 net.IPv4(127, 0, 0, 1),
203 net.IPv4(192, 168, 1, 1),
204 },
205 URIs: []*url.URL{
206 uri,
207 },
208 }
209 return cert
210 }
211
View as plain text