1
16
17 package clusterauthenticationtrust
18
19 import (
20 "reflect"
21 "testing"
22
23 "github.com/google/go-cmp/cmp"
24
25 corev1 "k8s.io/api/core/v1"
26 apierrors "k8s.io/apimachinery/pkg/api/errors"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/util/dump"
31 "k8s.io/apimachinery/pkg/util/validation/field"
32 "k8s.io/apiserver/pkg/authentication/request/headerrequest"
33 "k8s.io/apiserver/pkg/server/dynamiccertificates"
34 "k8s.io/client-go/kubernetes/fake"
35 corev1listers "k8s.io/client-go/listers/core/v1"
36 clienttesting "k8s.io/client-go/testing"
37 "k8s.io/client-go/tools/cache"
38 )
39
40 var (
41 someRandomCA = []byte(`-----BEGIN CERTIFICATE-----
42 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
43 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
44 MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
45 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
46 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
47 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
48 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
49 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
50 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
51 -----END CERTIFICATE-----
52 `)
53 anotherRandomCA = []byte(`-----BEGIN CERTIFICATE-----
54 MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
55 BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
56 DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo
57 b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
58 AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa
59 dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0
60 r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD
61 XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp
62 7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E
63 j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
64 BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
65 hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
66 ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6
67 ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc
68 T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF
69 bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3
70 M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0
71 YkNtGc1RUDHwecCTFpJtPb7Yu/E=
72 -----END CERTIFICATE-----
73 `)
74
75 someRandomCAProvider dynamiccertificates.CAContentProvider
76 anotherRandomCAProvider dynamiccertificates.CAContentProvider
77 )
78
79 func init() {
80 var err error
81 someRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("foo", someRandomCA)
82 if err != nil {
83 panic(err)
84 }
85 anotherRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("bar", anotherRandomCA)
86 if err != nil {
87 panic(err)
88 }
89 }
90
91 func TestWriteClientCAs(t *testing.T) {
92 tests := []struct {
93 name string
94 clusterAuthInfo ClusterAuthenticationInfo
95 preexistingObjs []runtime.Object
96 expectedConfigMaps map[string]*corev1.ConfigMap
97 expectCreate bool
98 }{
99 {
100 name: "basic",
101 clusterAuthInfo: ClusterAuthenticationInfo{
102 ClientCA: someRandomCAProvider,
103 RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
104 RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
105 RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
106 RequestHeaderCA: anotherRandomCAProvider,
107 RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
108 },
109 expectedConfigMaps: map[string]*corev1.ConfigMap{
110 "extension-apiserver-authentication": {
111 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
112 Data: map[string]string{
113 "client-ca-file": string(someRandomCA),
114 "requestheader-username-headers": `["alfa","bravo","charlie"]`,
115 "requestheader-group-headers": `["delta"]`,
116 "requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
117 "requestheader-client-ca-file": string(anotherRandomCA),
118 "requestheader-allowed-names": `["first","second"]`,
119 },
120 },
121 },
122 expectCreate: true,
123 },
124 {
125 name: "skip extension-apiserver-authentication",
126 clusterAuthInfo: ClusterAuthenticationInfo{
127 RequestHeaderCA: anotherRandomCAProvider,
128 RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
129 },
130 expectedConfigMaps: map[string]*corev1.ConfigMap{
131 "extension-apiserver-authentication": {
132 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
133 Data: map[string]string{
134 "requestheader-username-headers": `[]`,
135 "requestheader-group-headers": `[]`,
136 "requestheader-extra-headers-prefix": `[]`,
137 "requestheader-client-ca-file": string(anotherRandomCA),
138 "requestheader-allowed-names": `["first","second"]`,
139 },
140 },
141 },
142 expectCreate: true,
143 },
144 {
145 name: "skip extension-apiserver-authentication",
146 clusterAuthInfo: ClusterAuthenticationInfo{
147 ClientCA: someRandomCAProvider,
148 },
149 expectedConfigMaps: map[string]*corev1.ConfigMap{
150 "extension-apiserver-authentication": {
151 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
152 Data: map[string]string{
153 "client-ca-file": string(someRandomCA),
154 },
155 },
156 },
157 expectCreate: true,
158 },
159 {
160 name: "empty allowed names",
161 clusterAuthInfo: ClusterAuthenticationInfo{
162 RequestHeaderCA: anotherRandomCAProvider,
163 },
164 expectedConfigMaps: map[string]*corev1.ConfigMap{
165 "extension-apiserver-authentication": {
166 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
167 Data: map[string]string{
168 "requestheader-username-headers": `[]`,
169 "requestheader-group-headers": `[]`,
170 "requestheader-extra-headers-prefix": `[]`,
171 "requestheader-client-ca-file": string(anotherRandomCA),
172 "requestheader-allowed-names": `[]`,
173 },
174 },
175 },
176 expectCreate: true,
177 },
178 {
179 name: "overwrite extension-apiserver-authentication",
180 clusterAuthInfo: ClusterAuthenticationInfo{
181 ClientCA: someRandomCAProvider,
182 },
183 preexistingObjs: []runtime.Object{
184 &corev1.ConfigMap{
185 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
186 Data: map[string]string{
187 "client-ca-file": string(anotherRandomCA),
188 },
189 },
190 },
191 expectedConfigMaps: map[string]*corev1.ConfigMap{
192 "extension-apiserver-authentication": {
193 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
194 Data: map[string]string{
195 "client-ca-file": string(anotherRandomCA) + string(someRandomCA),
196 },
197 },
198 },
199 },
200 {
201 name: "overwrite extension-apiserver-authentication requestheader",
202 clusterAuthInfo: ClusterAuthenticationInfo{
203 RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
204 RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
205 RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
206 RequestHeaderCA: anotherRandomCAProvider,
207 RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
208 },
209 preexistingObjs: []runtime.Object{
210 &corev1.ConfigMap{
211 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
212 Data: map[string]string{
213 "requestheader-username-headers": `[]`,
214 "requestheader-group-headers": `[]`,
215 "requestheader-extra-headers-prefix": `[]`,
216 "requestheader-client-ca-file": string(someRandomCA),
217 "requestheader-allowed-names": `[]`,
218 },
219 },
220 },
221 expectedConfigMaps: map[string]*corev1.ConfigMap{
222 "extension-apiserver-authentication": {
223 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
224 Data: map[string]string{
225 "requestheader-username-headers": `[]`,
226 "requestheader-group-headers": `[]`,
227 "requestheader-extra-headers-prefix": `[]`,
228 "requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
229 "requestheader-allowed-names": `[]`,
230 },
231 },
232 },
233 },
234 {
235 name: "namespace exists",
236 clusterAuthInfo: ClusterAuthenticationInfo{
237 ClientCA: someRandomCAProvider,
238 },
239 preexistingObjs: []runtime.Object{
240 &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceSystem}},
241 },
242 expectedConfigMaps: map[string]*corev1.ConfigMap{
243 "extension-apiserver-authentication": {
244 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
245 Data: map[string]string{
246 "client-ca-file": string(someRandomCA),
247 },
248 },
249 },
250 expectCreate: true,
251 },
252 {
253 name: "skip on no change",
254 clusterAuthInfo: ClusterAuthenticationInfo{
255 RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
256 RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
257 RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
258 RequestHeaderCA: anotherRandomCAProvider,
259 RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
260 },
261 preexistingObjs: []runtime.Object{
262 &corev1.ConfigMap{
263 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
264 Data: map[string]string{
265 "requestheader-username-headers": `[]`,
266 "requestheader-group-headers": `[]`,
267 "requestheader-extra-headers-prefix": `[]`,
268 "requestheader-client-ca-file": string(anotherRandomCA),
269 "requestheader-allowed-names": `[]`,
270 },
271 },
272 },
273 expectedConfigMaps: map[string]*corev1.ConfigMap{},
274 expectCreate: false,
275 },
276 }
277
278 for _, test := range tests {
279 t.Run(test.name, func(t *testing.T) {
280 client := fake.NewSimpleClientset(test.preexistingObjs...)
281 configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
282 for _, obj := range test.preexistingObjs {
283 configMapIndexer.Add(obj)
284 }
285 configmapLister := corev1listers.NewConfigMapLister(configMapIndexer)
286
287 c := &Controller{
288 configMapLister: configmapLister,
289 configMapClient: client.CoreV1(),
290 namespaceClient: client.CoreV1(),
291 requiredAuthenticationData: test.clusterAuthInfo,
292 }
293
294 err := c.syncConfigMap()
295 if err != nil {
296 t.Fatal(err)
297 }
298
299 actualConfigMaps, updated := getFinalConfigMaps(t, client)
300 if !reflect.DeepEqual(test.expectedConfigMaps, actualConfigMaps) {
301 t.Fatalf("%s: %v", test.name, cmp.Diff(test.expectedConfigMaps, actualConfigMaps))
302 }
303 if test.expectCreate != updated {
304 t.Fatalf("%s: expected %v, got %v", test.name, test.expectCreate, updated)
305 }
306 })
307 }
308 }
309
310 func getFinalConfigMaps(t *testing.T, client *fake.Clientset) (map[string]*corev1.ConfigMap, bool) {
311 ret := map[string]*corev1.ConfigMap{}
312 created := false
313
314 for _, action := range client.Actions() {
315 t.Log(dump.Pretty(action))
316 if action.Matches("create", "configmaps") {
317 created = true
318 obj := action.(clienttesting.CreateAction).GetObject().(*corev1.ConfigMap)
319 ret[obj.Name] = obj
320 }
321 if action.Matches("update", "configmaps") {
322 obj := action.(clienttesting.UpdateAction).GetObject().(*corev1.ConfigMap)
323 ret[obj.Name] = obj
324 }
325 }
326 return ret, created
327 }
328
329 func TestWriteConfigMapDeleted(t *testing.T) {
330
331 cm := &corev1.ConfigMap{
332 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
333 Data: map[string]string{
334 "requestheader-username-headers": `[]`,
335 "requestheader-group-headers": `[]`,
336 "requestheader-extra-headers-prefix": `[]`,
337 "requestheader-client-ca-file": string(anotherRandomCA),
338 "requestheader-allowed-names": `[]`,
339 },
340 }
341
342 t.Run("request entity too large", func(t *testing.T) {
343 client := fake.NewSimpleClientset()
344 client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
345 return true, nil, apierrors.NewRequestEntityTooLargeError("way too big")
346 })
347 client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
348 return true, nil, nil
349 })
350
351 err := writeConfigMap(client.CoreV1(), cm)
352 if err == nil || err.Error() != "Request entity too large: way too big" {
353 t.Fatal(err)
354 }
355 if len(client.Actions()) != 2 {
356 t.Fatal(client.Actions())
357 }
358 _, ok := client.Actions()[1].(clienttesting.DeleteAction)
359 if !ok {
360 t.Fatal(client.Actions())
361 }
362 })
363
364 t.Run("ca bundle too large", func(t *testing.T) {
365 client := fake.NewSimpleClientset()
366 client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
367 return true, nil, apierrors.NewInvalid(schema.GroupKind{Kind: "ConfigMap"}, cm.Name, field.ErrorList{field.TooLong(field.NewPath(""), cm, corev1.MaxSecretSize)})
368 })
369 client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
370 return true, nil, nil
371 })
372
373 err := writeConfigMap(client.CoreV1(), cm)
374 if err == nil || err.Error() != `ConfigMap "extension-apiserver-authentication" is invalid: []: Too long: must have at most 1048576 bytes` {
375 t.Fatal(err)
376 }
377 if len(client.Actions()) != 2 {
378 t.Fatal(client.Actions())
379 }
380 _, ok := client.Actions()[1].(clienttesting.DeleteAction)
381 if !ok {
382 t.Fatal(client.Actions())
383 }
384 })
385
386 }
387
View as plain text