k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // the basics are tested above, this checks the deletion logic when the ca bundles are too large 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 }