From 1d0f9b8784d8e3ba4eda6f13df815058d13eb9c4 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 09:34:23 +0200 Subject: [PATCH 01/13] encryption: refactor GetEncryptionConfigAndState to accept functions instead of clients Replace the deployer interface and secretClient/encryptionSecretSelector parameters with two injected functions, making the inputs explicit and decoupling the statemachine from specific client types. All five callers are updated with inline closures binding their existing clients. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/condition_controller.go | 11 ++++++++++- .../encryption/controllers/key_controller.go | 11 +++++++++-- .../encryption/controllers/migration_controller.go | 9 ++++++++- .../encryption/controllers/prune_controller.go | 9 ++++++++- .../encryption/controllers/state_controller.go | 11 ++++++++++- pkg/operator/encryption/statemachine/transition.go | 12 ++++-------- 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/pkg/operator/encryption/controllers/condition_controller.go b/pkg/operator/encryption/controllers/condition_controller.go index ee4adb7717..34855d0326 100644 --- a/pkg/operator/encryption/controllers/condition_controller.go +++ b/pkg/operator/encryption/controllers/condition_controller.go @@ -7,6 +7,7 @@ import ( "time" operatorv1 "github.com/openshift/api/operator/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" @@ -16,6 +17,7 @@ import ( applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1" "github.com/openshift/library-go/pkg/controller/factory" "github.com/openshift/library-go/pkg/operator/encryption/encryptiondata" + "github.com/openshift/library-go/pkg/operator/encryption/secrets" "github.com/openshift/library-go/pkg/operator/encryption/state" "github.com/openshift/library-go/pkg/operator/encryption/statemachine" "github.com/openshift/library-go/pkg/operator/events" @@ -96,7 +98,14 @@ func (c *conditionController) sync(ctx context.Context, _ factory.SyncContext) ( } encryptedGRs := c.provider.EncryptedGRs() - currentConfig, desiredState, foundSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState(ctx, c.deployer, c.secretClient, c.encryptionSecretSelector, encryptedGRs) + currentConfig, desiredState, foundSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState( + ctx, + c.deployer.DeployedEncryptionConfigSecret, + func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + }, + encryptedGRs, + ) if err != nil || len(transitioningReason) > 0 { // do not update the encryption condition (cond). Note: progressing is set elsewhere. cond = nil diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index 653c643c71..fa6ed908f7 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -174,7 +174,14 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact return err } - currentConfig, desiredEncryptionState, secrets, isProgressingReason, err := statemachine.GetEncryptionConfigAndState(ctx, c.deployer, c.secretClient, c.encryptionSecretSelector, encryptedGRs) + currentConfig, desiredEncryptionState, encryptionSecrets, isProgressingReason, err := statemachine.GetEncryptionConfigAndState( + ctx, + c.deployer.DeployedEncryptionConfigSecret, + func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + }, + encryptedGRs, + ) if err != nil { return err } @@ -184,7 +191,7 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact } // avoid intended start of encryption - hasBeenOnBefore := currentConfig != nil || len(secrets) > 0 + hasBeenOnBefore := currentConfig != nil || len(encryptionSecrets) > 0 if currentMode == state.Identity && !hasBeenOnBefore { return nil } diff --git a/pkg/operator/encryption/controllers/migration_controller.go b/pkg/operator/encryption/controllers/migration_controller.go index 7fc649f020..52304fad08 100644 --- a/pkg/operator/encryption/controllers/migration_controller.go +++ b/pkg/operator/encryption/controllers/migration_controller.go @@ -168,7 +168,14 @@ func (c *migrationController) sync(ctx context.Context, syncCtx factory.SyncCont // TODO doc func (c *migrationController) migrateKeysIfNeededAndRevisionStable(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) (migratingResources []schema.GroupResource, err error) { // no storage migration during revision changes - currentEncryptionConfig, desiredEncryptionState, _, isTransitionalReason, err := statemachine.GetEncryptionConfigAndState(ctx, c.deployer, c.secretClient, c.encryptionSecretSelector, encryptedGRs) + currentEncryptionConfig, desiredEncryptionState, _, isTransitionalReason, err := statemachine.GetEncryptionConfigAndState( + ctx, + c.deployer.DeployedEncryptionConfigSecret, + func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + }, + encryptedGRs, + ) if err != nil { return nil, err } diff --git a/pkg/operator/encryption/controllers/prune_controller.go b/pkg/operator/encryption/controllers/prune_controller.go index 3078304e0b..09189d00d3 100644 --- a/pkg/operator/encryption/controllers/prune_controller.go +++ b/pkg/operator/encryption/controllers/prune_controller.go @@ -121,7 +121,14 @@ func (c *pruneController) sync(ctx context.Context, syncCtx factory.SyncContext) } func (c *pruneController) deleteOldMigratedSecrets(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) error { - _, desiredEncryptionConfig, _, isProgressingReason, err := statemachine.GetEncryptionConfigAndState(ctx, c.deployer, c.secretClient, c.encryptionSecretSelector, encryptedGRs) + _, desiredEncryptionConfig, _, isProgressingReason, err := statemachine.GetEncryptionConfigAndState( + ctx, + c.deployer.DeployedEncryptionConfigSecret, + func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + }, + encryptedGRs, + ) if err != nil { return err } diff --git a/pkg/operator/encryption/controllers/state_controller.go b/pkg/operator/encryption/controllers/state_controller.go index 85b224e19c..83c9790a07 100644 --- a/pkg/operator/encryption/controllers/state_controller.go +++ b/pkg/operator/encryption/controllers/state_controller.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" @@ -16,6 +17,7 @@ import ( configv1informers "github.com/openshift/client-go/config/informers/externalversions/config/v1" "github.com/openshift/library-go/pkg/controller/factory" "github.com/openshift/library-go/pkg/operator/encryption/encryptiondata" + "github.com/openshift/library-go/pkg/operator/encryption/secrets" "github.com/openshift/library-go/pkg/operator/encryption/state" "github.com/openshift/library-go/pkg/operator/encryption/statemachine" "github.com/openshift/library-go/pkg/operator/events" @@ -127,7 +129,14 @@ type eventWithReason struct { } func (c *stateController) generateAndApplyCurrentEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, recorder events.Recorder, encryptedGRs []schema.GroupResource) error { - currentConfig, desiredEncryptionState, encryptionSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState(ctx, c.deployer, c.secretClient, c.encryptionSecretSelector, encryptedGRs) + currentConfig, desiredEncryptionState, encryptionSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState( + ctx, + c.deployer.DeployedEncryptionConfigSecret, + func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + }, + encryptedGRs, + ) if err != nil { return err } diff --git a/pkg/operator/encryption/statemachine/transition.go b/pkg/operator/encryption/statemachine/transition.go index 12eb5b8b90..10ae48712f 100644 --- a/pkg/operator/encryption/statemachine/transition.go +++ b/pkg/operator/encryption/statemachine/transition.go @@ -5,14 +5,11 @@ import ( "fmt" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - corev1client "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "github.com/openshift/library-go/pkg/operator/encryption/encryptiondata" - "github.com/openshift/library-go/pkg/operator/encryption/secrets" "github.com/openshift/library-go/pkg/operator/encryption/state" ) @@ -30,13 +27,12 @@ type Deployer interface { func GetEncryptionConfigAndState( ctx context.Context, - deployer Deployer, - secretClient corev1client.SecretsGetter, - encryptionSecretSelector metav1.ListOptions, + getDeployedEncryptionConfigSecret func(context.Context) (*corev1.Secret, bool, error), + listKeySecrets func(context.Context) ([]*corev1.Secret, error), encryptedGRs []schema.GroupResource, ) (current *encryptiondata.Config, desired map[schema.GroupResource]state.GroupResourceState, encryptionSecrets []*corev1.Secret, transitioningReason string, err error) { // get current config - encryptionConfigSecret, converged, err := deployer.DeployedEncryptionConfigSecret(ctx) + encryptionConfigSecret, converged, err := getDeployedEncryptionConfigSecret(ctx) if err != nil { return nil, nil, nil, "", err } @@ -52,7 +48,7 @@ func GetEncryptionConfigAndState( } // compute desired config - encryptionSecrets, err = secrets.ListKeySecrets(ctx, secretClient, encryptionSecretSelector) + encryptionSecrets, err = listKeySecrets(ctx) if err != nil { return nil, nil, nil, "", err } From 00e6a0dfbc4a5f005ddec0cd060ed5b150822155 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 09:44:25 +0200 Subject: [PATCH 02/13] encryption: checkAndCreateKeys returns secret instead of creating it Move the secret creation side effect out of checkAndCreateKeys into sync, making the function's output explicit. sync now handles Create, AlreadyExists validation, and event recording. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index fa6ed908f7..e4475836c9 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -154,7 +154,19 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - err = c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs()) + keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs()) + if err == nil && keySecret != nil { + keyID, _ := state.NameToKeyID(keySecret.Name) + _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) + if errors.IsAlreadyExists(createErr) { + err = c.validateExistingSecret(ctx, keySecret, keyID) + } else if createErr != nil { + syncCtx.Recorder().Warningf("EncryptionKeyCreateFailed", "Secret %q failed to create: %v", keySecret.Name, createErr) + err = createErr + } else { + syncCtx.Recorder().Eventf("EncryptionKeyCreated", "Secret %q successfully created", keySecret.Name) + } + } if err != nil { degradedCondition = degradedCondition. WithStatus(operatorv1.ConditionTrue). @@ -168,10 +180,10 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err } -func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) error { +func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) (*corev1.Secret, error) { currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx) if err != nil { - return err + return nil, err } currentConfig, desiredEncryptionState, encryptionSecrets, isProgressingReason, err := statemachine.GetEncryptionConfigAndState( @@ -183,17 +195,17 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact encryptedGRs, ) if err != nil { - return err + return nil, err } if len(isProgressingReason) > 0 { syncContext.Queue().AddAfter(syncContext.QueueKey(), 2*time.Minute) - return nil + return nil, nil } // avoid intended start of encryption hasBeenOnBefore := currentConfig != nil || len(encryptionSecrets) > 0 if currentMode == state.Identity && !hasBeenOnBefore { - return nil + return nil, nil } var ( @@ -211,7 +223,7 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact var err error desiredProviderCfg, err = newKMSProviderConfig(apiEncryptionConfiguration.KMS) if err != nil { - return err + return nil, err } } @@ -219,7 +231,7 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact for gr, grKeys := range desiredEncryptionState { latestKeyID, internalReason, needed, err := needsNewKey(grKeys, currentMode, externalReason, encryptedGRs, desiredProviderCfg) if err != nil { - return err + return nil, err } if !needed { continue @@ -239,7 +251,7 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact reasons = append(reasons, fmt.Sprintf("%s-%s", gr.Resource, internalReason)) } if !newKeyRequired { - return nil + return nil, nil } if commonReason != nil && len(*commonReason) > 0 && len(reasons) > 1 { reasons = []string{*commonReason} // don't repeat reasons @@ -249,20 +261,9 @@ func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext fact internalReason := strings.Join(reasons, ", ") keySecret, err := c.generateKeySecret(ctx, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason) if err != nil { - return fmt.Errorf("failed to create key: %v", err) - } - _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) - if errors.IsAlreadyExists(createErr) { - return c.validateExistingSecret(ctx, keySecret, newKeyID) + return nil, fmt.Errorf("failed to create key: %v", err) } - if createErr != nil { - syncContext.Recorder().Warningf("EncryptionKeyCreateFailed", "Secret %q failed to create: %v", keySecret.Name, err) - return createErr - } - - syncContext.Recorder().Eventf("EncryptionKeyCreated", "Secret %q successfully created: %q", keySecret.Name, reasons) - - return nil + return keySecret, nil } func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *corev1.Secret, keyID uint64) error { From 68c3d9269d1230e714f410594d8f22f15f7bbee3 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 09:59:52 +0200 Subject: [PATCH 03/13] encryption: inject resource-fetching functions into checkAndCreateKeys Refactor getCurrentModeReasonAndEncryptionConfig and the GetEncryptionConfigAndState call site to accept their client reads as explicit function parameters rather than using receiver fields directly. The three bound functions are stored on the struct and wired in the constructor, making all external dependencies of checkAndCreateKeys visible in its signature. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 48 +++++++++++++------ .../controllers/key_controller_test.go | 17 ++++++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index e4475836c9..6ff309b480 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -80,6 +80,10 @@ type keyController struct { preconditionsFulfilledFn preconditionsFulfilled unsupportedConfigPrefix []string + + getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) + deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error) + listKeySecretsFn func(context.Context) ([]*corev1.Secret, error) } func NewKeyController( @@ -113,6 +117,22 @@ func NewKeyController( configMapClient: configMapClient, } + c.getAPIServerAndOperatorSpecFn = func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { + apiServer, err := c.apiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + return nil, nil, err + } + operatorSpec, _, _, err := c.operatorClient.GetOperatorState() + if err != nil { + return nil, nil, err + } + return apiServer, operatorSpec, nil + } + c.deployedEncryptionConfigSecretFn = c.deployer.DeployedEncryptionConfigSecret + c.listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + } + return factory.New(). WithSync(c.sync). WithControllerInstanceName(c.controllerInstanceName). @@ -154,7 +174,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs()) + keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -180,18 +200,23 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err } -func (c *keyController) checkAndCreateKeys(ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource) (*corev1.Secret, error) { - currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx) +func (c *keyController) checkAndCreateKeys( + ctx context.Context, + syncContext factory.SyncContext, + encryptedGRs []schema.GroupResource, + getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), + deployedEncryptionConfigSecret func(context.Context) (*corev1.Secret, bool, error), + listKeySecrets func(context.Context) ([]*corev1.Secret, error), +) (*corev1.Secret, error) { + currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec) if err != nil { return nil, err } currentConfig, desiredEncryptionState, encryptionSecrets, isProgressingReason, err := statemachine.GetEncryptionConfigAndState( ctx, - c.deployer.DeployedEncryptionConfigSecret, - func(ctx context.Context) ([]*corev1.Secret, error) { - return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) - }, + deployedEncryptionConfigSecret, + listKeySecrets, encryptedGRs, ) if err != nil { @@ -346,13 +371,8 @@ func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, cur return secrets.FromKeyState(c.instanceName, ks) } -func (c *keyController) getCurrentModeReasonAndEncryptionConfig(ctx context.Context) (state.Mode, string, configv1.APIServerEncryption, error) { - apiServer, err := c.apiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) - if err != nil { - return "", "", configv1.APIServerEncryption{}, err - } - - operatorSpec, _, _, err := c.operatorClient.GetOperatorState() +func (c *keyController) getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error)) (state.Mode, string, configv1.APIServerEncryption, error) { + apiServer, operatorSpec, err := getAPIServerAndOperatorSpec(ctx) if err != nil { return "", "", configv1.APIServerEncryption{}, err } diff --git a/pkg/operator/encryption/controllers/key_controller_test.go b/pkg/operator/encryption/controllers/key_controller_test.go index 76e911c2be..7b6c920c5c 100644 --- a/pkg/operator/encryption/controllers/key_controller_test.go +++ b/pkg/operator/encryption/controllers/key_controller_test.go @@ -1275,8 +1275,21 @@ func TestGetCurrentModeReasonAndEncryptionConfig(t *testing.T) { fakeApiServerClient := fakeConfigClient.ConfigV1().APIServers() // act - target := keyController{unsupportedConfigPrefix: scenario.prefix, operatorClient: fakeOperatorClient, apiServerClient: fakeApiServerClient} - currentMode, externalReason, encryption, err := target.getCurrentModeReasonAndEncryptionConfig(context.TODO()) + target := keyController{unsupportedConfigPrefix: scenario.prefix} + currentMode, externalReason, encryption, err := target.getCurrentModeReasonAndEncryptionConfig( + context.TODO(), + func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { + apiServer, err := fakeApiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + return nil, nil, err + } + operatorSpec, _, _, err := fakeOperatorClient.GetOperatorState() + if err != nil { + return nil, nil, err + } + return apiServer, operatorSpec, nil + }, + ) // validate if err != nil { From 22a9754ee98dc043142a2dfa0cbe28c8d797aae5 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 10:02:13 +0200 Subject: [PATCH 04/13] encryption: inject KMS plugin resource fetchers into generateKeySecret Refactor generateKeySecret to accept getKMSPluginSecret and getKMSPluginConfigMap as explicit function parameters instead of using the receiver's secretClient and configMapClient directly. Both functions are stored on the struct, wired in the constructor, and threaded through checkAndCreateKeys following the same pattern as the other resource fetchers. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index 6ff309b480..27816d764a 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -84,6 +84,8 @@ type keyController struct { getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error) listKeySecretsFn func(context.Context) ([]*corev1.Secret, error) + getKMSPluginSecretFn func(context.Context, string) (*corev1.Secret, error) + getKMSPluginConfigMapFn func(context.Context, string) (*corev1.ConfigMap, error) } func NewKeyController( @@ -132,6 +134,12 @@ func NewKeyController( c.listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) } + c.getKMSPluginSecretFn = func(ctx context.Context, name string) (*corev1.Secret, error) { + return c.secretClient.Secrets(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) + } + c.getKMSPluginConfigMapFn = func(ctx context.Context, name string) (*corev1.ConfigMap, error) { + return c.configMapClient.ConfigMaps(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) + } return factory.New(). WithSync(c.sync). @@ -174,7 +182,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) + keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -207,6 +215,8 @@ func (c *keyController) checkAndCreateKeys( getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), deployedEncryptionConfigSecret func(context.Context) (*corev1.Secret, bool, error), listKeySecrets func(context.Context) ([]*corev1.Secret, error), + getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), + getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error), ) (*corev1.Secret, error) { currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec) if err != nil { @@ -284,7 +294,7 @@ func (c *keyController) checkAndCreateKeys( sort.Sort(sort.StringSlice(reasons)) internalReason := strings.Join(reasons, ", ") - keySecret, err := c.generateKeySecret(ctx, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason) + keySecret, err := c.generateKeySecret(ctx, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) if err != nil { return nil, fmt.Errorf("failed to create key: %v", err) } @@ -310,7 +320,7 @@ func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *c return nil // we made this key earlier } -func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string) (*corev1.Secret, error) { +func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { bs := crypto.ModeToNewKeyFunc[currentMode]() ks := state.KeyState{ Key: apiserverv1.Key{ @@ -335,7 +345,7 @@ func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, cur if secretName, expectedKeys, err := desiredProviderCfg.referencedSecretName(); err != nil { return nil, err } else if len(secretName) > 0 { - refSecret, err := c.secretClient.Secrets(openshiftConfigNS).Get(ctx, secretName, metav1.GetOptions{}) + refSecret, err := getKMSPluginSecret(ctx, secretName) if err != nil { return nil, fmt.Errorf("failed to get secret %s in %s: %w", secretName, openshiftConfigNS, err) } @@ -353,7 +363,7 @@ func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, cur if cmName, expectedKeys, err := desiredProviderCfg.referencedConfigMapName(); err != nil { return nil, err } else if len(cmName) > 0 { - refCM, err := c.configMapClient.ConfigMaps(openshiftConfigNS).Get(ctx, cmName, metav1.GetOptions{}) + refCM, err := getKMSPluginConfigMap(ctx, cmName) if err != nil { return nil, fmt.Errorf("failed to get configmap %s in %s: %w", cmName, openshiftConfigNS, err) } From c3931b6ee47cc5af0ce6ec4c61fa1f7dc61775f4 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 10:05:32 +0200 Subject: [PATCH 05/13] encryption: make instanceName and unsupportedConfigPrefix explicit inputs to checkAndCreateKeys Pass instanceName and unsupportedConfigPrefix as explicit parameters through checkAndCreateKeys into generateKeySecret and getCurrentModeReasonAndEncryptionConfig respectively, removing the last remaining receiver accesses from those helper methods. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 16 +++++++++------- .../controllers/key_controller_test.go | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index 27816d764a..b2e08ba0cb 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -182,7 +182,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) + keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -212,13 +212,15 @@ func (c *keyController) checkAndCreateKeys( ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource, + instanceName string, + unsupportedConfigPrefix []string, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), deployedEncryptionConfigSecret func(context.Context) (*corev1.Secret, bool, error), listKeySecrets func(context.Context) ([]*corev1.Secret, error), getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error), ) (*corev1.Secret, error) { - currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec) + currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec, unsupportedConfigPrefix) if err != nil { return nil, err } @@ -294,7 +296,7 @@ func (c *keyController) checkAndCreateKeys( sort.Sort(sort.StringSlice(reasons)) internalReason := strings.Join(reasons, ", ") - keySecret, err := c.generateKeySecret(ctx, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) + keySecret, err := c.generateKeySecret(ctx, instanceName, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) if err != nil { return nil, fmt.Errorf("failed to create key: %v", err) } @@ -320,7 +322,7 @@ func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *c return nil // we made this key earlier } -func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { +func (c *keyController) generateKeySecret(ctx context.Context, instanceName string, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { bs := crypto.ModeToNewKeyFunc[currentMode]() ks := state.KeyState{ Key: apiserverv1.Key{ @@ -378,16 +380,16 @@ func (c *keyController) generateKeySecret(ctx context.Context, keyID uint64, cur } } } - return secrets.FromKeyState(c.instanceName, ks) + return secrets.FromKeyState(instanceName, ks) } -func (c *keyController) getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error)) (state.Mode, string, configv1.APIServerEncryption, error) { +func (c *keyController) getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), unsupportedConfigPrefix []string) (state.Mode, string, configv1.APIServerEncryption, error) { apiServer, operatorSpec, err := getAPIServerAndOperatorSpec(ctx) if err != nil { return "", "", configv1.APIServerEncryption{}, err } - encryptionConfig, err := structuredUnsupportedConfigFrom(operatorSpec.UnsupportedConfigOverrides.Raw, c.unsupportedConfigPrefix) + encryptionConfig, err := structuredUnsupportedConfigFrom(operatorSpec.UnsupportedConfigOverrides.Raw, unsupportedConfigPrefix) if err != nil { return "", "", configv1.APIServerEncryption{}, err } diff --git a/pkg/operator/encryption/controllers/key_controller_test.go b/pkg/operator/encryption/controllers/key_controller_test.go index 7b6c920c5c..5a27619df5 100644 --- a/pkg/operator/encryption/controllers/key_controller_test.go +++ b/pkg/operator/encryption/controllers/key_controller_test.go @@ -1275,7 +1275,7 @@ func TestGetCurrentModeReasonAndEncryptionConfig(t *testing.T) { fakeApiServerClient := fakeConfigClient.ConfigV1().APIServers() // act - target := keyController{unsupportedConfigPrefix: scenario.prefix} + target := keyController{} currentMode, externalReason, encryption, err := target.getCurrentModeReasonAndEncryptionConfig( context.TODO(), func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { @@ -1289,6 +1289,7 @@ func TestGetCurrentModeReasonAndEncryptionConfig(t *testing.T) { } return apiServer, operatorSpec, nil }, + scenario.prefix, ) // validate From 4f6db87b87fcd1ac7068a67b64237eb00e17e7ed Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 10:08:04 +0200 Subject: [PATCH 06/13] encryption: promote checkAndCreateKeys and helpers to package-level functions Convert checkAndCreateKeys, getCurrentModeReasonAndEncryptionConfig and generateKeySecret from methods to package-level functions. The absence of a receiver proves all inputs are fully mapped out as explicit parameters. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 12 ++++++------ .../encryption/controllers/key_controller_test.go | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index b2e08ba0cb..93eb068686 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -182,7 +182,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := c.checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) + keySecret, err := checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -208,7 +208,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err } -func (c *keyController) checkAndCreateKeys( +func checkAndCreateKeys( ctx context.Context, syncContext factory.SyncContext, encryptedGRs []schema.GroupResource, @@ -220,7 +220,7 @@ func (c *keyController) checkAndCreateKeys( getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error), ) (*corev1.Secret, error) { - currentMode, externalReason, apiEncryptionConfiguration, err := c.getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec, unsupportedConfigPrefix) + currentMode, externalReason, apiEncryptionConfiguration, err := getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec, unsupportedConfigPrefix) if err != nil { return nil, err } @@ -296,7 +296,7 @@ func (c *keyController) checkAndCreateKeys( sort.Sort(sort.StringSlice(reasons)) internalReason := strings.Join(reasons, ", ") - keySecret, err := c.generateKeySecret(ctx, instanceName, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) + keySecret, err := generateKeySecret(ctx, instanceName, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) if err != nil { return nil, fmt.Errorf("failed to create key: %v", err) } @@ -322,7 +322,7 @@ func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *c return nil // we made this key earlier } -func (c *keyController) generateKeySecret(ctx context.Context, instanceName string, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { +func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { bs := crypto.ModeToNewKeyFunc[currentMode]() ks := state.KeyState{ Key: apiserverv1.Key{ @@ -383,7 +383,7 @@ func (c *keyController) generateKeySecret(ctx context.Context, instanceName stri return secrets.FromKeyState(instanceName, ks) } -func (c *keyController) getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), unsupportedConfigPrefix []string) (state.Mode, string, configv1.APIServerEncryption, error) { +func getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), unsupportedConfigPrefix []string) (state.Mode, string, configv1.APIServerEncryption, error) { apiServer, operatorSpec, err := getAPIServerAndOperatorSpec(ctx) if err != nil { return "", "", configv1.APIServerEncryption{}, err diff --git a/pkg/operator/encryption/controllers/key_controller_test.go b/pkg/operator/encryption/controllers/key_controller_test.go index 5a27619df5..a6e94ac8ec 100644 --- a/pkg/operator/encryption/controllers/key_controller_test.go +++ b/pkg/operator/encryption/controllers/key_controller_test.go @@ -1275,8 +1275,7 @@ func TestGetCurrentModeReasonAndEncryptionConfig(t *testing.T) { fakeApiServerClient := fakeConfigClient.ConfigV1().APIServers() // act - target := keyController{} - currentMode, externalReason, encryption, err := target.getCurrentModeReasonAndEncryptionConfig( + currentMode, externalReason, encryption, err := getCurrentModeReasonAndEncryptionConfig( context.TODO(), func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { apiServer, err := fakeApiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) From 184c88618f0da301ac3cb5b69d8f00a9fcf24913 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 10:09:39 +0200 Subject: [PATCH 07/13] encryption: add Fn suffix to function parameters to highlight they are functions Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_controller.go | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index 93eb068686..810d25aa53 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -81,11 +81,11 @@ type keyController struct { unsupportedConfigPrefix []string - getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) - deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error) + getAPIServerAndOperatorSpecFnFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) + deployedEncryptionConfigSecretFnFn func(context.Context) (*corev1.Secret, bool, error) listKeySecretsFn func(context.Context) ([]*corev1.Secret, error) - getKMSPluginSecretFn func(context.Context, string) (*corev1.Secret, error) - getKMSPluginConfigMapFn func(context.Context, string) (*corev1.ConfigMap, error) + getKMSPluginSecretFnFn func(context.Context, string) (*corev1.Secret, error) + getKMSPluginConfigMapFnFn func(context.Context, string) (*corev1.ConfigMap, error) } func NewKeyController( @@ -119,7 +119,7 @@ func NewKeyController( configMapClient: configMapClient, } - c.getAPIServerAndOperatorSpecFn = func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { + c.getAPIServerAndOperatorSpecFnFn = func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { apiServer, err := c.apiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) if err != nil { return nil, nil, err @@ -130,14 +130,14 @@ func NewKeyController( } return apiServer, operatorSpec, nil } - c.deployedEncryptionConfigSecretFn = c.deployer.DeployedEncryptionConfigSecret + c.deployedEncryptionConfigSecretFnFn = c.deployer.DeployedEncryptionConfigSecret c.listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) } - c.getKMSPluginSecretFn = func(ctx context.Context, name string) (*corev1.Secret, error) { + c.getKMSPluginSecretFnFn = func(ctx context.Context, name string) (*corev1.Secret, error) { return c.secretClient.Secrets(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) } - c.getKMSPluginConfigMapFn = func(ctx context.Context, name string) (*corev1.ConfigMap, error) { + c.getKMSPluginConfigMapFnFn = func(ctx context.Context, name string) (*corev1.ConfigMap, error) { return c.configMapClient.ConfigMaps(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) } @@ -182,7 +182,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFn, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn, c.getKMSPluginSecretFn, c.getKMSPluginConfigMapFn) + keySecret, err := checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFnFn, c.deployedEncryptionConfigSecretFnFn, c.listKeySecretsFn, c.getKMSPluginSecretFnFn, c.getKMSPluginConfigMapFnFn) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -214,21 +214,21 @@ func checkAndCreateKeys( encryptedGRs []schema.GroupResource, instanceName string, unsupportedConfigPrefix []string, - getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), - deployedEncryptionConfigSecret func(context.Context) (*corev1.Secret, bool, error), - listKeySecrets func(context.Context) ([]*corev1.Secret, error), - getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), - getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error), + getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), + deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error), + listKeySecretsFn func(context.Context) ([]*corev1.Secret, error), + getKMSPluginSecretFn func(context.Context, string) (*corev1.Secret, error), + getKMSPluginConfigMapFn func(context.Context, string) (*corev1.ConfigMap, error), ) (*corev1.Secret, error) { - currentMode, externalReason, apiEncryptionConfiguration, err := getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpec, unsupportedConfigPrefix) + currentMode, externalReason, apiEncryptionConfiguration, err := getCurrentModeReasonAndEncryptionConfig(ctx, getAPIServerAndOperatorSpecFn, unsupportedConfigPrefix) if err != nil { return nil, err } currentConfig, desiredEncryptionState, encryptionSecrets, isProgressingReason, err := statemachine.GetEncryptionConfigAndState( ctx, - deployedEncryptionConfigSecret, - listKeySecrets, + deployedEncryptionConfigSecretFn, + listKeySecretsFn, encryptedGRs, ) if err != nil { @@ -296,7 +296,7 @@ func checkAndCreateKeys( sort.Sort(sort.StringSlice(reasons)) internalReason := strings.Join(reasons, ", ") - keySecret, err := generateKeySecret(ctx, instanceName, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecret, getKMSPluginConfigMap) + keySecret, err := generateKeySecret(ctx, instanceName, newKeyID, currentMode, apiEncryptionConfiguration, desiredProviderCfg, internalReason, externalReason, getKMSPluginSecretFn, getKMSPluginConfigMapFn) if err != nil { return nil, fmt.Errorf("failed to create key: %v", err) } @@ -322,7 +322,7 @@ func (c *keyController) validateExistingSecret(ctx context.Context, keySecret *c return nil // we made this key earlier } -func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecret func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMap func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { +func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, currentMode state.Mode, apiServerEncryption configv1.APIServerEncryption, desiredProviderCfg kmsProviderConfig, internalReason, externalReason string, getKMSPluginSecretFn func(context.Context, string) (*corev1.Secret, error), getKMSPluginConfigMapFn func(context.Context, string) (*corev1.ConfigMap, error)) (*corev1.Secret, error) { bs := crypto.ModeToNewKeyFunc[currentMode]() ks := state.KeyState{ Key: apiserverv1.Key{ @@ -347,7 +347,7 @@ func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, c if secretName, expectedKeys, err := desiredProviderCfg.referencedSecretName(); err != nil { return nil, err } else if len(secretName) > 0 { - refSecret, err := getKMSPluginSecret(ctx, secretName) + refSecret, err := getKMSPluginSecretFn(ctx, secretName) if err != nil { return nil, fmt.Errorf("failed to get secret %s in %s: %w", secretName, openshiftConfigNS, err) } @@ -365,7 +365,7 @@ func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, c if cmName, expectedKeys, err := desiredProviderCfg.referencedConfigMapName(); err != nil { return nil, err } else if len(cmName) > 0 { - refCM, err := getKMSPluginConfigMap(ctx, cmName) + refCM, err := getKMSPluginConfigMapFn(ctx, cmName) if err != nil { return nil, fmt.Errorf("failed to get configmap %s in %s: %w", cmName, openshiftConfigNS, err) } @@ -383,8 +383,8 @@ func generateKeySecret(ctx context.Context, instanceName string, keyID uint64, c return secrets.FromKeyState(instanceName, ks) } -func getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpec func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), unsupportedConfigPrefix []string) (state.Mode, string, configv1.APIServerEncryption, error) { - apiServer, operatorSpec, err := getAPIServerAndOperatorSpec(ctx) +func getCurrentModeReasonAndEncryptionConfig(ctx context.Context, getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error), unsupportedConfigPrefix []string) (state.Mode, string, configv1.APIServerEncryption, error) { + apiServer, operatorSpec, err := getAPIServerAndOperatorSpecFn(ctx) if err != nil { return "", "", configv1.APIServerEncryption{}, err } From c53b8df8ca626ba831734e5b01e1f981b95320b7 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 10:29:16 +0200 Subject: [PATCH 08/13] encryption: add KeyComputer and computeKeySecret for read-only key computation Add a private computeKeySecret method on keyController that binds all stored functions and calls checkAndCreateKeys. sync now calls through this method. Add KeyComputer, a thin wrapper that accepts a keyController and exposes ComputeKey so future components can compute what key would be needed without creating it. Also fix double Fn suffix on struct field names introduced by the previous rename. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/key_computer.go | 25 ++++++++++++++++ .../encryption/controllers/key_controller.go | 30 +++++++++++++------ 2 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 pkg/operator/encryption/controllers/key_computer.go diff --git a/pkg/operator/encryption/controllers/key_computer.go b/pkg/operator/encryption/controllers/key_computer.go new file mode 100644 index 0000000000..d657675de0 --- /dev/null +++ b/pkg/operator/encryption/controllers/key_computer.go @@ -0,0 +1,25 @@ +package controllers + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + + "github.com/openshift/library-go/pkg/controller/factory" +) + +// KeyComputer uses a keyController to compute what key secret would be +// needed without actually creating it. +type KeyComputer struct { + controller *keyController +} + +func newKeyComputer(controller *keyController) *KeyComputer { + return &KeyComputer{controller: controller} +} + +// ComputeKey returns the key secret that would be created by the key +// controller, or nil if no new key is needed. +func (k *KeyComputer) ComputeKey(ctx context.Context, syncCtx factory.SyncContext) (*corev1.Secret, error) { + return k.controller.computeKeySecret(ctx, syncCtx) +} diff --git a/pkg/operator/encryption/controllers/key_controller.go b/pkg/operator/encryption/controllers/key_controller.go index 810d25aa53..23990e97a4 100644 --- a/pkg/operator/encryption/controllers/key_controller.go +++ b/pkg/operator/encryption/controllers/key_controller.go @@ -81,11 +81,11 @@ type keyController struct { unsupportedConfigPrefix []string - getAPIServerAndOperatorSpecFnFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) - deployedEncryptionConfigSecretFnFn func(context.Context) (*corev1.Secret, bool, error) + getAPIServerAndOperatorSpecFn func(context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) + deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error) listKeySecretsFn func(context.Context) ([]*corev1.Secret, error) - getKMSPluginSecretFnFn func(context.Context, string) (*corev1.Secret, error) - getKMSPluginConfigMapFnFn func(context.Context, string) (*corev1.ConfigMap, error) + getKMSPluginSecretFn func(context.Context, string) (*corev1.Secret, error) + getKMSPluginConfigMapFn func(context.Context, string) (*corev1.ConfigMap, error) } func NewKeyController( @@ -119,7 +119,7 @@ func NewKeyController( configMapClient: configMapClient, } - c.getAPIServerAndOperatorSpecFnFn = func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { + c.getAPIServerAndOperatorSpecFn = func(ctx context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { apiServer, err := c.apiServerClient.Get(ctx, "cluster", metav1.GetOptions{}) if err != nil { return nil, nil, err @@ -130,14 +130,14 @@ func NewKeyController( } return apiServer, operatorSpec, nil } - c.deployedEncryptionConfigSecretFnFn = c.deployer.DeployedEncryptionConfigSecret + c.deployedEncryptionConfigSecretFn = c.deployer.DeployedEncryptionConfigSecret c.listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) } - c.getKMSPluginSecretFnFn = func(ctx context.Context, name string) (*corev1.Secret, error) { + c.getKMSPluginSecretFn = func(ctx context.Context, name string) (*corev1.Secret, error) { return c.secretClient.Secrets(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) } - c.getKMSPluginConfigMapFnFn = func(ctx context.Context, name string) (*corev1.ConfigMap, error) { + c.getKMSPluginConfigMapFn = func(ctx context.Context, name string) (*corev1.ConfigMap, error) { return c.configMapClient.ConfigMaps(openshiftConfigNS).Get(ctx, name, metav1.GetOptions{}) } @@ -182,7 +182,7 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err // we will get re-kicked when the operator status updates } - keySecret, err := checkAndCreateKeys(ctx, syncCtx, c.provider.EncryptedGRs(), c.instanceName, c.unsupportedConfigPrefix, c.getAPIServerAndOperatorSpecFnFn, c.deployedEncryptionConfigSecretFnFn, c.listKeySecretsFn, c.getKMSPluginSecretFnFn, c.getKMSPluginConfigMapFnFn) + keySecret, err := c.computeKeySecret(ctx, syncCtx) if err == nil && keySecret != nil { keyID, _ := state.NameToKeyID(keySecret.Name) _, createErr := c.secretClient.Secrets("openshift-config-managed").Create(ctx, keySecret, metav1.CreateOptions{}) @@ -208,6 +208,18 @@ func (c *keyController) sync(ctx context.Context, syncCtx factory.SyncContext) ( return err } +func (c *keyController) computeKeySecret(ctx context.Context, syncContext factory.SyncContext) (*corev1.Secret, error) { + return checkAndCreateKeys( + ctx, syncContext, c.provider.EncryptedGRs(), + c.instanceName, c.unsupportedConfigPrefix, + c.getAPIServerAndOperatorSpecFn, + c.deployedEncryptionConfigSecretFn, + c.listKeySecretsFn, + c.getKMSPluginSecretFn, + c.getKMSPluginConfigMapFn, + ) +} + func checkAndCreateKeys( ctx context.Context, syncContext factory.SyncContext, From ba06492bc434df9de14c45405443f1e18e418f48 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 11:24:29 +0200 Subject: [PATCH 09/13] encryption: generateEncryptionConfigSecret returns secret instead of applying it Move resourceapply.ApplySecret out of generateEncryptionConfigSecret into sync, following the same pattern as checkAndCreateKeys. The function now returns the secret to apply and pre-computed pending events; sync applies the secret and emits events only if ApplySecret reports a change. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../controllers/state_controller.go | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/pkg/operator/encryption/controllers/state_controller.go b/pkg/operator/encryption/controllers/state_controller.go index 83c9790a07..9b73822208 100644 --- a/pkg/operator/encryption/controllers/state_controller.go +++ b/pkg/operator/encryption/controllers/state_controller.go @@ -110,7 +110,17 @@ func (c *stateController) sync(ctx context.Context, syncCtx factory.SyncContext) return err // we will get re-kicked when the operator status updates } - configError := c.generateAndApplyCurrentEncryptionConfigSecret(ctx, syncCtx.Queue(), syncCtx.Recorder(), c.provider.EncryptedGRs()) + secretToApply, pendingEvents, configError := c.generateEncryptionConfigSecret(ctx, syncCtx.Queue(), c.provider.EncryptedGRs()) + if configError == nil && secretToApply != nil { + _, changed, applyErr := resourceapply.ApplySecret(ctx, c.secretClient, syncCtx.Recorder(), secretToApply) + if applyErr != nil { + configError = applyErr + } else if changed { + for _, event := range pendingEvents { + syncCtx.Recorder().Eventf(event.reason, "%s", event.message) + } + } + } if configError != nil { degradedCondition = degradedCondition. WithStatus(operatorv1.ConditionTrue). @@ -128,7 +138,7 @@ type eventWithReason struct { message string } -func (c *stateController) generateAndApplyCurrentEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, recorder events.Recorder, encryptedGRs []schema.GroupResource) error { +func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource) (*corev1.Secret, []eventWithReason, error) { currentConfig, desiredEncryptionState, encryptionSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState( ctx, c.deployer.DeployedEncryptionConfigSecret, @@ -138,48 +148,41 @@ func (c *stateController) generateAndApplyCurrentEncryptionConfigSecret(ctx cont encryptedGRs, ) if err != nil { - return err + return nil, nil, err } if len(transitioningReason) > 0 { queue.AddAfter(stateWorkKey, 2*time.Minute) - return nil + return nil, nil, nil } if currentConfig == nil && len(encryptionSecrets) == 0 { // we depend on the key controller to create the first key to bootstrap encryption. // Later-on either the config exists or there are keys, even in the case of disabled // encryption via the apiserver config. - return nil + return nil, nil, nil } desiredSecretData, err := encryptiondata.FromEncryptionState(desiredEncryptionState) if err != nil { - return err + return nil, nil, err } - changed, err := c.applyEncryptionConfigSecret(ctx, desiredSecretData, recorder) + secretToApply, err := c.applyEncryptionConfigSecret(ctx, desiredSecretData) if err != nil { - return err + return nil, nil, err } - if changed { - currentEncryptionConfig, _ := encryptiondata.ToEncryptionState(currentConfig, encryptionSecrets) - if actionEvents := eventsFromEncryptionConfigChanges(currentEncryptionConfig, desiredEncryptionState); len(actionEvents) > 0 { - for _, event := range actionEvents { - recorder.Eventf(event.reason, "%s", event.message) - } - } - } - return nil + currentEncryptionConfig, _ := encryptiondata.ToEncryptionState(currentConfig, encryptionSecrets) + pendingEvents := eventsFromEncryptionConfigChanges(currentEncryptionConfig, desiredEncryptionState) + + return secretToApply, pendingEvents, nil } -func (c *stateController) applyEncryptionConfigSecret(ctx context.Context, secretData *encryptiondata.Config, recorder events.Recorder) (bool, error) { +func (c *stateController) applyEncryptionConfigSecret(ctx context.Context, secretData *encryptiondata.Config) (*corev1.Secret, error) { s, err := encryptiondata.ToSecret("openshift-config-managed", fmt.Sprintf("%s-%s", encryptiondata.EncryptionConfSecretName, c.instanceName), secretData) if err != nil { - return false, err + return nil, err } - - _, changed, applyErr := resourceapply.ApplySecret(ctx, c.secretClient, recorder, s) - return changed, applyErr + return s, nil } // eventsFromEncryptionConfigChanges return slice of event reasons with messages corresponding to a difference between current and desired encryption state. From 7d6e7b63d8f5aaee22d259ba790fae6e2333c44c Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 11:30:38 +0200 Subject: [PATCH 10/13] encryption: inject resource-fetching functions into generateEncryptionConfigSecret Store deployedEncryptionConfigSecretFn and listKeySecretsFn on the stateController struct, wire them in the constructor, and pass them as explicit parameters to generateEncryptionConfigSecret, following the same pattern as the key controller. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../encryption/controllers/state_controller.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/operator/encryption/controllers/state_controller.go b/pkg/operator/encryption/controllers/state_controller.go index 9b73822208..8ba4e29542 100644 --- a/pkg/operator/encryption/controllers/state_controller.go +++ b/pkg/operator/encryption/controllers/state_controller.go @@ -48,6 +48,9 @@ type stateController struct { deployer statemachine.Deployer provider Provider preconditionsFulfilledFn preconditionsFulfilled + + deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error) + listKeySecretsFn func(context.Context) ([]*corev1.Secret, error) } func NewStateController( @@ -74,6 +77,11 @@ func NewStateController( preconditionsFulfilledFn: preconditionsFulfilledFn, } + c.deployedEncryptionConfigSecretFn = c.deployer.DeployedEncryptionConfigSecret + c.listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { + return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) + } + return factory.New().ResyncEvery(time.Minute).WithSync(c.sync).WithControllerInstanceName(c.controllerInstanceName).WithInformers( operatorClient.Informer(), kubeInformersForNamespaces.InformersFor("openshift-config-managed").Core().V1().Secrets().Informer(), @@ -110,7 +118,7 @@ func (c *stateController) sync(ctx context.Context, syncCtx factory.SyncContext) return err // we will get re-kicked when the operator status updates } - secretToApply, pendingEvents, configError := c.generateEncryptionConfigSecret(ctx, syncCtx.Queue(), c.provider.EncryptedGRs()) + secretToApply, pendingEvents, configError := c.generateEncryptionConfigSecret(ctx, syncCtx.Queue(), c.provider.EncryptedGRs(), c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) if configError == nil && secretToApply != nil { _, changed, applyErr := resourceapply.ApplySecret(ctx, c.secretClient, syncCtx.Recorder(), secretToApply) if applyErr != nil { @@ -138,13 +146,11 @@ type eventWithReason struct { message string } -func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource) (*corev1.Secret, []eventWithReason, error) { +func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource, deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error), listKeySecretsFn func(context.Context) ([]*corev1.Secret, error)) (*corev1.Secret, []eventWithReason, error) { currentConfig, desiredEncryptionState, encryptionSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState( ctx, - c.deployer.DeployedEncryptionConfigSecret, - func(ctx context.Context) ([]*corev1.Secret, error) { - return secrets.ListKeySecrets(ctx, c.secretClient, c.encryptionSecretSelector) - }, + deployedEncryptionConfigSecretFn, + listKeySecretsFn, encryptedGRs, ) if err != nil { From e8a695de4b68e50bef793f054eb02a068105daeb Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 11:34:16 +0200 Subject: [PATCH 11/13] encryption: promote generateEncryptionConfigSecret to package-level function and add EncryptionComputer Inject instanceName as an explicit parameter into generateEncryptionConfigSecret and applyEncryptionConfigSecret, removing the last receiver accesses and promoting both to package-level functions. Add computeEncryptionConfigSecret as a private wrapper method on stateController (analogous to computeKeySecret on keyController). Add EncryptionComputer, a combined component that accepts both controllers and exposes read-only computation of both the key secret and the encryption config secret. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../controllers/encryption_computer.go | 36 +++++++++++++++++++ .../controllers/state_controller.go | 14 +++++--- 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 pkg/operator/encryption/controllers/encryption_computer.go diff --git a/pkg/operator/encryption/controllers/encryption_computer.go b/pkg/operator/encryption/controllers/encryption_computer.go new file mode 100644 index 0000000000..d1b3e6673e --- /dev/null +++ b/pkg/operator/encryption/controllers/encryption_computer.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/util/workqueue" + + "github.com/openshift/library-go/pkg/controller/factory" +) + +// EncryptionComputer accepts a keyController and a stateController and +// allows computing their outputs without side effects. +type EncryptionComputer struct { + keyController *keyController + stateController *stateController +} + +func NewEncryptionComputer(keyCtrl *keyController, stateCtrl *stateController) *EncryptionComputer { + return &EncryptionComputer{ + keyController: keyCtrl, + stateController: stateCtrl, + } +} + +// ComputeKeySecret returns the key secret that would be created by the +// key controller, or nil if no new key is needed. +func (e *EncryptionComputer) ComputeKeySecret(ctx context.Context, syncCtx factory.SyncContext) (*corev1.Secret, error) { + return e.keyController.computeKeySecret(ctx, syncCtx) +} + +// ComputeEncryptionConfigSecret returns the encryption config secret that +// would be applied by the state controller, or nil if no update is needed. +func (e *EncryptionComputer) ComputeEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface) (*corev1.Secret, []eventWithReason, error) { + return e.stateController.computeEncryptionConfigSecret(ctx, queue) +} diff --git a/pkg/operator/encryption/controllers/state_controller.go b/pkg/operator/encryption/controllers/state_controller.go index 8ba4e29542..3115bd75b3 100644 --- a/pkg/operator/encryption/controllers/state_controller.go +++ b/pkg/operator/encryption/controllers/state_controller.go @@ -118,7 +118,7 @@ func (c *stateController) sync(ctx context.Context, syncCtx factory.SyncContext) return err // we will get re-kicked when the operator status updates } - secretToApply, pendingEvents, configError := c.generateEncryptionConfigSecret(ctx, syncCtx.Queue(), c.provider.EncryptedGRs(), c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) + secretToApply, pendingEvents, configError := c.computeEncryptionConfigSecret(ctx, syncCtx.Queue()) if configError == nil && secretToApply != nil { _, changed, applyErr := resourceapply.ApplySecret(ctx, c.secretClient, syncCtx.Recorder(), secretToApply) if applyErr != nil { @@ -146,7 +146,11 @@ type eventWithReason struct { message string } -func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource, deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error), listKeySecretsFn func(context.Context) ([]*corev1.Secret, error)) (*corev1.Secret, []eventWithReason, error) { +func (c *stateController) computeEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface) (*corev1.Secret, []eventWithReason, error) { + return generateEncryptionConfigSecret(ctx, queue, c.provider.EncryptedGRs(), c.instanceName, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) +} + +func generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource, instanceName string, deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error), listKeySecretsFn func(context.Context) ([]*corev1.Secret, error)) (*corev1.Secret, []eventWithReason, error) { currentConfig, desiredEncryptionState, encryptionSecrets, transitioningReason, err := statemachine.GetEncryptionConfigAndState( ctx, deployedEncryptionConfigSecretFn, @@ -172,7 +176,7 @@ func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, qu if err != nil { return nil, nil, err } - secretToApply, err := c.applyEncryptionConfigSecret(ctx, desiredSecretData) + secretToApply, err := applyEncryptionConfigSecret(instanceName, desiredSecretData) if err != nil { return nil, nil, err } @@ -183,8 +187,8 @@ func (c *stateController) generateEncryptionConfigSecret(ctx context.Context, qu return secretToApply, pendingEvents, nil } -func (c *stateController) applyEncryptionConfigSecret(ctx context.Context, secretData *encryptiondata.Config) (*corev1.Secret, error) { - s, err := encryptiondata.ToSecret("openshift-config-managed", fmt.Sprintf("%s-%s", encryptiondata.EncryptionConfSecretName, c.instanceName), secretData) +func applyEncryptionConfigSecret(instanceName string, secretData *encryptiondata.Config) (*corev1.Secret, error) { + s, err := encryptiondata.ToSecret("openshift-config-managed", fmt.Sprintf("%s-%s", encryptiondata.EncryptionConfSecretName, instanceName), secretData) if err != nil { return nil, err } From 7b5b5d862ea165981e4b173a24b90c0930547a5f Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 11:42:33 +0200 Subject: [PATCH 12/13] encryption: add EncryptionComputer with key propagation to state controller Add computeEncryptionConfigSecretWithCustomListKeySecretFn on stateController to allow injecting a custom listKeySecretsFn into the encryption config computation. Add EncryptionComputer combining both controllers, exposing ComputeEncryptionConfigSecretWithNewKey which chains the key controller output into the state controller's computation by prepending the would-be key secret to the list of existing secrets. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../controllers/encryption_computer.go | 25 +++++++++++++++++++ .../controllers/state_controller.go | 6 ++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pkg/operator/encryption/controllers/encryption_computer.go b/pkg/operator/encryption/controllers/encryption_computer.go index d1b3e6673e..8dc38017a8 100644 --- a/pkg/operator/encryption/controllers/encryption_computer.go +++ b/pkg/operator/encryption/controllers/encryption_computer.go @@ -34,3 +34,28 @@ func (e *EncryptionComputer) ComputeKeySecret(ctx context.Context, syncCtx facto func (e *EncryptionComputer) ComputeEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface) (*corev1.Secret, []eventWithReason, error) { return e.stateController.computeEncryptionConfigSecret(ctx, queue) } + +// ComputeEncryptionConfigSecretWithNewKey computes the key secret that +// would be created by the key controller and propagates it into the state +// controller's computation, returning the encryption config secret that +// would result if the new key had been created. +func (e *EncryptionComputer) ComputeEncryptionConfigSecretWithNewKey(ctx context.Context, syncCtx factory.SyncContext) (*corev1.Secret, []eventWithReason, error) { + newKeySecret, err := e.keyController.computeKeySecret(ctx, syncCtx) + if err != nil { + return nil, nil, err + } + + sc := e.stateController + listKeySecretsFn := sc.listKeySecretsFn + if newKeySecret != nil { + listKeySecretsFn = func(ctx context.Context) ([]*corev1.Secret, error) { + existing, err := sc.listKeySecretsFn(ctx) + if err != nil { + return nil, err + } + return append([]*corev1.Secret{newKeySecret}, existing...), nil + } + } + + return sc.computeEncryptionConfigSecretWithCustomListKeySecretFn(ctx, syncCtx.Queue(), listKeySecretsFn) +} diff --git a/pkg/operator/encryption/controllers/state_controller.go b/pkg/operator/encryption/controllers/state_controller.go index 3115bd75b3..1acffcdf48 100644 --- a/pkg/operator/encryption/controllers/state_controller.go +++ b/pkg/operator/encryption/controllers/state_controller.go @@ -147,7 +147,11 @@ type eventWithReason struct { } func (c *stateController) computeEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface) (*corev1.Secret, []eventWithReason, error) { - return generateEncryptionConfigSecret(ctx, queue, c.provider.EncryptedGRs(), c.instanceName, c.deployedEncryptionConfigSecretFn, c.listKeySecretsFn) + return c.computeEncryptionConfigSecretWithCustomListKeySecretFn(ctx, queue, c.listKeySecretsFn) +} + +func (c *stateController) computeEncryptionConfigSecretWithCustomListKeySecretFn(ctx context.Context, queue workqueue.RateLimitingInterface, listKeySecretsFn func(context.Context) ([]*corev1.Secret, error)) (*corev1.Secret, []eventWithReason, error) { + return generateEncryptionConfigSecret(ctx, queue, c.provider.EncryptedGRs(), c.instanceName, c.deployedEncryptionConfigSecretFn, listKeySecretsFn) } func generateEncryptionConfigSecret(ctx context.Context, queue workqueue.RateLimitingInterface, encryptedGRs []schema.GroupResource, instanceName string, deployedEncryptionConfigSecretFn func(context.Context) (*corev1.Secret, bool, error), listKeySecretsFn func(context.Context) ([]*corev1.Secret, error)) (*corev1.Secret, []eventWithReason, error) { From 9e385c90caabe6515bb1020f36166146a1d8ccb9 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 4 Aug 2026 13:07:54 +0200 Subject: [PATCH 13/13] encryption: add tests for ComputeEncryptionConfigSecretWithNewKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two table-driven scenarios with full YAML representations of both expected outputs — the newly computed KMS key secret and the resulting encryption config secret: 1. Fresh KMS setup (no existing keys): new key gets ID 1, encryption config places it as a read key in the first transitional pass. 2. Migrating from AESCBC to KMS (one fully-migrated AESCBC key exists): new KMS key gets ID 8, encryption config carries both keys as read keys alongside an identity write key until convergence. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../controllers/encryption_computer_test.go | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 pkg/operator/encryption/controllers/encryption_computer_test.go diff --git a/pkg/operator/encryption/controllers/encryption_computer_test.go b/pkg/operator/encryption/controllers/encryption_computer_test.go new file mode 100644 index 0000000000..c60e89c046 --- /dev/null +++ b/pkg/operator/encryption/controllers/encryption_computer_test.go @@ -0,0 +1,245 @@ +package controllers + +import ( + "context" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/diff" + clocktesting "k8s.io/utils/clock/testing" + sigsyaml "sigs.k8s.io/yaml" + + configv1 "github.com/openshift/api/config/v1" + operatorv1 "github.com/openshift/api/operator/v1" + + "github.com/openshift/library-go/pkg/controller/factory" + encryptiontesting "github.com/openshift/library-go/pkg/operator/encryption/testing" + "github.com/openshift/library-go/pkg/operator/events" +) + +var ( + testSecretGR = schema.GroupResource{Resource: "secrets"} + testEncryptedGRs = []schema.GroupResource{testSecretGR} + aescbcRawKey = []byte("61def964fb967f5d7c44a2af8dab6865") + + // testKMSPluginConfig has no external secret or configmap references so the + // expected key secret can be fully expressed as a literal YAML string. + testKMSPluginConfig = configv1.KMSPluginConfig{ + Type: configv1.VaultKMSProvider, + Vault: configv1.VaultKMSPluginConfig{ + KMSPluginImage: "vault-kms-plugin:latest", + VaultAddress: "https://vault.example.com", + Authentication: configv1.VaultAuthentication{ + Type: configv1.VaultAuthenticationTypeAppRole, + AppRole: configv1.VaultAppRoleAuthentication{}, // no secret reference + }, + VaultKeyPath: "transit/keys/mykey", + }, + } +) + +func TestComputeEncryptionConfigSecretWithNewKey(t *testing.T) { + aescbcKey7Secret := encryptiontesting.CreateExpiredMigratedEncryptionKeySecretWithRawKey( + "openshift-config-managed", testEncryptedGRs, 7, aescbcRawKey, + ) + + scenarios := []struct { + name string + existingKeySecrets []*corev1.Secret // inputs: key secrets already in the cluster + + wantNewKeySecret string // expected: YAML of the newly computed key secret + wantEncConfigSecret string // expected: YAML of the computed encryption config secret + }{ + { + name: "fresh KMS setup: no existing keys", + existingKeySecrets: nil, + + wantNewKeySecret: ` +data: + encryption.apiserver.operator.openshift.io-key: AAAAAAAAAAAAAAAAAAAAAA== + encryption.apiserver.operator.openshift.io-kms-encryption-config: eyJraW5kIjoiRW5jcnlwdGlvbkNvbmZpZ3VyYXRpb24iLCJhcGlWZXJzaW9uIjoiYXBpc2VydmVyLmNvbmZpZy5rOHMuaW8vdjEiLCJyZXNvdXJjZXMiOlt7InJlc291cmNlcyI6bnVsbCwicHJvdmlkZXJzIjpbeyJrbXMiOnsiYXBpVmVyc2lvbiI6InYyIiwibmFtZSI6IjEiLCJlbmRwb2ludCI6InVuaXg6Ly8vdmFyL3J1bi9rbXNwbHVnaW4va21zLTEuc29jayIsInRpbWVvdXQiOiIxMHMifX1dfV19Cg== + encryption.apiserver.operator.openshift.io-kms-plugin-config: eyJraW5kIjoiQVBJU2VydmVyIiwiYXBpVmVyc2lvbiI6ImNvbmZpZy5vcGVuc2hpZnQuaW8vdjEiLCJtZXRhZGF0YSI6e30sInNwZWMiOnsic2VydmluZ0NlcnRzIjp7fSwiY2xpZW50Q0EiOnsibmFtZSI6IiJ9LCJlbmNyeXB0aW9uIjp7ImttcyI6eyJ0eXBlIjoiVmF1bHQiLCJ2YXVsdCI6eyJrbXNQbHVnaW5JbWFnZSI6InZhdWx0LWttcy1wbHVnaW46bGF0ZXN0IiwidmF1bHRBZGRyZXNzIjoiaHR0cHM6Ly92YXVsdC5leGFtcGxlLmNvbSIsImF1dGhlbnRpY2F0aW9uIjp7InR5cGUiOiJBcHBSb2xlIn0sInZhdWx0S2V5UGF0aCI6InRyYW5zaXQva2V5cy9teWtleSJ9fX0sImF1ZGl0Ijp7fX0sInN0YXR1cyI6e319Cg== +metadata: + annotations: + encryption.apiserver.operator.openshift.io/external-reason: "" + encryption.apiserver.operator.openshift.io/internal-reason: secrets-key-does-not-exist + encryption.apiserver.operator.openshift.io/mode: KMS + kubernetes.io/description: |- + WARNING: DO NOT EDIT. + Altering of the encryption secrets will render you cluster inaccessible. + Catastrophic data loss can occur from the most minor changes. + finalizers: + - encryption.apiserver.operator.openshift.io/deletion-protection + labels: + encryption.apiserver.operator.openshift.io/component: test-component + name: encryption-key-test-component-1 + namespace: openshift-config-managed +type: Opaque +`, + + // State machine places the new KMS key as a read key on the first + // pass; write key promotion happens after the config converges. + wantEncConfigSecret: ` +apiVersion: v1 +data: + encryption-config: eyJraW5kIjoiRW5jcnlwdGlvbkNvbmZpZ3VyYXRpb24iLCJhcGlWZXJzaW9uIjoiYXBpc2VydmVyLmNvbmZpZy5rOHMuaW8vdjEiLCJyZXNvdXJjZXMiOlt7InJlc291cmNlcyI6WyJzZWNyZXRzIl0sInByb3ZpZGVycyI6W3siaWRlbnRpdHkiOnt9fSx7ImttcyI6eyJhcGlWZXJzaW9uIjoidjIiLCJuYW1lIjoiMV9zZWNyZXRzIiwiZW5kcG9pbnQiOiJ1bml4Oi8vL3Zhci9ydW4va21zcGx1Z2luL2ttcy0xLnNvY2siLCJ0aW1lb3V0IjoiMTBzIn19XX1dfQo= + kms-plugin-config-1: eyJraW5kIjoiQVBJU2VydmVyIiwiYXBpVmVyc2lvbiI6ImNvbmZpZy5vcGVuc2hpZnQuaW8vdjEiLCJtZXRhZGF0YSI6e30sInNwZWMiOnsic2VydmluZ0NlcnRzIjp7fSwiY2xpZW50Q0EiOnsibmFtZSI6IiJ9LCJlbmNyeXB0aW9uIjp7ImttcyI6eyJ0eXBlIjoiVmF1bHQiLCJ2YXVsdCI6eyJrbXNQbHVnaW5JbWFnZSI6InZhdWx0LWttcy1wbHVnaW46bGF0ZXN0IiwidmF1bHRBZGRyZXNzIjoiaHR0cHM6Ly92YXVsdC5leGFtcGxlLmNvbSIsImF1dGhlbnRpY2F0aW9uIjp7InR5cGUiOiJBcHBSb2xlIn0sInZhdWx0S2V5UGF0aCI6InRyYW5zaXQva2V5cy9teWtleSJ9fX0sImF1ZGl0Ijp7fX0sInN0YXR1cyI6e319Cg== +kind: Secret +metadata: + annotations: + kubernetes.io/description: |- + WARNING: DO NOT EDIT. + Altering of the encryption secrets will render you cluster inaccessible. + Catastrophic data loss can occur from the most minor changes. + finalizers: + - encryption.apiserver.operator.openshift.io/deletion-protection + name: encryption-config-test-component + namespace: openshift-config-managed +type: Opaque +`, + }, + { + name: "migrating from AESCBC to KMS: one fully-migrated AESCBC key exists", + existingKeySecrets: []*corev1.Secret{aescbcKey7Secret}, + + wantNewKeySecret: ` +data: + encryption.apiserver.operator.openshift.io-key: AAAAAAAAAAAAAAAAAAAAAA== + encryption.apiserver.operator.openshift.io-kms-encryption-config: eyJraW5kIjoiRW5jcnlwdGlvbkNvbmZpZ3VyYXRpb24iLCJhcGlWZXJzaW9uIjoiYXBpc2VydmVyLmNvbmZpZy5rOHMuaW8vdjEiLCJyZXNvdXJjZXMiOlt7InJlc291cmNlcyI6bnVsbCwicHJvdmlkZXJzIjpbeyJrbXMiOnsiYXBpVmVyc2lvbiI6InYyIiwibmFtZSI6IjgiLCJlbmRwb2ludCI6InVuaXg6Ly8vdmFyL3J1bi9rbXNwbHVnaW4va21zLTguc29jayIsInRpbWVvdXQiOiIxMHMifX1dfV19Cg== + encryption.apiserver.operator.openshift.io-kms-plugin-config: eyJraW5kIjoiQVBJU2VydmVyIiwiYXBpVmVyc2lvbiI6ImNvbmZpZy5vcGVuc2hpZnQuaW8vdjEiLCJtZXRhZGF0YSI6e30sInNwZWMiOnsic2VydmluZ0NlcnRzIjp7fSwiY2xpZW50Q0EiOnsibmFtZSI6IiJ9LCJlbmNyeXB0aW9uIjp7ImttcyI6eyJ0eXBlIjoiVmF1bHQiLCJ2YXVsdCI6eyJrbXNQbHVnaW5JbWFnZSI6InZhdWx0LWttcy1wbHVnaW46bGF0ZXN0IiwidmF1bHRBZGRyZXNzIjoiaHR0cHM6Ly92YXVsdC5leGFtcGxlLmNvbSIsImF1dGhlbnRpY2F0aW9uIjp7InR5cGUiOiJBcHBSb2xlIn0sInZhdWx0S2V5UGF0aCI6InRyYW5zaXQva2V5cy9teWtleSJ9fX0sImF1ZGl0Ijp7fX0sInN0YXR1cyI6e319Cg== +metadata: + annotations: + encryption.apiserver.operator.openshift.io/external-reason: "" + encryption.apiserver.operator.openshift.io/internal-reason: secrets-encryption-mode-changed + encryption.apiserver.operator.openshift.io/mode: KMS + kubernetes.io/description: |- + WARNING: DO NOT EDIT. + Altering of the encryption secrets will render you cluster inaccessible. + Catastrophic data loss can occur from the most minor changes. + finalizers: + - encryption.apiserver.operator.openshift.io/deletion-protection + labels: + encryption.apiserver.operator.openshift.io/component: test-component + name: encryption-key-test-component-8 + namespace: openshift-config-managed +type: Opaque +`, + + // Both the new KMS key (8) and the existing AESCBC key (7) appear + // as read keys; write key promotion happens after convergence. + wantEncConfigSecret: ` +apiVersion: v1 +data: + encryption-config: eyJraW5kIjoiRW5jcnlwdGlvbkNvbmZpZ3VyYXRpb24iLCJhcGlWZXJzaW9uIjoiYXBpc2VydmVyLmNvbmZpZy5rOHMuaW8vdjEiLCJyZXNvdXJjZXMiOlt7InJlc291cmNlcyI6WyJzZWNyZXRzIl0sInByb3ZpZGVycyI6W3siaWRlbnRpdHkiOnt9fSx7ImttcyI6eyJhcGlWZXJzaW9uIjoidjIiLCJuYW1lIjoiOF9zZWNyZXRzIiwiZW5kcG9pbnQiOiJ1bml4Oi8vL3Zhci9ydW4va21zcGx1Z2luL2ttcy04LnNvY2siLCJ0aW1lb3V0IjoiMTBzIn19LHsiYWVzY2JjIjp7ImtleXMiOlt7Im5hbWUiOiI3Iiwic2VjcmV0IjoiTmpGa1pXWTVOalJtWWprMk4yWTFaRGRqTkRSaE1tRm1PR1JoWWpZNE5qVT0ifV19fV19XX0K + kms-plugin-config-8: eyJraW5kIjoiQVBJU2VydmVyIiwiYXBpVmVyc2lvbiI6ImNvbmZpZy5vcGVuc2hpZnQuaW8vdjEiLCJtZXRhZGF0YSI6e30sInNwZWMiOnsic2VydmluZ0NlcnRzIjp7fSwiY2xpZW50Q0EiOnsibmFtZSI6IiJ9LCJlbmNyeXB0aW9uIjp7ImttcyI6eyJ0eXBlIjoiVmF1bHQiLCJ2YXVsdCI6eyJrbXNQbHVnaW5JbWFnZSI6InZhdWx0LWttcy1wbHVnaW46bGF0ZXN0IiwidmF1bHRBZGRyZXNzIjoiaHR0cHM6Ly92YXVsdC5leGFtcGxlLmNvbSIsImF1dGhlbnRpY2F0aW9uIjp7InR5cGUiOiJBcHBSb2xlIn0sInZhdWx0S2V5UGF0aCI6InRyYW5zaXQva2V5cy9teWtleSJ9fX0sImF1ZGl0Ijp7fX0sInN0YXR1cyI6e319Cg== +kind: Secret +metadata: + annotations: + kubernetes.io/description: |- + WARNING: DO NOT EDIT. + Altering of the encryption secrets will render you cluster inaccessible. + Catastrophic data loss can occur from the most minor changes. + finalizers: + - encryption.apiserver.operator.openshift.io/deletion-protection + name: encryption-config-test-component + namespace: openshift-config-managed +type: Opaque +`, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + computer := newTestEncryptionComputer(scenario.existingKeySecrets) + syncCtx := newTestSyncContext() + + gotNewKeySecret, err := computer.ComputeKeySecret(context.Background(), syncCtx) + if err != nil { + t.Fatalf("ComputeKeySecret: %v", err) + } + if !equality.Semantic.DeepEqual(gotNewKeySecret, mustParseSecret(t, scenario.wantNewKeySecret)) { + t.Errorf("new key secret mismatch:\n%s", diff.Diff(mustParseSecret(t, scenario.wantNewKeySecret), gotNewKeySecret)) + } + + gotEncConfigSecret, _, err := computer.ComputeEncryptionConfigSecretWithNewKey(context.Background(), syncCtx) + if err != nil { + t.Fatalf("ComputeEncryptionConfigSecretWithNewKey: %v", err) + } + if !equality.Semantic.DeepEqual(gotEncConfigSecret, mustParseSecret(t, scenario.wantEncConfigSecret)) { + t.Errorf("encryption config secret mismatch:\n%s", diff.Diff(mustParseSecret(t, scenario.wantEncConfigSecret), gotEncConfigSecret)) + } + }) + } +} + +func mustParseSecret(t *testing.T, yamlStr string) *corev1.Secret { + t.Helper() + s := &corev1.Secret{} + if err := sigsyaml.Unmarshal([]byte(yamlStr), s); err != nil { + t.Fatalf("mustParseSecret: %v", err) + } + return s +} + +func newTestEncryptionComputer(existingKeySecrets []*corev1.Secret) *EncryptionComputer { + instanceName := "test-component" + provider := &fakeProvider{encryptedGRs: testEncryptedGRs} + + noDeployedConfig := func(_ context.Context) (*corev1.Secret, bool, error) { + return nil, true, nil + } + listExistingKeys := func(_ context.Context) ([]*corev1.Secret, error) { + return existingKeySecrets, nil + } + + keyCtrl := &keyController{ + instanceName: instanceName, + provider: provider, + getAPIServerAndOperatorSpecFn: func(_ context.Context) (*configv1.APIServer, *operatorv1.OperatorSpec, error) { + return &configv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Spec: configv1.APIServerSpec{ + Encryption: configv1.APIServerEncryption{ + Type: "KMS", + KMS: testKMSPluginConfig, + }, + }, + }, &operatorv1.OperatorSpec{}, nil + }, + deployedEncryptionConfigSecretFn: noDeployedConfig, + listKeySecretsFn: listExistingKeys, + getKMSPluginSecretFn: func(_ context.Context, _ string) (*corev1.Secret, error) { + return nil, nil // not called: testKMSPluginConfig has no secret reference + }, + getKMSPluginConfigMapFn: func(_ context.Context, _ string) (*corev1.ConfigMap, error) { + return nil, nil // not called: testKMSPluginConfig has no configmap reference + }, + } + + stateCtrl := &stateController{ + instanceName: instanceName, + provider: provider, + deployedEncryptionConfigSecretFn: noDeployedConfig, + listKeySecretsFn: listExistingKeys, + } + + return NewEncryptionComputer(keyCtrl, stateCtrl) +} + +func newTestSyncContext() factory.SyncContext { + recorder := events.NewRecorder(nil, "test", &corev1.ObjectReference{}, clocktesting.NewFakePassiveClock(time.Now())) + return factory.NewSyncContext("test", recorder) +} + +var _ Provider = &fakeProvider{} + +type fakeProvider struct { + encryptedGRs []schema.GroupResource +} + +func (f *fakeProvider) EncryptedGRs() []schema.GroupResource { return f.encryptedGRs } +func (f *fakeProvider) ShouldRunEncryptionControllers() (bool, error) { return true, nil }