diff --git a/go.mod b/go.mod index b4c0969f82..e5e9fc951d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/openshift/api v0.0.0-20260521125114-09730f85d883 github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a - github.com/openshift/library-go v0.0.0-20260527152424-3ad832f9a5a5 + github.com/openshift/library-go v0.0.0-20260529072806-a742402e251f github.com/pkg/profile v1.7.0 // indirect github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.0 @@ -136,3 +136,5 @@ require ( ) replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 + +replace github.com/openshift/library-go => github.com/gangwgr/library-go v0.0.0-20260529072806-a742402e251f diff --git a/go.sum b/go.sum index b80219f5eb..55d38bc007 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfT github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bertinatto/library-go v0.0.0-20260528214545-b6069bef15aa h1:S9NOrmGI3O9XyZC3mVseB6JIT1f1ENMVRR02WfGB7ac= +github.com/bertinatto/library-go v0.0.0-20260528214545-b6069bef15aa/go.mod h1:/HBhy6jm/igWI3Y1vYFwFG3ZCcXmnNsKUT6VBpPyM9A= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -50,6 +52,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gangwgr/library-go v0.0.0-20260529072806-a742402e251f h1:hBZcBJzuvfN9Tovg0hADuajaPDD7KZHdrT3RrB/vEi0= +github.com/gangwgr/library-go v0.0.0-20260529072806-a742402e251f/go.mod h1:/HBhy6jm/igWI3Y1vYFwFG3ZCcXmnNsKUT6VBpPyM9A= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -165,8 +169,6 @@ 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-20260512113608-deb4dc54551a h1:EKx2XhOKehd1C5ptY7IrLl4WV35E8kP0pRPnG5BUZXk= github.com/openshift/client-go v0.0.0-20260512113608-deb4dc54551a/go.mod h1:V933kvY/cb/Un7UCEOhXHUySNX327u7Epe8g9KNqg2Q= -github.com/openshift/library-go v0.0.0-20260527152424-3ad832f9a5a5 h1:IPkGTFwKR7Y6/6NNsp681u8Qi/zZGJdY2dySYVTuyyc= -github.com/openshift/library-go v0.0.0-20260527152424-3ad832f9a5a5/go.mod h1:/HBhy6jm/igWI3Y1vYFwFG3ZCcXmnNsKUT6VBpPyM9A= github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 h1:PMTgifBcBRLJJiM+LgSzPDTk9/Rx4qS09OUrfpY6GBQ= github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= diff --git a/pkg/operator/targetconfigcontroller/targetconfigcontroller.go b/pkg/operator/targetconfigcontroller/targetconfigcontroller.go index 615db4e28a..ee50805e2a 100644 --- a/pkg/operator/targetconfigcontroller/targetconfigcontroller.go +++ b/pkg/operator/targetconfigcontroller/targetconfigcontroller.go @@ -330,7 +330,7 @@ func managePods(ctx context.Context, client coreclientv1.ConfigMapsGetter, secre required.Spec.Containers[i].Env = append(container.Env, proxyEnvVars...) } - if err := kmspluginlifecycle.AddKMSPluginSidecarToPodSpec(ctx, &required.Spec, "kube-apiserver", operatorclient.TargetNamespace, "encryption-config", secretClient, featureGateAccessor); err != nil { + if err := kmspluginlifecycle.AddKMSPluginSidecarToStaticPodSpec(ctx, &required.Spec, "kube-apiserver", operatorclient.TargetNamespace, "encryption-config", "/etc/kubernetes/static-pod-resources", secretClient, featureGateAccessor); err != nil { return nil, false, fmt.Errorf("failed to add KMS plugin to pod spec: %w", err) } diff --git a/test/e2e-encryption-kms/encryption_kms.go b/test/e2e-encryption-kms/encryption_kms.go index cdb4e0345b..af467c11a5 100644 --- a/test/e2e-encryption-kms/encryption_kms.go +++ b/test/e2e-encryption-kms/encryption_kms.go @@ -52,7 +52,7 @@ func testKMSEncryptionOnOff(ctx context.Context, t testing.TB) { AssertResourceNotEncryptedFunc: operatorencryption.AssertSecretOfLifeNotEncrypted, ResourceFunc: operatorencryption.SecretOfLife, ResourceName: "SecretOfLife", - EncryptionProvider: librarykms.DefaultFakeVaultEncryptionProvider, + EncryptionProvider: librarykms.DefaultVaultEncryptionProvider, }) } @@ -81,7 +81,7 @@ func testKMSEncryptionProvidersMigration(ctx context.Context, t testing.TB) { ResourceFunc: operatorencryption.SecretOfLife, ResourceName: "SecretOfLife", EncryptionProviders: library.ShuffleEncryptionProviders([]library.EncryptionProvider{ - librarykms.DefaultFakeVaultEncryptionProvider, + librarykms.DefaultVaultEncryptionProvider, library.SupportedStaticEncryptionProviders[rand.IntN(len(library.SupportedStaticEncryptionProviders))], }), }) diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go index b76c494402..d1c0a59ee8 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go @@ -327,10 +327,10 @@ func (r *ClientRotation) CertificateType() pki.CertificateType { } func (r *ClientRotation) NewCertificate(signer *crypto.CA, validity time.Duration, keyGen crypto.KeyPairGenerator) (*crypto.TLSCertificateConfig, error) { - if keyGen != nil { - return signer.NewClientCertificate(r.UserInfo, keyGen, crypto.WithLifetime(validity)) + if keyGen == nil { + return signer.MakeClientCertificateForDuration(r.UserInfo, validity) } - return signer.MakeClientCertificateForDuration(r.UserInfo, validity) + return signer.NewClientCertificate(r.UserInfo, keyGen, crypto.WithLifetime(validity)) } func (r *ClientRotation) NeedNewTargetCertKeyPair(currentCertSecret *corev1.Secret, signer *crypto.CA, caBundleCerts []*x509.Certificate, refresh time.Duration, refreshOnlyWhenExpired, exists bool) string { @@ -356,14 +356,13 @@ func (r *ServingRotation) NewCertificate(signer *crypto.CA, validity time.Durati if len(hostnames) == 0 { return nil, fmt.Errorf("no hostnames set") } - if keyGen != nil { - return signer.NewServerCertificate( - sets.New(hostnames...), keyGen, - crypto.WithLifetime(validity), - crypto.WithExtensions(r.CertificateExtensionFn...), - ) + if keyGen == nil { + return signer.MakeServerCertForDuration(sets.New(hostnames...), validity, r.CertificateExtensionFn...) } - return signer.MakeServerCertForDuration(sets.New(hostnames...), validity, r.CertificateExtensionFn...) + return signer.NewServerCertificate(sets.New(hostnames...), keyGen, + crypto.WithLifetime(validity), + crypto.WithExtensions(r.CertificateExtensionFn...), + ) } func (r *ServingRotation) RecheckChannel() <-chan struct{} { @@ -423,23 +422,25 @@ func (r *SignerRotation) CertificateType() pki.CertificateType { func (r *SignerRotation) NewCertificate(signer *crypto.CA, validity time.Duration, keyGen crypto.KeyPairGenerator) (*crypto.TLSCertificateConfig, error) { signerName := fmt.Sprintf("%s_@%d", r.SignerName, time.Now().Unix()) - if keyGen != nil { - return crypto.NewSigningCertificate(signerName, keyGen, - crypto.WithSigner(signer), - crypto.WithLifetime(validity), - ) + if keyGen == nil { + return crypto.MakeCAConfigForDuration(signerName, validity, signer) } - return crypto.MakeCAConfigForDuration(signerName, validity, signer) + return crypto.NewSigningCertificate(signerName, keyGen, + crypto.WithSigner(signer), + crypto.WithLifetime(validity), + ) } // PeerRotation creates certificates used for both server and client authentication // (e.g., etcd peer certificates). It uses CertificateTypePeer for PKI profile // resolution, which selects the stronger of the serving and client key configurations. // -// When keyGen is non-nil (ConfigurablePKI enabled), it calls NewPeerCertificate -// which requires UserInfo for the client identity. When keyGen is nil (legacy), -// it falls back to MakeServerCertForDuration with an extension function that -// sets both ClientAuth and ServerAuth ExtKeyUsages. +// UserInfo is always required. When keyGen is non-nil (ConfigurablePKI enabled), +// NewPeerCertificate encodes UserInfo as the client identity in the certificate +// subject. When keyGen is nil (legacy), UserInfo.Name must match the sorted first +// hostname (used as CN by MakeServerCertForDuration), and the certificate falls +// back to MakeServerCertForDuration with an extension function that sets both +// ClientAuth and ServerAuth ExtKeyUsages. type PeerRotation struct { Hostnames ServingHostnameFunc UserInfo user.Info @@ -456,24 +457,28 @@ func (r *PeerRotation) NewCertificate(signer *crypto.CA, validity time.Duration, if len(hostnames) == 0 { return nil, fmt.Errorf("no hostnames set") } - if keyGen != nil { - if r.UserInfo == nil { - return nil, fmt.Errorf("PeerRotation requires UserInfo for configurable PKI certificates") + if r.UserInfo == nil { + return nil, fmt.Errorf("PeerRotation requires UserInfo") + } + if keyGen == nil { + // Legacy path: use server cert template with extension fn to add both ExtKeyUsages. + // MakeServerCertForDuration sorts hostnames and uses the first sorted value as CN. + sortedHostnames := sets.List(sets.New(hostnames...)) + if cn := sortedHostnames[0]; r.UserInfo.GetName() != cn { + return nil, fmt.Errorf("PeerRotation legacy path uses sorted first hostname %q as CN; UserInfo.Name %q conflicts — set UserInfo.Name to match or enable ConfigurablePKI", cn, r.UserInfo.GetName()) + } + peerExtFn := func(cert *x509.Certificate) error { + cert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth} + return nil } - return signer.NewPeerCertificate( - sets.New(hostnames...), r.UserInfo, keyGen, - crypto.WithLifetime(validity), - crypto.WithExtensions(r.CertificateExtensionFn...), - ) - } - // Legacy path: use server cert template with extension fn to add both ExtKeyUsages. - // The subject CN comes from the first hostname (preserves current behavior). - peerExtFn := func(cert *x509.Certificate) error { - cert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth} - return nil - } - extensions := append(append([]crypto.CertificateExtensionFunc{}, r.CertificateExtensionFn...), peerExtFn) - return signer.MakeServerCertForDuration(sets.New(hostnames...), validity, extensions...) + extensions := append(append([]crypto.CertificateExtensionFunc{}, r.CertificateExtensionFn...), peerExtFn) + return signer.MakeServerCertForDuration(sets.New(hostnames...), validity, extensions...) + } + return signer.NewPeerCertificate( + sets.New(hostnames...), r.UserInfo, keyGen, + crypto.WithLifetime(validity), + crypto.WithExtensions(r.CertificateExtensionFn...), + ) } func (r *PeerRotation) RecheckChannel() <-chan struct{} { diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptiondata/secret.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptiondata/secret.go index b2d025e19f..9eccbef748 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptiondata/secret.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/encryptiondata/secret.go @@ -202,6 +202,12 @@ func ExtractUniqueAndSortedKMSConfigurations(secretData *Config) ([]*apiserverco return result, nil } +// SecretDataKeyName returns the data key used in the encryption-config Secret +// for a KMS plugin secret entry: "kms-plugin-secret-{secretName}_{dataKey}-{keyID}". +func SecretDataKeyName(secretName, dataKey, keyID string) string { + return fmt.Sprintf("%s%s_%s-%s", encryptionConfigSecretDataPrefix, secretName, dataKey, keyID) +} + func parseSecretDataKey(dataKey string) (keyID, rawKey string, found bool, err error) { rest, found := strings.CutPrefix(dataKey, encryptionConfigSecretDataPrefix) if !found { diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/sidecar.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/sidecar.go index 1925cec3e0..b42a5f9430 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/sidecar.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/sidecar.go @@ -3,16 +3,25 @@ package pluginlifecycle import ( "context" "fmt" + "path/filepath" configv1 "github.com/openshift/api/config/v1" "github.com/openshift/api/features" "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" "github.com/openshift/library-go/pkg/operator/encryption/encryptiondata" + "github.com/openshift/library-go/pkg/operator/encryption/state" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/klog/v2" + "k8s.io/utils/ptr" +) + +const ( + resourceDirVolumeName = "resource-dir" + credentialsVolumeName = "kms-plugin-credentials" + credentialsMountPath = "/var/run/secrets/kms-plugin" ) // sidecarProvider abstracts the construction of a KMS plugin sidecar container for a specific provider (e.g. Vault). @@ -24,65 +33,113 @@ type sidecarProvider interface { } // newSidecarProvider creates a provider-specific SidecarProvider for the given keyID, UDS endpoint, and plugin configuration. -func newSidecarProvider(keyID string, udsPath string, pluginConfig configv1.KMSPluginConfig) (sidecarProvider, error) { +func newSidecarProvider(keyID string, udsPath string, pluginConfig configv1.KMSPluginConfig, secretData state.KMSSecretData, credentialsDir string) (sidecarProvider, error) { switch pluginConfig.Type { case configv1.VaultKMSProvider: - return newVaultSidecarProvider("vault-kms-plugin", keyID, udsPath, pluginConfig) + return newVaultSidecarProvider("vault-kms-plugin", keyID, udsPath, pluginConfig, secretData, credentialsDir) default: return nil, fmt.Errorf("unsupported KMS plugin configuration") } } -// AddKMSPluginSidecarToPodSpec discovers KMS plugins from the encryption-config secret and injects a sidecar container for each one into the pod spec. +// AddKMSPluginSidecarToStaticPodSpec discovers KMS plugins from the encryption-config secret and injects a sidecar +// container for each one into a kube-apiserver static pod spec. Credentials are accessed via the resource-dir volume +// mount that the static pod revision controller populates on disk. +// It is a no-op when the KMSEncryption feature gate is not enabled or the encryption-config secret does not exist. +func AddKMSPluginSidecarToStaticPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, resourcesDir string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess) error { + credentialsDir := filepath.Join(resourcesDir, "secrets", encryptionConfigSecretName) + + sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsDir) + if err != nil { + return err + } + + for _, name := range sidecarNames { + if err := ensureVolumeMountInContainer(podSpec.InitContainers, name, resourceDirVolumeName, resourcesDir, true); err != nil { + return err + } + setRunAsUser(podSpec.InitContainers, name, ptr.To(int64(0))) + } + + return nil +} + +// AddKMSPluginSidecarToPodSpec discovers KMS plugins from the encryption-config secret and injects a sidecar +// container for each one into an aggregated API server pod spec. The encryption-config secret is mounted as a +// volume to provide credentials. // It is a no-op when the KMSEncryption feature gate is not enabled or the encryption-config secret does not exist. -// It uses an uncached client to avoid injecting sidecars based on a stale encryption configuration. func AddKMSPluginSidecarToPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess) error { + sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsMountPath) + if err != nil { + return err + } + + if len(sidecarNames) == 0 { + return nil + } + + for _, name := range sidecarNames { + if err := ensureVolumeMountInContainer(podSpec.InitContainers, name, credentialsVolumeName, credentialsMountPath, true); err != nil { + return err + } + } + + ensureCredentialsVolume(podSpec, encryptionConfigSecretName) + + return nil +} + +// addKMSPluginSidecars contains the shared logic for discovering KMS plugins and injecting sidecar containers. +// It returns the names of the sidecar containers that were injected, so callers can add deployment-mode-specific +// volume mounts. +func addKMSPluginSidecars(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess, credentialsDir string) ([]string, error) { if podSpec == nil { - return fmt.Errorf("pod spec cannot be nil") + return nil, fmt.Errorf("pod spec cannot be nil") } if containerName == "" { - return fmt.Errorf("container name cannot be empty") + return nil, fmt.Errorf("container name cannot be empty") } if !featureGateAccessor.AreInitialFeatureGatesObserved() { - return nil + return nil, nil } featureGates, err := featureGateAccessor.CurrentFeatureGates() if err != nil { - return fmt.Errorf("failed to get feature gates: %w", err) + return nil, fmt.Errorf("failed to get feature gates: %w", err) } if !featureGates.Enabled(features.FeatureGateKMSEncryption) { - return nil + return nil, nil } encryptionConfigurationSecret, err := secretClient.Secrets(encryptionConfigNamespace).Get(ctx, encryptionConfigSecretName, metav1.GetOptions{}) if apierrors.IsNotFound(err) { klog.V(4).Infof("skipping KMS sidecar injection: %s/%s secret not found", encryptionConfigNamespace, encryptionConfigSecretName) - return nil + return nil, nil } if err != nil { - return fmt.Errorf("failed to get %s/%s secret: %w", encryptionConfigNamespace, encryptionConfigSecretName, err) + return nil, fmt.Errorf("failed to get %s/%s secret: %w", encryptionConfigNamespace, encryptionConfigSecretName, err) } encryptionConfig, err := encryptiondata.FromSecret(encryptionConfigurationSecret) if err != nil { - return fmt.Errorf("failed to extract encryption config from %s/%s secret: %w", encryptionConfigNamespace, encryptionConfigSecretName, err) + return nil, fmt.Errorf("failed to extract encryption config from %s/%s secret: %w", encryptionConfigNamespace, encryptionConfigSecretName, err) } kmsConfigurations, err := encryptiondata.ExtractUniqueAndSortedKMSConfigurations(encryptionConfig) if err != nil { - return fmt.Errorf("failed to get KMS configurations: %w", err) + return nil, fmt.Errorf("failed to get KMS configurations: %w", err) } if len(kmsConfigurations) == 0 { klog.V(4).Infof("skipping KMS sidecar injection: no KMS plugins found in EncryptionConfiguration") - return nil + return nil, nil } klog.V(4).Infof("injecting %d KMS sidecar(s)", len(kmsConfigurations)) + var sidecarNames []string for _, kmsConfiguration := range kmsConfigurations { // ExtractUniqueAndSortedKMSConfigurations function rewrites the .Name field to include only the key ID keyID := kmsConfiguration.Name @@ -90,31 +147,38 @@ func AddKMSPluginSidecarToPodSpec(ctx context.Context, podSpec *corev1.PodSpec, pluginConfig, ok := encryptionConfig.KMSPlugins[keyID] if !ok { - return fmt.Errorf("missing plugin config for keyID %s", keyID) + return nil, fmt.Errorf("missing plugin config for keyID %s", keyID) + } + + var secretData state.KMSSecretData + if encryptionConfig.KMSPluginsSecretData.ByKeyID != nil { + secretData = encryptionConfig.KMSPluginsSecretData.ByKeyID[keyID] } - sidecarProvider, err := newSidecarProvider(keyID, udsPath, pluginConfig) + provider, err := newSidecarProvider(keyID, udsPath, pluginConfig, secretData, credentialsDir) if err != nil { - return fmt.Errorf("failed to create a sidecar provider for keyID %s: %w", keyID, err) + return nil, fmt.Errorf("failed to create a sidecar provider for keyID %s: %w", keyID, err) } - if err := ensureSidecarContainer(podSpec, sidecarProvider); err != nil { - return err + if err := ensureSidecarContainer(podSpec, provider); err != nil { + return nil, err } - if err := ensureSocketVolumeMountInContainer(podSpec.InitContainers, sidecarProvider.Name()); err != nil { - return err + if err := ensureVolumeMountInContainer(podSpec.InitContainers, provider.Name(), "kms-plugin-socket", "/var/run/kmsplugin", false); err != nil { + return nil, err } + + sidecarNames = append(sidecarNames, provider.Name()) } - if err := ensureSocketVolumeMountInContainer(podSpec.Containers, containerName); err != nil { - return err + if err := ensureVolumeMountInContainer(podSpec.Containers, containerName, "kms-plugin-socket", "/var/run/kmsplugin", false); err != nil { + return nil, err } // The volume mount in the kube-apiserver and KMS plugin containers requires a volume in the podSpec ensureSocketVolume(podSpec) - return nil + return sidecarNames, nil } func ensureSidecarContainer(podSpec *corev1.PodSpec, provider sidecarProvider) error { @@ -134,7 +198,7 @@ func ensureSidecarContainer(podSpec *corev1.PodSpec, provider sidecarProvider) e return nil } -func ensureSocketVolumeMountInContainer(containers []corev1.Container, containerName string) error { +func ensureVolumeMountInContainer(containers []corev1.Container, containerName, volumeName, mountPath string, readOnly bool) error { containerIndex := -1 for i, container := range containers { if container.Name == containerName { @@ -147,22 +211,19 @@ func ensureSocketVolumeMountInContainer(containers []corev1.Container, container return fmt.Errorf("container %s not found", containerName) } - foundMount := false container := &containers[containerIndex] for _, m := range container.VolumeMounts { - if m.Name == "kms-plugin-socket" { - foundMount = true - break + if m.Name == volumeName { + return nil } } - if !foundMount { - container.VolumeMounts = append(container.VolumeMounts, - corev1.VolumeMount{ - Name: "kms-plugin-socket", - MountPath: "/var/run/kmsplugin", - }, - ) - } + container.VolumeMounts = append(container.VolumeMounts, + corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + ReadOnly: readOnly, + }, + ) return nil } @@ -182,3 +243,34 @@ func ensureSocketVolume(podSpec *corev1.PodSpec) { }, ) } + +func setRunAsUser(containers []corev1.Container, containerName string, uid *int64) { + for i, c := range containers { + if c.Name == containerName { + if c.SecurityContext == nil { + containers[i].SecurityContext = &corev1.SecurityContext{} + } + containers[i].SecurityContext.RunAsUser = uid + return + } + } +} + +func ensureCredentialsVolume(podSpec *corev1.PodSpec, secretName string) { + for _, volume := range podSpec.Volumes { + if volume.Name == credentialsVolumeName { + return + } + } + + podSpec.Volumes = append(podSpec.Volumes, + corev1.Volume{ + Name: credentialsVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + }, + }, + }, + ) +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/vault.go b/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/vault.go index 9b2fdc2562..aee7aedf49 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/vault.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/encryption/kms/pluginlifecycle/vault.go @@ -2,29 +2,45 @@ package pluginlifecycle import ( "fmt" + "path/filepath" configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/operator/encryption/encryptiondata" + "github.com/openshift/library-go/pkg/operator/encryption/state" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/utils/ptr" ) // newVaultSidecarProvider creates a Vault sidecar provider from the given KMS plugin configuration. -func newVaultSidecarProvider(name, keyID, udsPath string, pluginConfig configv1.KMSPluginConfig) (*vault, error) { +func newVaultSidecarProvider(name, keyID, udsPath string, pluginConfig configv1.KMSPluginConfig, secretData state.KMSSecretData, credentialsDir string) (*vault, error) { + secretName := pluginConfig.Vault.Authentication.AppRole.Secret.Name + + var roleID string + if entries, ok := secretData.Entries[secretName]; ok { + roleID = string(entries["role-id"]) + } + + secretIDPath := filepath.Join(credentialsDir, encryptiondata.SecretDataKeyName(secretName, "secret-id", keyID)) + return &vault{ - name: name, - keyID: keyID, - udsPath: udsPath, - config: &pluginConfig.Vault, + name: name, + keyID: keyID, + udsPath: udsPath, + config: &pluginConfig.Vault, + roleID: roleID, + secretIDPath: secretIDPath, }, nil } // vault implements SidecarProvider for HashiCorp Vault KMS. type vault struct { - name string - keyID string - udsPath string - config *configv1.VaultKMSPluginConfig + name string + keyID string + udsPath string + config *configv1.VaultKMSPluginConfig + roleID string + secretIDPath string } // Name returns the sidecar name appended by the key id. @@ -41,10 +57,8 @@ func (v *vault) BuildSidecarContainer() (corev1.Container, error) { fmt.Sprintf("-vault-address=%s", v.config.VaultAddress), fmt.Sprintf("-transit-mount=%s", v.config.TransitMount), fmt.Sprintf("-transit-key=%s", v.config.TransitKey), - // TODO(bertinatto): dummy value for the Vault mock plugin; will come from the encryption-config secret. - fmt.Sprintf("-approle-role-id=dummy-role-id-%s", v.keyID), - // TODO(bertinatto): placeholder path for the Vault mock plugin; will differ per operator (KASO vs. aggregated apiserver operators). - fmt.Sprintf("-approle-secret-id-path=/var/run/secrets/vault-kms/secret-id-%s", v.keyID), + fmt.Sprintf("-approle-role-id=%s", v.roleID), + fmt.Sprintf("-approle-secret-id-path=%s", v.secretIDPath), } // Optional fields: only pass non-empty values. diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/kms/vault.go b/vendor/github.com/openshift/library-go/test/library/encryption/kms/vault.go index 5585ec5a2a..8978ce48ef 100644 --- a/vendor/github.com/openshift/library-go/test/library/encryption/kms/vault.go +++ b/vendor/github.com/openshift/library-go/test/library/encryption/kms/vault.go @@ -22,17 +22,18 @@ import ( ) const ( - defaultVaultNamespace = "vault-kms" - defaultVaultPodName = "vault-0" - defaultVaultCredentialsSecret = "vault-credentials" - defaultVaultAppRoleSecretName = "vault-approle-secret" - defaultVaultKMSPluginImage = "quay.io/openshifttest/mock-kms-plugin@sha256:03bb07a2c08b509653c4c70217a06a4b389c10b4d87922f50ee5eac82db5e140" - defaultVaultAddress = "https://vault.vault-kms.svc:8200" - defaultVaultEnterpriseNS = "admin" - defaultVaultTransitMount = "transit" - defaultVaultTransitKey = "kms-key" - defaultAppRoleTargetNamespace = "openshift-config" - vaultCommandTimeout = 30 * time.Second + defaultVaultNamespace = "vault-kms" + defaultVaultPodName = "vault-0" + defaultVaultCredentialsSecret = "vault-credentials" + defaultVaultAppRoleSecretName = "vault-approle-secret" + defaultfakevaultkmspluginimage = "quay.io/openshifttest/mock-kms-plugin@sha256:03bb07a2c08b509653c4c70217a06a4b389c10b4d87922f50ee5eac82db5e140" + defaultVaultKMSPluginImage = "registry.ci.openshift.org/control-plane-custom-builds/vault-kube-kms@sha256:33599dd6eee61dcf9a60138759fafda3d88593a3c2072585156882c6b5bd3fa5" + defaultVaultAddress = "https://vault.vault-kms.svc:8200" + defaultVaultEnterpriseNS = "admin" + defaultVaultTransitMount = "transit" + defaultVaultTransitKey = "kms-key" + defaultAppRoleTargetNamespace = "openshift-config" + vaultCommandTimeout = 30 * time.Second ) // DefaultVaultEncryptionProvider is a ready-to-use Vault KMS EncryptionProvider for e2e tests. @@ -75,7 +76,7 @@ var DefaultFakeKMSPluginConfig = configv1.APIServerEncryption{ KMS: configv1.KMSPluginConfig{ Type: configv1.VaultKMSProvider, Vault: configv1.VaultKMSPluginConfig{ - KMSPluginImage: WellKnownUpstreamMockKMSPluginImage, + KMSPluginImage: defaultfakevaultkmspluginimage, VaultAddress: "https://vault.example.com", Authentication: configv1.VaultAuthentication{ Type: configv1.VaultAuthenticationTypeAppRole, diff --git a/vendor/modules.txt b/vendor/modules.txt index ec17793e99..2eb76d335d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -407,7 +407,7 @@ github.com/openshift/client-go/security/informers/externalversions/internalinter github.com/openshift/client-go/security/informers/externalversions/security github.com/openshift/client-go/security/informers/externalversions/security/v1 github.com/openshift/client-go/security/listers/security/v1 -# github.com/openshift/library-go v0.0.0-20260527152424-3ad832f9a5a5 +# github.com/openshift/library-go v0.0.0-20260529072806-a742402e251f => github.com/gangwgr/library-go v0.0.0-20260529072806-a742402e251f ## explicit; go 1.25.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/assets @@ -1699,3 +1699,4 @@ sigs.k8s.io/structured-merge-diff/v6/value ## explicit; go 1.22 sigs.k8s.io/yaml # github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 +# github.com/openshift/library-go => github.com/gangwgr/library-go v0.0.0-20260529072806-a742402e251f