From f24e67dbc5d6460370cc9fac98abfa8e6e7e06b5 Mon Sep 17 00:00:00 2001 From: Fabio Bertinatto Date: Mon, 26 Jan 2026 16:43:44 -0300 Subject: [PATCH] kms: add helper for TechPreview V1 --- pkg/operator/encryption/kms/helpers.go | 66 +++++++ pkg/operator/encryption/kms/helpers_test.go | 197 ++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 pkg/operator/encryption/kms/helpers.go create mode 100644 pkg/operator/encryption/kms/helpers_test.go diff --git a/pkg/operator/encryption/kms/helpers.go b/pkg/operator/encryption/kms/helpers.go new file mode 100644 index 0000000000..c5031ce2ab --- /dev/null +++ b/pkg/operator/encryption/kms/helpers.go @@ -0,0 +1,66 @@ +package kms + +import ( + "fmt" + + "github.com/openshift/api/features" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" + corev1 "k8s.io/api/core/v1" +) + +// AddKMSPluginVolumeAndMountToPodSpec conditionally adds the KMS plugin volume mount to the specified container. +// It assumes the pod spec does not already contain the KMS volume or mount; no deduplication is performed. +// Deprecated: this is a temporary solution to get KMS TP v1 out. We should come up with a different approach afterwards. +func AddKMSPluginVolumeAndMountToPodSpec(podSpec *corev1.PodSpec, containerName string, featureGateAccessor featuregates.FeatureGateAccess) error { + if podSpec == nil { + return fmt.Errorf("pod spec cannot be nil") + } + + if !featureGateAccessor.AreInitialFeatureGatesObserved() { + return nil + } + + featureGates, err := featureGateAccessor.CurrentFeatureGates() + if err != nil { + return fmt.Errorf("failed to get feature gates: %w", err) + } + + if !featureGates.Enabled(features.FeatureGateKMSEncryption) { + return nil + } + + containerIndex := -1 + for i, container := range podSpec.Containers { + if container.Name == containerName { + containerIndex = i + break + } + } + + if containerIndex < 0 { + return fmt.Errorf("container %s not found", containerName) + } + + container := &podSpec.Containers[containerIndex] + container.VolumeMounts = append(container.VolumeMounts, + corev1.VolumeMount{ + Name: "kms-plugin-socket", + MountPath: "/var/run/kmsplugin", + }, + ) + + directoryOrCreate := corev1.HostPathDirectoryOrCreate + podSpec.Volumes = append(podSpec.Volumes, + corev1.Volume{ + Name: "kms-plugin-socket", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/var/run/kmsplugin", + Type: &directoryOrCreate, + }, + }, + }, + ) + + return nil +} diff --git a/pkg/operator/encryption/kms/helpers_test.go b/pkg/operator/encryption/kms/helpers_test.go new file mode 100644 index 0000000000..626c93f375 --- /dev/null +++ b/pkg/operator/encryption/kms/helpers_test.go @@ -0,0 +1,197 @@ +package kms + +import ( + "errors" + "testing" + + configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/api/features" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" + corev1 "k8s.io/api/core/v1" + + "github.com/stretchr/testify/require" +) + +func TestAddKMSPluginVolume(t *testing.T) { + directoryOrCreate := corev1.HostPathDirectoryOrCreate + + tests := []struct { + name string + featureGateAccessor featuregates.FeatureGateAccess + actualPodSpec *corev1.PodSpec + expectedPodSpec *corev1.PodSpec + containerName string + expectError bool + }{ + { + name: "nil pod: returns error", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccess( + []configv1.FeatureGateName{features.FeatureGateKMSEncryption}, + []configv1.FeatureGateName{}, + ), + actualPodSpec: nil, + expectedPodSpec: nil, + containerName: "kube-apiserver", + expectError: true, + }, + { + name: "container not found: returns error", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccess( + []configv1.FeatureGateName{features.FeatureGateKMSEncryption}, + []configv1.FeatureGateName{}, + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "other-container"}, + }, + }, + expectedPodSpec: nil, + containerName: "kube-apiserver", + expectError: true, + }, + { + name: "feature gates not observed: no volume added", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccessForTesting( + nil, + nil, + make(chan struct{}), + nil, + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + expectedPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + containerName: "kube-apiserver", + expectError: false, + }, + { + name: "feature gate accessor error: returns error", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccessForTesting( + nil, + nil, + func() chan struct{} { ch := make(chan struct{}); close(ch); return ch }(), + errors.New("some error"), + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + expectedPodSpec: nil, + containerName: "kube-apiserver", + expectError: true, + }, + { + name: "KMSEncryption feature gate disabled: no volume added", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccess( + []configv1.FeatureGateName{}, + []configv1.FeatureGateName{features.FeatureGateKMSEncryption}, + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + expectedPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + containerName: "kube-apiserver", + expectError: false, + }, + { + name: "KMSEncryption feature gate enabled: volume and mount added", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccess( + []configv1.FeatureGateName{features.FeatureGateKMSEncryption}, + []configv1.FeatureGateName{}, + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "kube-apiserver"}, + }, + }, + expectedPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "kube-apiserver", + VolumeMounts: []corev1.VolumeMount{ + {Name: "kms-plugin-socket", MountPath: "/var/run/kmsplugin"}, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "kms-plugin-socket", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/var/run/kmsplugin", + Type: &directoryOrCreate, + }, + }, + }, + }, + }, + containerName: "kube-apiserver", + expectError: false, + }, + { + name: "KMSEncryption feature gate enabled: only kube-apiserver container gets mount", + featureGateAccessor: featuregates.NewHardcodedFeatureGateAccess( + []configv1.FeatureGateName{features.FeatureGateKMSEncryption}, + []configv1.FeatureGateName{}, + ), + actualPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "other-container"}, + {Name: "kube-apiserver"}, + {Name: "another-container"}, + }, + }, + expectedPodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "other-container"}, + { + Name: "kube-apiserver", + VolumeMounts: []corev1.VolumeMount{ + {Name: "kms-plugin-socket", MountPath: "/var/run/kmsplugin"}, + }, + }, + {Name: "another-container"}, + }, + Volumes: []corev1.Volume{ + { + Name: "kms-plugin-socket", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "/var/run/kmsplugin", + Type: &directoryOrCreate, + }, + }, + }, + }, + }, + containerName: "kube-apiserver", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := AddKMSPluginVolumeAndMountToPodSpec(tt.actualPodSpec, tt.containerName, tt.featureGateAccessor) + + if tt.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.expectedPodSpec, tt.actualPodSpec) + }) + } +}