Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Confirm active replace directive:"
rg -n '^replace github.com/openshift/library-go' go.mod

echo
echo "2) Check whether pseudo-version suffix exists in fork vs upstream:"
SHORT_SHA='1ae67025ddad'
echo "Fork:"
git ls-remote https://github.com/bertinatto/library-go.git | rg "$SHORT_SHA" || true
echo "Upstream:"
git ls-remote https://github.com/openshift/library-go.git | rg "$SHORT_SHA" || true

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 407


Remove the personal-fork replace directive before merge.

Line 140 reroutes all github.com/openshift/library-go imports to a personal fork on a non-upstream feature branch (kms-plugin-lifecycle-postcondition), creating a supply-chain and reproducibility risk for CI and releases. Either remove this directive or resolve it by contributing the change upstream and using the official version.

🔧 Proposed fix
-replace github.com/openshift/library-go => github.com/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
replace github.com/openshift/library-go => github.com/bertinatto/library-go v0.0.0-20260423204751-1ae67025ddad
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go.mod` at line 140, The go.mod contains a replace directive rerouting
github.com/openshift/library-go to a personal fork (replace
github.com/openshift/library-go => github.com/bertinatto/library-go
v0.0.0-20260423204751-1ae67025ddad); remove this replace entry (or replace it
with an official upstream version tag) before merging, and if the forked change
is needed, upstream the patch and reference the official module version instead
of the personal fork.

4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
116 changes: 116 additions & 0 deletions pkg/operator/kms/postcheck.go
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +43 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not swallow feature-gate retrieval errors.

At Line 45, err != nil returns nil, so post-check silently fails open.

🔧 Proposed fix
  featureGates, err := featureGateAccessor.CurrentFeatureGates()
  if err != nil {
-		return nil
+		return fmt.Errorf("kms revision post-check: failed to read current feature gates: %w", err)
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
featureGates, err := featureGateAccessor.CurrentFeatureGates()
if err != nil {
return nil
featureGates, err := featureGateAccessor.CurrentFeatureGates()
if err != nil {
return fmt.Errorf("kms revision post-check: failed to read current feature gates: %w", err)
}
🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 45-45: error is not nil (line 43) but it returns nil

(nilerr)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/kms/postcheck.go` around lines 43 - 45, The code is silently
swallowing errors from featureGateAccessor.CurrentFeatureGates(); instead of
returning nil on err, propagate or return the error (or a wrapped context error)
from the enclosing post-check function so callers know retrieval failed; update
the error handling around featureGates, err :=
featureGateAccessor.CurrentFeatureGates() to return the err (or
fmt.Errorf("failed to get feature gates: %w", err)) and ensure any callers of
the post-check function handle that error appropriately.

}
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that notfound revisioned Secret is totally benign?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume so, that's why I return nil here. Should I handle it differently?

}
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
}
Comment on lines +53 to +64
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Current NotFound handling bypasses revision validation.

Lines 55 and 63 return early, which skips mismatch detection entirely. For encryption-config NotFound, continue with kmsConfigured=false; for kube-apiserver-pod NotFound, fail the post-check.

🔧 Proposed fix
-	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)
-	}
+	encryptionSecret, err := kubeClient.CoreV1().Secrets(operatorclient.TargetNamespace).Get(ctx, "encryption-config"+revSuffix, metav1.GetOptions{})
+	kmsConfigured := false
+	if apierrors.IsNotFound(err) {
+		kmsConfigured = false
+	} else if err != nil {
+		return fmt.Errorf("kms revision post-check: failed to get encryption-config for revision %d: %w", revision, err)
+	} else {
+		kmsConfigured = hasKMSEncryptionProvider(encryptionSecret)
+	}
 
 	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)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/kms/postcheck.go` around lines 53 - 64, The current NotFound
handling in the post-check returns early and skips revision validation; instead,
when kubeClient.CoreV1().Secrets(...).Get for "encryption-config"+revSuffix
returns apierrors.IsNotFound(err) set kmsConfigured = false and continue
processing (do not return), and when kubeClient.CoreV1().ConfigMaps(...).Get for
"kube-apiserver-pod"+revSuffix returns apierrors.IsNotFound(err) treat it as a
failure of the post-check (return a descriptive error) so the revision mismatch
is detected; update the logic around encryptionSecret, podCM, the
apierrors.IsNotFound checks and the post-check return paths to implement these
behaviors.

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
})
})
}
2 changes: 2 additions & 0 deletions pkg/operator/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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