diff --git a/go.mod b/go.mod index 9b2595cd5f..a4ddf0f4c6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/openshift/api v0.0.0-20260304172252-b0658d22beea github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee github.com/openshift/client-go v0.0.0-20251205093018-96a6cbc1420c - github.com/openshift/library-go v0.0.0-20260306144728-e1d99440aba6 + github.com/openshift/library-go v0.0.0-20260309173530-9ed71ac3148f github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 diff --git a/go.sum b/go.sum index 53b6a93ae7..9278abed09 100644 --- a/go.sum +++ b/go.sum @@ -153,8 +153,8 @@ github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+S github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= github.com/openshift/client-go v0.0.0-20251205093018-96a6cbc1420c h1:TBE0Gl+oCo/SNEhLKZQNNH/SWHXrpGyhAw7P0lAqdHg= github.com/openshift/client-go v0.0.0-20251205093018-96a6cbc1420c/go.mod h1:IsynOWZAfdH+BgWimcFQRtI41Id9sgdhsCEjIk8ACLw= -github.com/openshift/library-go v0.0.0-20260306144728-e1d99440aba6 h1:SsXtB95ZuyDKpO2zkXsFJenrDytSqSM342PZ33wEJa0= -github.com/openshift/library-go v0.0.0-20260306144728-e1d99440aba6/go.mod h1:rYGQrSg+t1JEzeEwg6BJw3loPpXg/n3kgRygUpgxavY= +github.com/openshift/library-go v0.0.0-20260309173530-9ed71ac3148f h1:4nMvQlqotxtqJo4noee7Jr9AKpJZEG7fu26IJFKX5/c= +github.com/openshift/library-go v0.0.0-20260309173530-9ed71ac3148f/go.mod h1:rYGQrSg+t1JEzeEwg6BJw3loPpXg/n3kgRygUpgxavY= github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d h1:Rzx23P63JFNNz5D23ubhC0FCN5rK8CeJhKcq5QKcdyU= github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d/go.mod h1:iVi9Bopa5cLhjG5ie9DoZVVqkH8BGb1FQVTtecOLn4I= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/controllers/key_controller.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/controllers/key_controller.go index c999f140f0..79863d50e6 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/controllers/key_controller.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/controllers/key_controller.go @@ -37,7 +37,11 @@ import ( // encryptionSecretMigrationInterval determines how much time must pass after a key has been observed as // migrated before a new key is created by the key minting controller. The new key's ID will be one // greater than the last key's ID (the first key has a key ID of 1). -const encryptionSecretMigrationInterval = time.Hour * 24 * 7 // one week +const ( + encryptionSecretMigrationInterval = time.Hour * 24 * 7 // one week + defaultKMSEndpoint = "unix:///var/run/kmsplugin/kms.sock" + defaultKMSTimeout = 10 * time.Second +) // keyController creates new keys if necessary. It // * watches @@ -266,6 +270,14 @@ func (c *keyController) generateKeySecret(keyID uint64, currentMode state.Mode, InternalReason: internalReason, ExternalReason: externalReason, } + if currentMode == state.KMS { + ks.KMSConfiguration = &apiserverv1.KMSConfiguration{ + APIVersion: "v2", + Name: fmt.Sprintf("%d", keyID), + Endpoint: defaultKMSEndpoint, + Timeout: &metav1.Duration{Duration: defaultKMSTimeout}, + } + } return secrets.FromKeyState(c.instanceName, ks) } @@ -287,7 +299,7 @@ func (c *keyController) getCurrentModeAndExternalReason(ctx context.Context) (st reason := encryptionConfig.Encryption.Reason switch currentMode := state.Mode(apiServer.Spec.Encryption.Type); currentMode { - case state.AESCBC, state.AESGCM, state.Identity: // secretbox is disabled for now + case state.AESCBC, state.AESGCM, state.KMS, state.Identity: // secretbox is disabled for now return currentMode, reason, nil case "": // unspecified means use the default (which can change over time) return state.DefaultMode, reason, nil @@ -341,6 +353,19 @@ func needsNewKey(grKeys state.GroupResourceState, currentMode state.Mode, extern return 0, "", false } + if currentMode == state.KMS { + // We are here because Encryption Mode is not changed + + // For now in Tech Preview v1, we don't support configurational changes. Therefore, + // it is pointless comparing the secrets. + + // For KMS mode, we don't do time-based rotation. Therefore, we shortcut here + // KMS keys are rotated externally by the KMS system. + // Moreover, we don't trigger new key when external reason is changed. + // Because it would lead to duplicate providers which is not allowed. + return 0, "", false + } + // if the most recent secret has a different external reason than the current reason, we need to generate a new key if latestKey.ExternalReason != externalReason && len(externalReason) != 0 { return latestKeyID, "external-reason-changed", true diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/crypto/keys.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/crypto/keys.go index a623d30f79..806f39f533 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/crypto/keys.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/crypto/keys.go @@ -11,7 +11,8 @@ var ( state.AESCBC: NewAES256Key, state.AESGCM: NewAES256Key, state.SecretBox: NewAES256Key, // secretbox requires a 32 byte key so we can reuse the same function here - state.Identity: NewIdentityKey, + state.Identity: NewEmptyKey, + state.KMS: NewEmptyKey, } ) @@ -23,6 +24,6 @@ func NewAES256Key() []byte { return b } -func NewIdentityKey() []byte { +func NewEmptyKey() []byte { return make([]byte, 16) // the key is not used to perform encryption but must be a valid AES key } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptionconfig/config.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptionconfig/config.go index 3082aa653f..ccface2ef0 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptionconfig/config.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptionconfig/config.go @@ -2,7 +2,9 @@ package encryptionconfig import ( "encoding/base64" + "fmt" "sort" + "strings" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -15,7 +17,7 @@ import ( ) var ( - emptyStaticIdentityKey = base64.StdEncoding.EncodeToString(crypto.NewIdentityKey()) + emptyStaticKey = base64.StdEncoding.EncodeToString(crypto.NewEmptyKey()) ) // FromEncryptionState converts state to config. @@ -25,7 +27,7 @@ func FromEncryptionState(encryptionState map[schema.GroupResource]state.GroupRes for gr, grKeys := range encryptionState { resourceConfigs = append(resourceConfigs, apiserverconfigv1.ResourceConfiguration{ Resources: []string{gr.String()}, // we are forced to lose data here because this API is broken - Providers: stateToProviders(grKeys), + Providers: stateToProviders(gr.Resource, grKeys), }) } @@ -97,7 +99,7 @@ func ToEncryptionState(encryptionConfig *apiserverconfigv1.EncryptionConfigurati case provider.AESGCM != nil && len(provider.AESGCM.Keys) == 1: s := state.AESGCM - if provider.AESGCM.Keys[0].Secret == emptyStaticIdentityKey { + if provider.AESGCM.Keys[0].Secret == emptyStaticKey { s = state.Identity } @@ -106,6 +108,20 @@ func ToEncryptionState(encryptionConfig *apiserverconfigv1.EncryptionConfigurati Mode: s, } + case provider.KMS != nil: + // Name and Secret must match to find our backed Secret + keyID, err := getKeyIDFromProviderName(provider.KMS.Name) + if err != nil { + klog.Warningf("resource: %s provider index: %d: Skipping invalid provider name %s: %v", resourceConfig.Resources[0], i, provider.KMS.Name, err) + continue // should never happen + } + ks = state.KeyState{ + Key: apiserverconfigv1.Key{ + Name: keyID, + Secret: emptyStaticKey, + }, + Mode: state.KMS, + } default: klog.Infof("skipping invalid provider index %d for resource %s", i, resourceConfig.Resources[0]) continue // should never happen @@ -139,7 +155,7 @@ func ToEncryptionState(encryptionConfig *apiserverconfigv1.EncryptionConfigurati // it primarily handles the conversion of KeyState to the appropriate provider config. // the identity mode is transformed into a custom aesgcm provider that simply exists to // curry the associated null key secret through the encryption state machine. -func stateToProviders(desired state.GroupResourceState) []apiserverconfigv1.ProviderConfiguration { +func stateToProviders(resource string, desired state.GroupResourceState) []apiserverconfigv1.ProviderConfiguration { allKeys := desired.ReadKeys providers := make([]apiserverconfigv1.ProviderConfiguration, 0, len(allKeys)+1) // one extra for identity @@ -192,6 +208,18 @@ func stateToProviders(desired state.GroupResourceState) []apiserverconfigv1.Prov Keys: []apiserverconfigv1.Key{key.Key}, }, }) + case state.KMS: + if key.KMSConfiguration == nil { + klog.Infof("skipping key %s for %s in KMS mode as its KMSConfiguration is nil", key.Key.Name, resource) + continue // this should never happen + } + // In order to preserve the uniqueness, we should insert resource name + kmsCopy := key.KMSConfiguration.DeepCopy() + kmsCopy.Name = createKMSProviderName(key.Key.Name, resource) + provider := apiserverconfigv1.ProviderConfiguration{ + KMS: kmsCopy, + } + providers = append(providers, provider) default: // this should never happen because our input should always be valid klog.Infof("skipping key %s as it has invalid mode %s", key.Key.Name, key.Mode) @@ -210,3 +238,20 @@ func stateToProviders(desired state.GroupResourceState) []apiserverconfigv1.Prov return providers } + +func createKMSProviderName(keyID, resource string) string { + // Ideally we should have used keyID simply in kms provider name. + // However, this is an upstream constraint that every provider name must be unique. + // To maintain uniqueness while still allowing access to the keyID, we generate provider name in this format. + return fmt.Sprintf("%s_%s", keyID, resource) +} + +func getKeyIDFromProviderName(providerName string) (string, error) { + // We just need to obtain the keyID to find our backed secret + // e.g. "1_secrets" + parsed := strings.SplitN(providerName, "_", 2) + if len(parsed) != 2 { + return "", fmt.Errorf("invalid provider name %q: expected format keyID_resourceName", providerName) + } + return parsed[0], nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/secrets.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/secrets.go index 4e54317c7d..54aec75f2e 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/secrets.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/secrets.go @@ -58,10 +58,23 @@ func ToKeyState(s *corev1.Secret) (state.KeyState, error) { key.ExternalReason = v } + if v, ok := s.Annotations[EncryptionSecretKMSConfig]; ok && len(v) > 0 { + kmsConfiguration := &apiserverconfigv1.KMSConfiguration{} + if err := json.Unmarshal([]byte(v), kmsConfiguration); err != nil { + return state.KeyState{}, fmt.Errorf("secret %s/%s has invalid %s annotation: %v", s.Namespace, s.Name, EncryptionSecretKMSConfig, err) + } + key.KMSConfiguration = kmsConfiguration + } + keyMode := state.Mode(s.Annotations[encryptionSecretMode]) switch keyMode { case state.AESCBC, state.AESGCM, state.SecretBox, state.Identity: key.Mode = keyMode + case state.KMS: + if key.KMSConfiguration == nil { + return state.KeyState{}, fmt.Errorf("KMSConfiguration can not be nil, when mode is KMS") + } + key.Mode = keyMode default: return state.KeyState{}, fmt.Errorf("secret %s/%s has invalid mode: %s", s.Namespace, s.Name, keyMode) } @@ -113,6 +126,14 @@ func FromKeyState(component string, ks state.KeyState) (*corev1.Secret, error) { s.Annotations[EncryptionSecretMigratedResources] = string(bs) } + if ks.KMSConfiguration != nil { + ksJSON, err := json.Marshal(ks.KMSConfiguration) + if err != nil { + return nil, err + } + s.Annotations[EncryptionSecretKMSConfig] = string(ksJSON) + } + return s, nil } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/types.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/types.go index 7161e4a124..443c7975e1 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/types.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/secrets/types.go @@ -49,6 +49,9 @@ const ( // by the encryption controllers. Its sole purpose is to prevent the accidental // deletion of secrets by enforcing a two phase delete. EncryptionSecretFinalizer = "encryption.apiserver.operator.openshift.io/deletion-protection" + + // EncryptionSecretKMSConfig is the annotation that stores the encoded KMS configuration. + EncryptionSecretKMSConfig = "encryption.apiserver.operator.openshift.io/kms-config" ) // MigratedGroupResources is the data structured stored in the diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/state/types.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/state/types.go index 460c21bfa2..61313d745e 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/state/types.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/state/types.go @@ -40,6 +40,8 @@ type KeyState struct { InternalReason string // the user via unsupportConfigOverrides.encryption.reason triggered this key. ExternalReason string + // Encoded KMSConfiguration that stores the KMS related fields + KMSConfiguration *apiserverconfigv1.KMSConfiguration } type MigrationState struct { @@ -60,6 +62,7 @@ const ( AESGCM Mode = "aesgcm" SecretBox Mode = "secretbox" // available from the first release, see defaultMode below Identity Mode = "identity" // available from the first release, see defaultMode below + KMS Mode = "KMS" // only supports KMS v2 // Changing this value requires caution to not break downgrades. // Specifically, if some new Mode is released in version X, that new Mode cannot diff --git a/vendor/modules.txt b/vendor/modules.txt index 4cc1e6b832..729089f291 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -319,7 +319,7 @@ github.com/openshift/client-go/user/applyconfigurations/internal github.com/openshift/client-go/user/applyconfigurations/user/v1 github.com/openshift/client-go/user/clientset/versioned/scheme github.com/openshift/client-go/user/clientset/versioned/typed/user/v1 -# github.com/openshift/library-go v0.0.0-20260306144728-e1d99440aba6 +# github.com/openshift/library-go v0.0.0-20260309173530-9ed71ac3148f ## explicit; go 1.24.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/apps/deployment