@@ -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