From 78fdfb38372c7d1e825a8ae69ac42855bc13cf0c Mon Sep 17 00:00:00 2001 From: Fabio Bertinatto Date: Thu, 23 Apr 2026 17:13:51 -0400 Subject: [PATCH 1/2] fake bump --- go.mod | 2 ++ go.sum | 4 ++-- .../revisioncontroller/revision_controller.go | 14 ++++++++++++++ .../pkg/operator/staticpod/controllers.go | 8 ++++++++ vendor/modules.txt | 3 ++- 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5030e2f8c5..7fb01e69e5 100644 --- a/go.mod +++ b/go.mod @@ -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/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad diff --git a/go.sum b/go.sum index 4a551f7e46..dc4f91dd36 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-20260423204751-1ae67025ddad h1:niyn5OpELV07D+aBoTi3sxpl70CQUQUde+8UHZ00Tn4= +github.com/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad/go.mod h1:pQx73OLgJJtHR2WJVdkH0Zng/yywdL5hmPGw1QbGV/w= 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= @@ -165,8 +167,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-20260317180604-743f664b82d1 h1:Hr/R38eg5ZJXfbiaHumjJIN1buDZwhsm4ys4npVCXH0= github.com/openshift/client-go v0.0.0-20260317180604-743f664b82d1/go.mod h1:Za51LlH76ALiQ/aKGBYJXmyJNkA//IDJ+I///30CA2M= -github.com/openshift/library-go v0.0.0-20260420122951-18e793702c2d h1:qyb9W4WOZM2QFnEFzECG96sPjLyceqY9tdaa+8S0f8k= -github.com/openshift/library-go v0.0.0-20260420122951-18e793702c2d/go.mod h1:pQx73OLgJJtHR2WJVdkH0Zng/yywdL5hmPGw1QbGV/w= 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/vendor/github.com/openshift/library-go/pkg/operator/revisioncontroller/revision_controller.go b/vendor/github.com/openshift/library-go/pkg/operator/revisioncontroller/revision_controller.go index ed2a96c3d2..0a25a9c504 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/revisioncontroller/revision_controller.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/revisioncontroller/revision_controller.go @@ -40,6 +40,7 @@ type RevisionController struct { secretGetter corev1client.SecretsGetter revisionPrecondition PreconditionFunc + revisionPostCheck RevisionPostCheckFunc } type RevisionResource struct { @@ -50,6 +51,11 @@ type RevisionResource struct { // PreconditionFunc checks if revision precondition is met (is true) and then proceeeds with the creation of new revision type PreconditionFunc func(ctx context.Context) (bool, error) +// RevisionPostCheckFunc validates the assembled revision data after all resources +// have been copied but before the revision is marked as ready. If it returns an +// error, the revision will not be marked ready and the installer will not deploy it. +type RevisionPostCheckFunc func(ctx context.Context, revision int32) error + // NewRevisionController create a new revision controller. func NewRevisionController( instanceName string, @@ -62,6 +68,7 @@ func NewRevisionController( secretGetter corev1client.SecretsGetter, eventRecorder events.Recorder, revisionPrecondition PreconditionFunc, + revisionPostCheck RevisionPostCheckFunc, ) factory.Controller { if revisionPrecondition == nil { revisionPrecondition = func(ctx context.Context) (bool, error) { @@ -79,6 +86,7 @@ func NewRevisionController( configMapGetter: configMapGetter, secretGetter: secretGetter, revisionPrecondition: revisionPrecondition, + revisionPostCheck: revisionPostCheck, } return factory.New(). @@ -309,6 +317,12 @@ func (c RevisionController) createNewRevision(ctx context.Context, recorder even } } + if c.revisionPostCheck != nil { + if err := c.revisionPostCheck(ctx, revision); err != nil { + return false, fmt.Errorf("revision post-check failed for revision %d: %w", revision, err) + } + } + if createdStatus.Annotations == nil { createdStatus.Annotations = map[string]string{} } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/staticpod/controllers.go b/vendor/github.com/openshift/library-go/pkg/operator/staticpod/controllers.go index dc824715f1..645e1a0b0a 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/staticpod/controllers.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/staticpod/controllers.go @@ -82,6 +82,7 @@ type staticPodOperatorControllerBuilder struct { guardCreateConditionalFunc func() (bool, bool, error) revisionControllerPrecondition revisioncontroller.PreconditionFunc + revisionControllerPostCheck revisioncontroller.RevisionPostCheckFunc extraNodeSelector labels.Selector } @@ -131,6 +132,7 @@ type Builder interface { // Use this with caution, as this option can disrupt perspective pods that have not yet had a chance to become healthy. WithPodDisruptionBudgetGuard(operatorNamespace, operatorName, readyzPort, readyzEndpoint string, pdbUnhealthyPodEvictionPolicy *v1.UnhealthyPodEvictionPolicyType, createConditionalFunc func() (bool, bool, error)) Builder WithRevisionControllerPrecondition(revisionControllerPrecondition revisioncontroller.PreconditionFunc) Builder + WithRevisionControllerPostCheck(revisionControllerPostCheck revisioncontroller.RevisionPostCheckFunc) Builder ToControllers() (manager.ControllerManager, error) } @@ -223,6 +225,11 @@ func (b *staticPodOperatorControllerBuilder) WithRevisionControllerPrecondition( return b } +func (b *staticPodOperatorControllerBuilder) WithRevisionControllerPostCheck(revisionControllerPostCheck revisioncontroller.RevisionPostCheckFunc) Builder { + b.revisionControllerPostCheck = revisionControllerPostCheck + return b +} + func (b *staticPodOperatorControllerBuilder) ToControllers() (manager.ControllerManager, error) { manager := manager.NewControllerManager() @@ -261,6 +268,7 @@ func (b *staticPodOperatorControllerBuilder) ToControllers() (manager.Controller secretClient, eventRecorder, b.revisionControllerPrecondition, + b.revisionControllerPostCheck, ), 1) } else { errs = append(errs, fmt.Errorf("missing revisionController; cannot proceed")) diff --git a/vendor/modules.txt b/vendor/modules.txt index ed4fdc55a1..4cedf99908 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -406,7 +406,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-20260420122951-18e793702c2d +# github.com/openshift/library-go v0.0.0-20260420122951-18e793702c2d => github.com/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad ## explicit; go 1.25.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/assets @@ -1696,3 +1696,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/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad From 6f09bec7ec42922593b10c9bbb3fdd57f5ecfa67 Mon Sep 17 00:00:00 2001 From: Fabio Bertinatto Date: Thu, 23 Apr 2026 10:29:18 -0400 Subject: [PATCH 2/2] kms: validate the revision --- pkg/operator/kms/postcheck.go | 116 ++++++++++++++++++++++++++++++++++ pkg/operator/starter.go | 2 + 2 files changed, 118 insertions(+) create mode 100644 pkg/operator/kms/postcheck.go diff --git a/pkg/operator/kms/postcheck.go b/pkg/operator/kms/postcheck.go new file mode 100644 index 0000000000..fdc56f2128 --- /dev/null +++ b/pkg/operator/kms/postcheck.go @@ -0,0 +1,116 @@ +package kms + +import ( + "context" + "encoding/json" + "fmt" + "slices" + + "github.com/openshift/api/features" + "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/operatorclient" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" + "github.com/openshift/library-go/pkg/operator/revisioncontroller" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" +) + +var ( + apiserverScheme = runtime.NewScheme() + apiserverCodecs = serializer.NewCodecFactory(apiserverScheme) +) + +func init() { + utilruntime.Must(apiserverv1.AddToScheme(apiserverScheme)) +} + +func KMSRevisionPostCheck(kubeClient kubernetes.Interface, featureGateAccessor featuregates.FeatureGateAccess) revisioncontroller.RevisionPostCheckFunc { + return func(ctx context.Context, revision int32) error { + return validateKMSRevision(ctx, kubeClient, featureGateAccessor, revision) + } +} + +func validateKMSRevision(ctx context.Context, kubeClient kubernetes.Interface, featureGateAccessor featuregates.FeatureGateAccess, revision int32) error { + if !featureGateAccessor.AreInitialFeatureGatesObserved() { + return nil + } + featureGates, err := featureGateAccessor.CurrentFeatureGates() + if err != nil { + return nil + } + if !featureGates.Enabled(features.FeatureGateKMSEncryption) { + return nil + } + + revSuffix := fmt.Sprintf("-%d", revision) + + encryptionSecret, err := kubeClient.CoreV1().Secrets(operatorclient.TargetNamespace).Get(ctx, "encryption-config"+revSuffix, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return fmt.Errorf("kms revision post-check: failed to get encryption-config for revision %d: %w", revision, err) + } + + podCM, err := kubeClient.CoreV1().ConfigMaps(operatorclient.TargetNamespace).Get(ctx, "kube-apiserver-pod"+revSuffix, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return fmt.Errorf("kms revision post-check: failed to get kube-apiserver-pod for revision %d: %w", revision, err) + } + + kmsConfigured := hasKMSEncryptionProvider(encryptionSecret) + // FIXME(bertinatto): plugin name is hardcoded here + kmsInPod := podHasKMSSidecar(podCM, "kms-plugin") + + // TODO: validation needs to cover more than this + if kmsConfigured != kmsInPod { + return fmt.Errorf("kms revision post-check: revision %d has mismatched KMS state: kmsConfigured=%v kmsInPod=%v", revision, kmsConfigured, kmsInPod) + } + + klog.V(4).Infof("kms revision post-check passed for revision %d: kmsConfigured=%v kmsInPod=%v", revision, kmsConfigured, kmsInPod) + return nil +} + +func podHasKMSSidecar(podCM *corev1.ConfigMap, sidecarName string) bool { + podYAML, ok := podCM.Data["pod.yaml"] + if !ok { + return false + } + + var pod corev1.Pod + if err := json.Unmarshal([]byte(podYAML), &pod); err != nil { + return false + } + + return slices.ContainsFunc(pod.Spec.Containers, func(c corev1.Container) bool { + return c.Name == sidecarName + }) +} + +func hasKMSEncryptionProvider(encryptionConfig *corev1.Secret) bool { + encryptionConfigBytes, ok := encryptionConfig.Data["encryption-config"] + if !ok { + return false + } + + gvk := apiserverv1.SchemeGroupVersion.WithKind("EncryptionConfiguration") + obj, _, err := apiserverCodecs.UniversalDeserializer().Decode(encryptionConfigBytes, &gvk, nil) + if err != nil { + return false + } + + config := obj.(*apiserverv1.EncryptionConfiguration) + return slices.ContainsFunc(config.Resources, func(resource apiserverv1.ResourceConfiguration) bool { + return slices.ContainsFunc(resource.Providers, func(provider apiserverv1.ProviderConfiguration) bool { + return provider.KMS != nil + }) + }) +} diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index 5e6b076337..785f8dbf8b 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -31,6 +31,7 @@ import ( "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/node" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/connectivitycheckcontroller" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/highcpuusagealertcontroller" + "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/kms" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/kubeletversionskewcontroller" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/nodekubeconfigcontroller" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/operatorclient" @@ -351,6 +352,7 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle }, ). WithOperandPodLabelSelector(labels.Set{"apiserver": "true"}.AsSelector()). + WithRevisionControllerPostCheck(kms.KMSRevisionPostCheck(kubeClient, featureGateAccessor)). ToControllers() if err != nil { return err