istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/credentials/kube/secrets_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kube 16 17 import ( 18 "fmt" 19 "testing" 20 21 authorizationv1 "k8s.io/api/authorization/v1" 22 corev1 "k8s.io/api/core/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/client-go/kubernetes/fake" 26 k8stesting "k8s.io/client-go/testing" 27 28 cluster2 "istio.io/istio/pkg/cluster" 29 "istio.io/istio/pkg/kube" 30 "istio.io/istio/pkg/kube/multicluster" 31 "istio.io/istio/pkg/test" 32 "istio.io/istio/pkg/util/sets" 33 ) 34 35 func makeSecret(name string, data map[string]string, secretType corev1.SecretType) *corev1.Secret { 36 bdata := map[string][]byte{} 37 for k, v := range data { 38 bdata[k] = []byte(v) 39 } 40 return &corev1.Secret{ 41 ObjectMeta: metav1.ObjectMeta{ 42 Name: name, 43 Namespace: "default", 44 }, 45 Data: bdata, 46 Type: secretType, 47 } 48 } 49 50 var ( 51 genericCert = makeSecret("generic", map[string]string{ 52 GenericScrtCert: "generic-cert", GenericScrtKey: "generic-key", 53 }, corev1.SecretTypeTLS) 54 genericMtlsCert = makeSecret("generic-mtls", map[string]string{ 55 GenericScrtCert: "generic-mtls-cert", GenericScrtKey: "generic-mtls-key", GenericScrtCaCert: "generic-mtls-ca", 56 }, corev1.SecretTypeTLS) 57 genericMtlsCertSplit = makeSecret("generic-mtls-split", map[string]string{ 58 GenericScrtCert: "generic-mtls-split-cert", GenericScrtKey: "generic-mtls-split-key", 59 }, corev1.SecretTypeTLS) 60 genericMtlsCertSplitCa = makeSecret("generic-mtls-split-cacert", map[string]string{ 61 GenericScrtCaCert: "generic-mtls-split-ca", 62 }, corev1.SecretTypeTLS) 63 overlapping = makeSecret("overlap", map[string]string{ 64 GenericScrtCert: "cert", GenericScrtKey: "key", GenericScrtCaCert: "main-ca", 65 }, corev1.SecretTypeTLS) 66 overlappingCa = makeSecret("overlap-cacert", map[string]string{ 67 GenericScrtCaCert: "split-ca", 68 }, corev1.SecretTypeTLS) 69 tlsCert = makeSecret("tls", map[string]string{ 70 TLSSecretCert: "tls-cert", TLSSecretKey: "tls-key", TLSSecretOcspStaple: "tls-ocsp-staple", 71 }, corev1.SecretTypeTLS) 72 tlsMtlsCert = makeSecret("tls-mtls", map[string]string{ 73 TLSSecretCert: "tls-mtls-cert", TLSSecretKey: "tls-mtls-key", TLSSecretCaCert: "tls-mtls-ca", 74 }, corev1.SecretTypeTLS) 75 tlsMtlsCertWithCrl = makeSecret("tls-mtls-crl", map[string]string{ 76 TLSSecretCert: "tls-mtls-cert", TLSSecretKey: "tls-mtls-key", TLSSecretCaCert: "tls-mtls-ca", TLSSecretCrl: "tls-mtls-crl", 77 }, corev1.SecretTypeTLS) 78 tlsMtlsCertSplit = makeSecret("tls-mtls-split", map[string]string{ 79 TLSSecretCert: "tls-mtls-split-cert", TLSSecretKey: "tls-mtls-split-key", 80 }, corev1.SecretTypeTLS) 81 tlsMtlsCertSplitCa = makeSecret("tls-mtls-split-cacert", map[string]string{ 82 TLSSecretCaCert: "tls-mtls-split-ca", 83 }, corev1.SecretTypeTLS) 84 tlsMtlsCertSplitCaWithCrl = makeSecret("tls-mtls-split-crl-cacert", map[string]string{ 85 TLSSecretCaCert: "tls-mtls-split-ca", TLSSecretCrl: "tls-mtls-split-crl", 86 }, corev1.SecretTypeTLS) 87 tlsMtlsCertSplitWithCrl = makeSecret("tls-mtls-split-crl", map[string]string{ 88 TLSSecretCert: "tls-mtls-split-crl-cert", TLSSecretKey: "tls-mtls-split-crl-key", 89 }, corev1.SecretTypeTLS) 90 emptyCert = makeSecret("empty-cert", map[string]string{ 91 TLSSecretCert: "", TLSSecretKey: "tls-key", 92 }, corev1.SecretTypeTLS) 93 wrongKeys = makeSecret("wrong-keys", map[string]string{ 94 "foo-bar": "my-cert", TLSSecretKey: "tls-key", 95 }, corev1.SecretTypeTLS) 96 dockerjson = makeSecret("docker-json", map[string]string{ 97 corev1.DockerConfigJsonKey: "docker-cred", 98 }, corev1.SecretTypeDockerConfigJson) 99 badDockerjson = makeSecret("bad-docker-json", map[string]string{ 100 "docker-key": "docker-cred", 101 }, corev1.SecretTypeDockerConfigJson) 102 ) 103 104 func TestSecretsController(t *testing.T) { 105 secrets := []runtime.Object{ 106 genericCert, 107 genericMtlsCert, 108 genericMtlsCertSplit, 109 genericMtlsCertSplitCa, 110 overlapping, 111 overlappingCa, 112 tlsCert, 113 tlsMtlsCert, 114 tlsMtlsCertWithCrl, 115 tlsMtlsCertSplit, 116 tlsMtlsCertSplitCa, 117 tlsMtlsCertSplitCaWithCrl, 118 tlsMtlsCertSplitWithCrl, 119 emptyCert, 120 wrongKeys, 121 } 122 client := kube.NewFakeClient(secrets...) 123 sc := NewCredentialsController(client, nil) 124 client.RunAndWait(test.NewStop(t)) 125 cases := []struct { 126 name string 127 namespace string 128 cert string 129 key string 130 staple string 131 caCert string 132 caCrl string 133 crl string 134 expectedError string 135 expectedCAError string 136 }{ 137 { 138 name: "generic", 139 namespace: "default", 140 cert: "generic-cert", 141 key: "generic-key", 142 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: cert, key", 143 }, 144 { 145 name: "generic-mtls", 146 namespace: "default", 147 cert: "generic-mtls-cert", 148 key: "generic-mtls-key", 149 caCert: "generic-mtls-ca", 150 }, 151 { 152 name: "generic-mtls-split", 153 namespace: "default", 154 cert: "generic-mtls-split-cert", 155 key: "generic-mtls-split-key", 156 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: cert, key", 157 }, 158 { 159 name: "generic-mtls-split-cacert", 160 namespace: "default", 161 caCert: "generic-mtls-split-ca", 162 expectedError: "found secret, but didn't have expected keys (cert and key) or (tls.crt and tls.key); found: cacert", 163 }, 164 // The -cacert secret has precedence 165 { 166 name: "overlap-cacert", 167 namespace: "default", 168 caCert: "split-ca", 169 expectedError: "found secret, but didn't have expected keys (cert and key) or (tls.crt and tls.key); found: cacert", 170 }, 171 { 172 name: "tls", 173 namespace: "default", 174 cert: "tls-cert", 175 key: "tls-key", 176 staple: "tls-ocsp-staple", 177 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: tls.crt, tls.key, tls.ocsp-staple, and 0 more...", 178 }, 179 { 180 name: "tls-mtls", 181 namespace: "default", 182 cert: "tls-mtls-cert", 183 key: "tls-mtls-key", 184 caCert: "tls-mtls-ca", 185 }, 186 { 187 name: "tls-mtls-crl", 188 namespace: "default", 189 cert: "tls-mtls-cert", 190 key: "tls-mtls-key", 191 caCert: "tls-mtls-ca", 192 crl: "tls-mtls-crl", 193 caCrl: "tls-mtls-crl", 194 }, 195 { 196 name: "tls-mtls-split", 197 namespace: "default", 198 cert: "tls-mtls-split-cert", 199 key: "tls-mtls-split-key", 200 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: tls.crt, tls.key", 201 }, 202 { 203 name: "tls-mtls-split-cacert", 204 namespace: "default", 205 caCert: "tls-mtls-split-ca", 206 expectedError: "found secret, but didn't have expected keys (cert and key) or (tls.crt and tls.key); found: ca.crt", 207 }, 208 { 209 name: "tls-mtls-split-crl-cacert", 210 namespace: "default", 211 caCert: "tls-mtls-split-ca", 212 expectedError: "found secret, but didn't have expected keys (cert and key) or (tls.crt and tls.key); found: ca.crl, ca.crt", 213 caCrl: "tls-mtls-split-crl", 214 }, 215 { 216 name: "tls-mtls-split-crl", 217 namespace: "default", 218 cert: "tls-mtls-split-crl-cert", 219 key: "tls-mtls-split-crl-key", 220 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: tls.crt, tls.key", 221 }, 222 { 223 name: "generic", 224 namespace: "wrong-namespace", 225 expectedError: `secret wrong-namespace/generic not found`, 226 expectedCAError: `secret wrong-namespace/generic not found`, 227 }, 228 { 229 name: "empty-cert", 230 namespace: "default", 231 expectedError: `found keys "tls.crt" and "tls.key", but they were empty`, 232 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: tls.crt, tls.key", 233 }, 234 { 235 name: "wrong-keys", 236 namespace: "default", 237 expectedError: `found secret, but didn't have expected keys (cert and key) or (tls.crt and tls.key); found: foo-bar, tls.key`, 238 expectedCAError: "found secret, but didn't have expected keys cacert or ca.crt; found: foo-bar, tls.key", 239 }, 240 } 241 for _, tt := range cases { 242 t.Run(tt.name, func(t *testing.T) { 243 certInfo, err := sc.GetCertInfo(tt.name, tt.namespace) 244 var actualKey []byte 245 var actualCert []byte 246 var actualStaple []byte 247 var actualCrl []byte 248 if certInfo != nil { 249 actualKey = certInfo.Key 250 actualCert = certInfo.Cert 251 actualStaple = certInfo.Staple 252 actualCrl = certInfo.CRL 253 } 254 if tt.key != string(actualKey) { 255 t.Errorf("got key %q, wanted %q", string(actualKey), tt.key) 256 } 257 if tt.cert != string(actualCert) { 258 t.Errorf("got cert %q, wanted %q", string(actualCert), tt.cert) 259 } 260 if tt.staple != string(actualStaple) { 261 t.Errorf("got staple %q, wanted %q", string(actualStaple), tt.staple) 262 } 263 if tt.crl != string(actualCrl) { 264 t.Errorf("got crl %q, wanted %q", string(actualCrl), tt.crl) 265 } 266 if tt.expectedError != errString(err) { 267 t.Errorf("got err %q, wanted %q", errString(err), tt.expectedError) 268 } 269 caCertInfo, err := sc.GetCaCert(tt.name, tt.namespace) 270 if caCertInfo != nil && tt.caCert != string(caCertInfo.Cert) { 271 t.Errorf("got caCert %q, wanted %q", string(caCertInfo.Cert), tt.caCert) 272 } 273 if caCertInfo != nil && tt.caCrl != string(caCertInfo.CRL) { 274 t.Errorf("got caCrl %q, wanted %q", string(caCertInfo.CRL), tt.crl) 275 } 276 if tt.expectedCAError != errString(err) { 277 t.Errorf("got ca err %q, wanted %q", errString(err), tt.expectedCAError) 278 } 279 }) 280 } 281 } 282 283 func TestDockerCredentials(t *testing.T) { 284 secrets := []runtime.Object{ 285 dockerjson, 286 badDockerjson, 287 genericCert, 288 } 289 client := kube.NewFakeClient(secrets...) 290 sc := NewCredentialsController(client, nil) 291 client.RunAndWait(test.NewStop(t)) 292 cases := []struct { 293 name string 294 namespace string 295 expectedType corev1.SecretType 296 expectedDockerCred string 297 expectedDockerError string 298 }{ 299 { 300 name: "docker-json", 301 namespace: "default", 302 expectedType: corev1.SecretTypeDockerConfigJson, 303 expectedDockerCred: "docker-cred", 304 }, 305 { 306 name: "bad-docker-json", 307 namespace: "default", 308 expectedDockerError: "cannot find docker config at secret default/bad-docker-json", 309 }, 310 { 311 name: "wrong-name", 312 namespace: "default", 313 expectedDockerError: "secret default/wrong-name not found", 314 }, 315 { 316 name: "generic", 317 namespace: "default", 318 expectedDockerError: "type of secret default/generic is not kubernetes.io/dockerconfigjson", 319 }, 320 } 321 for _, tt := range cases { 322 t.Run(tt.name, func(t *testing.T) { 323 dockerCred, err := sc.GetDockerCredential(tt.name, tt.namespace) 324 if tt.expectedDockerCred != "" && tt.expectedDockerCred != string(dockerCred) { 325 t.Errorf("got docker credential %q, want %q", string(dockerCred), tt.expectedDockerCred) 326 } 327 if tt.expectedDockerError != "" && tt.expectedDockerError != errString(err) { 328 t.Errorf("got docker err %q, wanted %q", errString(err), tt.expectedDockerError) 329 } 330 }) 331 } 332 } 333 334 func errString(e error) string { 335 if e == nil { 336 return "" 337 } 338 return e.Error() 339 } 340 341 func allowIdentities(c kube.Client, identities ...string) { 342 allowed := sets.New(identities...) 343 c.Kube().(*fake.Clientset).Fake.PrependReactor("create", "subjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) { 344 a := action.(k8stesting.CreateAction).GetObject().(*authorizationv1.SubjectAccessReview) 345 if allowed.Contains(a.Spec.User) { 346 return true, &authorizationv1.SubjectAccessReview{ 347 Status: authorizationv1.SubjectAccessReviewStatus{ 348 Allowed: true, 349 }, 350 }, nil 351 } 352 return true, &authorizationv1.SubjectAccessReview{ 353 Status: authorizationv1.SubjectAccessReviewStatus{ 354 Allowed: false, 355 Reason: fmt.Sprintf("user %s cannot access secrets", a.Spec.User), 356 }, 357 }, nil 358 }) 359 } 360 361 func TestForCluster(t *testing.T) { 362 stop := test.NewStop(t) 363 localClient := kube.NewFakeClient() 364 localClient.RunAndWait(stop) 365 remoteClient := kube.NewFakeClient() 366 remoteClient.RunAndWait(stop) 367 mc := multicluster.NewFakeController() 368 sc := NewMulticluster("local", mc) 369 mc.Add("local", localClient, stop) 370 mc.Add("remote", remoteClient, stop) 371 mc.Add("remote2", remoteClient, stop) 372 cases := []struct { 373 cluster cluster2.ID 374 allowed bool 375 }{ 376 {"local", true}, 377 {"remote", true}, 378 {"remote2", true}, 379 {"invalid", false}, 380 } 381 for _, tt := range cases { 382 t.Run(string(tt.cluster), func(t *testing.T) { 383 _, err := sc.ForCluster(tt.cluster) 384 if (err == nil) != tt.allowed { 385 t.Fatalf("expected allowed=%v, got err=%v", tt.allowed, err) 386 } 387 }) 388 } 389 } 390 391 func TestAuthorize(t *testing.T) { 392 stop := test.NewStop(t) 393 localClient := kube.NewFakeClient() 394 localClient.RunAndWait(stop) 395 remoteClient := kube.NewFakeClient() 396 remoteClient.RunAndWait(stop) 397 allowIdentities(localClient, "system:serviceaccount:ns-local:sa-allowed") 398 allowIdentities(remoteClient, "system:serviceaccount:ns-remote:sa-allowed") 399 mc := multicluster.NewFakeController() 400 sc := NewMulticluster("local", mc) 401 mc.Add("local", localClient, stop) 402 mc.Add("remote", remoteClient, stop) 403 cases := []struct { 404 sa string 405 ns string 406 cluster cluster2.ID 407 allowed bool 408 }{ 409 {"sa-denied", "ns-local", "local", false}, 410 {"sa-allowed", "ns-local", "local", true}, 411 {"sa-denied", "ns-local", "remote", false}, 412 {"sa-allowed", "ns-local", "remote", false}, 413 {"sa-denied", "ns-remote", "local", false}, 414 {"sa-allowed", "ns-remote", "local", false}, 415 {"sa-denied", "ns-remote", "remote", false}, 416 {"sa-allowed", "ns-remote", "remote", true}, 417 } 418 for _, tt := range cases { 419 t.Run(fmt.Sprintf("%v/%v/%v", tt.sa, tt.ns, tt.cluster), func(t *testing.T) { 420 con, err := sc.ForCluster(tt.cluster) 421 if err != nil { 422 t.Fatal(err) 423 } 424 got := con.Authorize(tt.sa, tt.ns) 425 if (got == nil) != tt.allowed { 426 t.Fatalf("expected allowed=%v, got error=%v", tt.allowed, got) 427 } 428 }) 429 } 430 } 431 432 func TestSecretsControllerMulticluster(t *testing.T) { 433 stop := make(chan struct{}) 434 defer close(stop) 435 secretsLocal := []runtime.Object{ 436 tlsCert, 437 tlsMtlsCert, 438 tlsMtlsCertSplit, 439 tlsMtlsCertSplitCa, 440 } 441 tlsCertModified := makeSecret("tls", map[string]string{ 442 TLSSecretCert: "tls-cert-mod", TLSSecretKey: "tls-key", 443 }, corev1.SecretTypeTLS) 444 secretsRemote := []runtime.Object{ 445 tlsCertModified, 446 genericCert, 447 genericMtlsCert, 448 genericMtlsCertSplit, 449 genericMtlsCertSplitCa, 450 } 451 452 localClient := kube.NewFakeClient(secretsLocal...) 453 remoteClient := kube.NewFakeClient(secretsRemote...) 454 otherRemoteClient := kube.NewFakeClient() 455 mc := multicluster.NewFakeController() 456 sc := NewMulticluster("local", mc) 457 mc.Add("local", localClient, stop) 458 mc.Add("remote", remoteClient, stop) 459 mc.Add("other", otherRemoteClient, stop) 460 461 // normally the remote secrets controller would start these 462 localClient.RunAndWait(stop) 463 remoteClient.RunAndWait(stop) 464 otherRemoteClient.RunAndWait(stop) 465 466 cases := []struct { 467 name string 468 namespace string 469 cluster cluster2.ID 470 cert string 471 key string 472 caCert string 473 }{ 474 // From local cluster 475 // These are only in remote cluster, we do not have access 476 {"generic", "default", "local", "", "", ""}, 477 {"generic-mtls", "default", "local", "", "", ""}, 478 {"generic-mtls-split", "default", "local", "", "", ""}, 479 {"generic-mtls-split-cacert", "default", "local", "", "", ""}, 480 // These are in local cluster, we can access 481 {"tls", "default", "local", "tls-cert", "tls-key", ""}, 482 {"tls-mtls", "default", "local", "tls-mtls-cert", "tls-mtls-key", "tls-mtls-ca"}, 483 {"tls-mtls-split", "default", "local", "tls-mtls-split-cert", "tls-mtls-split-key", ""}, 484 {"tls-mtls-split-cacert", "default", "local", "", "", "tls-mtls-split-ca"}, 485 {"generic", "wrong-namespace", "local", "", "", ""}, 486 487 // From remote cluster 488 // We can access all credentials - local and remote 489 {"generic", "default", "remote", "generic-cert", "generic-key", ""}, 490 {"generic-mtls", "default", "remote", "generic-mtls-cert", "generic-mtls-key", "generic-mtls-ca"}, 491 {"generic-mtls-split", "default", "remote", "generic-mtls-split-cert", "generic-mtls-split-key", ""}, 492 {"generic-mtls-split-cacert", "default", "remote", "", "", "generic-mtls-split-ca"}, 493 // This is present in local and remote, but with a different value. We have the remote. 494 {"tls", "default", "remote", "tls-cert-mod", "tls-key", ""}, 495 {"tls-mtls", "default", "remote", "tls-mtls-cert", "tls-mtls-key", "tls-mtls-ca"}, 496 {"tls-mtls-split", "default", "remote", "tls-mtls-split-cert", "tls-mtls-split-key", ""}, 497 {"tls-mtls-split-cacert", "default", "remote", "", "", "tls-mtls-split-ca"}, 498 {"generic", "wrong-namespace", "remote", "", "", ""}, 499 500 // From other remote cluster 501 // We have no in cluster credentials; can only access those in config cluster 502 {"generic", "default", "other", "", "", ""}, 503 {"generic-mtls", "default", "other", "", "", ""}, 504 {"generic-mtls-split", "default", "other", "", "", ""}, 505 {"generic-mtls-split-cacert", "default", "other", "", "", ""}, 506 {"tls", "default", "other", "tls-cert", "tls-key", ""}, 507 {"tls-mtls", "default", "other", "tls-mtls-cert", "tls-mtls-key", "tls-mtls-ca"}, 508 {"tls-mtls-split", "default", "other", "tls-mtls-split-cert", "tls-mtls-split-key", ""}, 509 {"tls-mtls-split-cacert", "default", "other", "", "", "tls-mtls-split-ca"}, 510 {"generic", "wrong-namespace", "other", "", "", ""}, 511 } 512 for _, tt := range cases { 513 t.Run(fmt.Sprintf("%s-%v", tt.name, tt.cluster), func(t *testing.T) { 514 con, err := sc.ForCluster(tt.cluster) 515 if err != nil { 516 t.Fatal(err) 517 } 518 certInfo, _ := con.GetCertInfo(tt.name, tt.namespace) 519 var actualKey []byte 520 var actualCert []byte 521 if certInfo != nil { 522 actualKey = certInfo.Key 523 actualCert = certInfo.Cert 524 } 525 if tt.key != string(actualKey) { 526 t.Errorf("got key %q, wanted %q", string(actualKey), tt.key) 527 } 528 if tt.cert != string(actualCert) { 529 t.Errorf("got cert %q, wanted %q", string(actualCert), tt.cert) 530 } 531 caCertInfo, err := con.GetCaCert(tt.name, tt.namespace) 532 if caCertInfo != nil && tt.caCert != string(caCertInfo.Cert) { 533 t.Errorf("got caCert %q, wanted %q with err %v", string(caCertInfo.Cert), tt.caCert, err) 534 } 535 }) 536 } 537 }