@@ -53,6 +53,8 @@ func TestKeyController(t *testing.T) {
5353 validateFunc func (ts * testing.T , actions []clientgotesting.Action , targetNamespace string , targetGRs []schema.GroupResource )
5454 validateOperatorClientFunc func (ts * testing.T , operatorClient v1helpers.OperatorClient )
5555 expectedError error
56+ kmsConfigHash string
57+ kmsKeyIDHash string
5658 }{
5759 {
5860 name : "no apiservers config" ,
@@ -324,6 +326,253 @@ func TestKeyController(t *testing.T) {
324326 }
325327 },
326328 },
329+
330+ // KMS mode test cases
331+ {
332+ name : "KMS: creates first encryption key when none exists" ,
333+ targetGRs : []schema.GroupResource {
334+ {Group : "" , Resource : "secrets" },
335+ },
336+ kmsConfigHash : "config-hash-12345678" ,
337+ kmsKeyIDHash : "key-id-hash-abcdefgh" ,
338+ initialObjects : []runtime.Object {
339+ encryptiontesting .CreateDummyKubeAPIPod ("kube-apiserver-1" , "kms" , "node-1" ),
340+ },
341+ apiServerObjects : []runtime.Object {
342+ func () * configv1.APIServer {
343+ apiServer := & configv1.APIServer {ObjectMeta : metav1.ObjectMeta {Name : "cluster" }}
344+ apiServer .Spec .Encryption = configv1.APIServerEncryption {
345+ Type : "kms" ,
346+ KMS : & configv1.KMSConfig {
347+ Type : configv1 .AWSKMSProvider ,
348+ AWS : & configv1.AWSKMSConfig {
349+ KeyARN : "arn:aws:kms:us-east-1:123456789012:key/test-key" ,
350+ Region : "us-east-1" ,
351+ },
352+ },
353+ }
354+ return apiServer
355+ }(),
356+ },
357+ targetNamespace : "kms" ,
358+ expectedActions : []string {
359+ "list:pods:kms" ,
360+ "get:secrets:kms" ,
361+ "list:secrets:openshift-config-managed" ,
362+ "create:secrets:openshift-config-managed" ,
363+ "create:events:kms" ,
364+ },
365+ validateFunc : func (ts * testing.T , actions []clientgotesting.Action , targetNamespace string , targetGRs []schema.GroupResource ) {
366+ wasSecretValidated := false
367+ for _ , action := range actions {
368+ if action .Matches ("create" , "secrets" ) {
369+ createAction := action .(clientgotesting.CreateAction )
370+ actualSecret := createAction .GetObject ().(* corev1.Secret )
371+
372+ // Verify KMS annotations are set
373+ if actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ] == "" {
374+ ts .Error ("expected KMS config hash annotation to be set" )
375+ }
376+ if actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/kms-key-id-hash" ] == "" {
377+ ts .Error ("expected KMS key ID hash annotation to be set" )
378+ }
379+ if actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/mode" ] != "kms" {
380+ ts .Errorf ("expected mode to be 'kms', got '%s'" , actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/mode" ])
381+ }
382+
383+ wasSecretValidated = true
384+ break
385+ }
386+ }
387+ if ! wasSecretValidated {
388+ ts .Errorf ("the secret wasn't created and validated" )
389+ }
390+ },
391+ },
392+
393+ {
394+ name : "KMS: creates new key when KMS config hash changes" ,
395+ targetGRs : []schema.GroupResource {
396+ {Group : "" , Resource : "secrets" },
397+ },
398+ kmsConfigHash : "new-config-hash-xyz" , // Different hash for new key ARN
399+ kmsKeyIDHash : "key-id-hash-abcd1234" ,
400+ initialObjects : []runtime.Object {
401+ encryptiontesting .CreateDummyKubeAPIPod ("kube-apiserver-1" , "kms" , "node-1" ),
402+ func () * corev1.Secret {
403+ s := encryptiontesting .CreateEncryptionKeySecretNoDataWithMode ("kms" , []schema.GroupResource {{Group : "" , Resource : "secrets" }}, 1 , "kms" )
404+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ] = "old-config-hash-1234"
405+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-key-id-hash" ] = "key-id-hash-abcd1234"
406+ return s
407+ }(),
408+ },
409+ apiServerObjects : []runtime.Object {
410+ func () * configv1.APIServer {
411+ apiServer := & configv1.APIServer {ObjectMeta : metav1.ObjectMeta {Name : "cluster" }}
412+ apiServer .Spec .Encryption = configv1.APIServerEncryption {
413+ Type : "kms" ,
414+ KMS : & configv1.KMSConfig {
415+ Type : configv1 .AWSKMSProvider ,
416+ AWS : & configv1.AWSKMSConfig {
417+ KeyARN : "arn:aws:kms:us-east-1:123456789012:key/new-key" , // Different key
418+ Region : "us-east-1" ,
419+ },
420+ },
421+ }
422+ return apiServer
423+ }(),
424+ },
425+ targetNamespace : "kms" ,
426+ expectedActions : []string {
427+ "list:pods:kms" ,
428+ "get:secrets:kms" ,
429+ "list:secrets:openshift-config-managed" ,
430+ "create:secrets:openshift-config-managed" ,
431+ "create:events:kms" ,
432+ },
433+ validateFunc : func (ts * testing.T , actions []clientgotesting.Action , targetNamespace string , targetGRs []schema.GroupResource ) {
434+ wasSecretValidated := false
435+ for _ , action := range actions {
436+ if action .Matches ("create" , "secrets" ) {
437+ createAction := action .(clientgotesting.CreateAction )
438+ actualSecret := createAction .GetObject ().(* corev1.Secret )
439+
440+ // Verify new KMS config hash is set and different from old
441+ newConfigHash := actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ]
442+ if newConfigHash == "" || newConfigHash == "old-config-hash-1234" {
443+ ts .Errorf ("expected new KMS config hash, got '%s'" , newConfigHash )
444+ }
445+
446+ // Verify internal reason mentions config change
447+ internalReason := actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/internal-reason" ]
448+ if internalReason != "secrets-kms-config-changed" {
449+ ts .Errorf ("expected internal reason 'secrets-kms-config-changed', got '%s'" , internalReason )
450+ }
451+
452+ wasSecretValidated = true
453+ break
454+ }
455+ }
456+ if ! wasSecretValidated {
457+ ts .Errorf ("the secret wasn't created and validated" )
458+ }
459+ },
460+ },
461+
462+ {
463+ name : "KMS: creates new key when KMS key ID hash changes (key rotation)" ,
464+ targetGRs : []schema.GroupResource {
465+ {Group : "" , Resource : "secrets" },
466+ },
467+ kmsConfigHash : "config-hash-12345678" ,
468+ kmsKeyIDHash : "new-key-id-hash-xyz" ,
469+ initialObjects : []runtime.Object {
470+ encryptiontesting .CreateDummyKubeAPIPod ("kube-apiserver-1" , "kms" , "node-1" ),
471+ func () * corev1.Secret {
472+ s := encryptiontesting .CreateEncryptionKeySecretNoDataWithMode ("kms" , []schema.GroupResource {{Group : "" , Resource : "secrets" }}, 1 , "kms" )
473+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ] = "config-hash-12345678"
474+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-key-id-hash" ] = "old-key-id-hash-abc"
475+ return s
476+ }(),
477+ },
478+ apiServerObjects : []runtime.Object {
479+ func () * configv1.APIServer {
480+ apiServer := & configv1.APIServer {ObjectMeta : metav1.ObjectMeta {Name : "cluster" }}
481+ apiServer .Spec .Encryption = configv1.APIServerEncryption {
482+ Type : "kms" ,
483+ KMS : & configv1.KMSConfig {
484+ Type : configv1 .AWSKMSProvider ,
485+ AWS : & configv1.AWSKMSConfig {
486+ KeyARN : "arn:aws:kms:us-east-1:123456789012:key/test-key" ,
487+ Region : "us-east-1" ,
488+ },
489+ },
490+ }
491+ return apiServer
492+ }(),
493+ },
494+ targetNamespace : "kms" ,
495+ expectedActions : []string {
496+ "list:pods:kms" ,
497+ "get:secrets:kms" ,
498+ "list:secrets:openshift-config-managed" ,
499+ "create:secrets:openshift-config-managed" ,
500+ "create:events:kms" ,
501+ },
502+ validateFunc : func (ts * testing.T , actions []clientgotesting.Action , targetNamespace string , targetGRs []schema.GroupResource ) {
503+ wasSecretValidated := false
504+ for _ , action := range actions {
505+ if action .Matches ("create" , "secrets" ) {
506+ createAction := action .(clientgotesting.CreateAction )
507+ actualSecret := createAction .GetObject ().(* corev1.Secret )
508+
509+ // Verify new KMS key ID hash is set and different from old
510+ newKeyIDHash := actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/kms-key-id-hash" ]
511+ if newKeyIDHash == "" || newKeyIDHash == "old-key-id-hash-abc" {
512+ ts .Errorf ("expected new KMS key ID hash, got '%s'" , newKeyIDHash )
513+ }
514+
515+ // Verify config hash stays the same
516+ configHash := actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ]
517+ if configHash != "config-hash-12345678" {
518+ ts .Errorf ("expected config hash to remain 'config-hash-12345678', got '%s'" , configHash )
519+ }
520+
521+ // Verify internal reason mentions key ID change
522+ internalReason := actualSecret .Annotations ["encryption.apiserver.operator.openshift.io/internal-reason" ]
523+ if internalReason != "secrets-kms-key-id-changed" {
524+ ts .Errorf ("expected internal reason 'secrets-kms-key-id-changed', got '%s'" , internalReason )
525+ }
526+
527+ wasSecretValidated = true
528+ break
529+ }
530+ }
531+ if ! wasSecretValidated {
532+ ts .Errorf ("the secret wasn't created and validated" )
533+ }
534+ },
535+ },
536+
537+ {
538+ name : "KMS: no-op when hashes match and key is migrated" ,
539+ targetGRs : []schema.GroupResource {
540+ {Group : "" , Resource : "secrets" },
541+ },
542+ kmsConfigHash : "config-hash-12345678" ,
543+ kmsKeyIDHash : "key-id-hash-abcdefgh" ,
544+ initialObjects : []runtime.Object {
545+ encryptiontesting .CreateDummyKubeAPIPod ("kube-apiserver-1" , "kms" , "node-1" ),
546+ func () * corev1.Secret {
547+ s := encryptiontesting .CreateEncryptionKeySecretNoDataWithMode ("kms" , []schema.GroupResource {{Group : "" , Resource : "secrets" }}, 1 , "kms" )
548+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-config-hash" ] = "config-hash-12345678"
549+ s .Annotations ["encryption.apiserver.operator.openshift.io/kms-key-id-hash" ] = "key-id-hash-abcdefgh"
550+ return s
551+ }(),
552+ },
553+ apiServerObjects : []runtime.Object {
554+ func () * configv1.APIServer {
555+ apiServer := & configv1.APIServer {ObjectMeta : metav1.ObjectMeta {Name : "cluster" }}
556+ apiServer .Spec .Encryption = configv1.APIServerEncryption {
557+ Type : "kms" ,
558+ KMS : & configv1.KMSConfig {
559+ Type : configv1 .AWSKMSProvider ,
560+ AWS : & configv1.AWSKMSConfig {
561+ KeyARN : "arn:aws:kms:us-east-1:123456789012:key/test-key" ,
562+ Region : "us-east-1" ,
563+ },
564+ },
565+ }
566+ return apiServer
567+ }(),
568+ },
569+ targetNamespace : "kms" ,
570+ expectedActions : []string {
571+ "list:pods:kms" ,
572+ "get:secrets:kms" ,
573+ "list:secrets:openshift-config-managed" ,
574+ },
575+ },
327576 }
328577
329578 for _ , scenario := range scenarios {
@@ -375,6 +624,12 @@ func TestKeyController(t *testing.T) {
375624
376625 target := NewKeyController (scenario .targetNamespace , nil , provider , deployer , alwaysFulfilledPreconditions , fakeOperatorClient , fakeApiServerClient , fakeApiServerInformer , kubeInformers , fakeSecretClient , scenario .encryptionSecretSelector , eventRecorder )
377626
627+ if scenario .kmsConfigHash != "" || scenario .kmsKeyIDHash != "" {
628+ kmsHashesGetterFunc = func (ctx context.Context , kmsConfig * configv1.KMSConfig ) (string , string , error ) {
629+ return scenario .kmsConfigHash , scenario .kmsKeyIDHash , nil
630+ }
631+ }
632+
378633 // act
379634 err = target .Sync (context .TODO (), factory .NewSyncContext ("test" , eventRecorder ))
380635
0 commit comments