Skip to content
Closed
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-20260423111950-e491ed3b41e5
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify replacement target and version from current go.mod
replace_line="$(awk '/^replace github.com\/openshift\/library-go =>/{print $0}' go.mod)"
echo "replace line: ${replace_line}"

target_mod="$(awk '/^replace github.com\/openshift\/library-go =>/{print $4}' go.mod)"
target_ver="$(awk '/^replace github.com\/openshift\/library-go =>/{print $5}' go.mod)"

if [[ -z "${target_mod}" || -z "${target_ver}" ]]; then
  echo "ERROR: could not parse replacement module/version" >&2
  exit 1
fi

echo "target module: ${target_mod}"
echo "target version/ref: ${target_ver}"

# Check that pinned ref exists and inspect declared module path
url="https://raw.githubusercontent.com/${target_mod}/${target_ver}/go.mod"
echo "fetching: ${url}"
curl -fsSL "${url}" | sed -n '1,10p'

declared_module="$(curl -fsSL "${url}" | awk '/^module /{print $2; exit}')"
echo "declared module in replacement go.mod: ${declared_module}"

# Expected outcome:
# 1) URL resolves (ref is retrievable/stable)
# 2) declared module path is compatible with replace target usage
# 3) team confirms provenance policy for using a personal fork in CI

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 498


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Extract replacement details from go.mod
replace_line="$(grep '^replace github.com/openshift/library-go' go.mod)"
echo "Replace directive: ${replace_line}"

# Parse out the replacement module and version
# Format: replace github.com/openshift/library-go => github.com/bertinatto/library-go v0.0.0-20260423111950-e491ed3b41e5
target_mod=$(echo "${replace_line}" | awk '{print $4}')
target_ver=$(echo "${replace_line}" | awk '{print $5}')

echo "Target module: ${target_mod}"
echo "Target version: ${target_ver}"

# Convert module path to GitHub user/repo
# github.com/bertinatto/library-go => bertinatto/library-go
github_path=$(echo "${target_mod}" | sed 's|github.com/||')
echo "GitHub path: ${github_path}"

# Check if repository exists using GitHub API
echo ""
echo "Checking if repository exists..."
api_response=$(curl -fsSL "https://api.github.com/repos/${github_path}" 2>&1 || echo "FAILED")
if echo "${api_response}" | grep -q '"id"'; then
  echo "✓ Repository exists"
  echo "${api_response}" | grep -E '"name"|"private"|"fork"' | head -5
else
  echo "✗ Repository not found or API error"
fi

# Try to check if the specific commit/tag exists
echo ""
echo "Checking commit/tag: ${target_ver}"
commit_check=$(curl -fsSL "https://api.github.com/repos/${github_path}/commits/${target_ver}" 2>&1 || echo "FAILED")
if echo "${commit_check}" | grep -q '"sha"'; then
  echo "✓ Commit/tag exists"
else
  echo "✗ Commit/tag not found"
fi

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 171


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, verify the replace directive is in go.mod as shown
echo "=== Verifying go.mod content ==="
grep -n "^replace github.com/openshift/library-go" go.mod

# Extract the exact replacement target
replace_line=$(grep "^replace github.com/openshift/library-go" go.mod)
echo ""
echo "Full replace line: ${replace_line}"

# Parse module and version
target_mod=$(echo "${replace_line}" | awk -F' ' '{print $4}')
target_ver=$(echo "${replace_line}" | awk -F' ' '{print $5}')

echo ""
echo "Target module: ${target_mod}"
echo "Target version: ${target_ver}"

# Try a simple HTTP HEAD request to the GitHub repo (simpler than API)
github_path=$(echo "${target_mod}" | sed 's|github.com/||')
echo "GitHub repo: ${github_path}"
echo ""
echo "Attempting to verify repository is publicly accessible..."

# Try to fetch the main branch README as a simple existence check
if timeout 10 curl -fsSLI "https://github.com/${github_path}" > /dev/null 2>&1; then
  echo "✓ GitHub repository appears to exist"
else
  echo "✗ Cannot reach repository or repository does not exist"
fi

# Also check: is this a personal fork (bertinatto) or org-owned?
echo ""
echo "Assessment:"
echo "- Module is from personal account: ${github_path} (bertinatto)"
echo "- Original: github.com/openshift/library-go"
echo "- Redirects to personal fork instead of org-owned repository"

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 800


Replace directive references an inaccessible or non-existent repository.

The replace directive on line 140 redirects github.com/openshift/library-go to github.com/bertinatto/library-go, which cannot be reached or does not exist. This will cause build failures. Additionally, redirecting to a personal fork creates supply-chain and reproducibility risk for the operator runtime and test utilities.

Switch to an org-owned (or upstream) replacement before merge, and document removal criteria for this replace directive.

🤖 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 replace directive replacing
github.com/openshift/library-go with github.com/bertinatto/library-go is
pointing to an inaccessible/personal fork and must be removed or replaced with
an org-owned/upstream module; update the replace in go.mod to either use the
upstream github.com/openshift/library-go (or a vetted org-owned fork) at a valid
version, remove the transient personal fork entry, and add a short comment above
the replace directive describing the temporary nature and explicit criteria for
removal (e.g., upstream PR merged or version published) so reviewers know when
it can be deleted.

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-20260423111950-e491ed3b41e5 h1:gChhqn7yIjwKYycGEs5Uo+SgAd5EmARQFyoZsW52Ruc=
github.com/bertinatto/library-go v0.0.0-20260423111950-e491ed3b41e5/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
119 changes: 119 additions & 0 deletions pkg/operator/kms/postcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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))
}

// TODO: this needs to be moved to library-go

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 +48 to +50
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

Fail closed when feature-gate state can't be read.

Line 50 returns nil on CurrentFeatureGates() error, which disables this guard exactly when the gate state is unknown and allows the revision to proceed unvalidated.

Suggested fix
 	featureGates, err := featureGateAccessor.CurrentFeatureGates()
 	if err != nil {
-		return nil
+		return fmt.Errorf("kms revision post-check: failed to read 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 feature gates: %w", err)
}
🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 50-50: error is not nil (line 48) 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 48 - 50, The code calls
featureGateAccessor.CurrentFeatureGates() and currently returns nil on error,
which disables the guard; change this to fail closed by returning an error (wrap
or return the received err) from the surrounding function (the post-check
handler that uses featureGates) instead of nil so the revision is rejected when
feature gate state cannot be read; update the error path where
featureGateAccessor.CurrentFeatureGates() is invoked to propagate a descriptive
error rather than returning 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
}
Comment on lines +58 to +69
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

Don't treat missing revision resources as a successful check.

Because encryption-config is optional in pkg/operator/starter.go Lines 651-653, the IsNotFound path on Line 59 is a real state here. Returning nil means a revision can still pass when kube-apiserver-pod-<rev> already has kms-plugin but encryption-config-<rev> is absent—the exact mismatch this hook is supposed to block.

A safer split is: treat a missing encryption-config-<rev> as kmsConfigured=false and continue the comparison, but treat a missing kube-apiserver-pod-<rev> as an error because the revision is incomplete.

🤖 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 58 - 69, The post-check
incorrectly treats a missing encryption-config-<rev> as a successful check;
update the logic in the kms revision post-check (around the encryptionSecret
variable and the podCM retrieval) so that when
kubeClient.CoreV1().Secrets(...).Get returns IsNotFound you do NOT return nil
but instead mark kmsConfigured=false and continue to compare with the
kube-apiserver-pod config; however when kubeClient.CoreV1().ConfigMaps(...).Get
for "kube-apiserver-pod"+revSuffix returns IsNotFound you should still treat
that as an error (returning the error) because the revision is incomplete.
Ensure the code paths reference encryptionSecret, podCM, and revSuffix and
preserve existing error wrapping for non-NotFound errors.

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")
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
}
Comment on lines +91 to +94
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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does Go's encoding/json.Unmarshal accept YAML input, or only JSON?

💡 Result:

No, Go's encoding/json.Unmarshal accepts only JSON input. It explicitly parses "JSON-encoded data" and returns a SyntaxError for invalid JSON syntax, such as YAML-specific features like colons without quotes or indentation-based structures.

Citations:


🏁 Script executed:

cat -n pkg/operator/kms/postcheck.go | head -120 | tail -50

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 2114


🏁 Script executed:

cat -n pkg/operator/kms/postcheck.go | head -100

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 3983


🏁 Script executed:

head -30 pkg/operator/kms/postcheck.go

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 964


Use yaml.Unmarshal to parse the pod manifest from ConfigMap.

Line 92 uses json.Unmarshal on podYAML retrieved from a ConfigMap with key "pod.yaml" (line 86). The encoding/json package only accepts JSON-formatted data and will fail when parsing YAML. This causes podHasKMSSidecar() to always return false, misclassifying KMS-enabled revisions and blocking all rollouts when KMS encryption is configured.

Suggested fix
-	if err := json.Unmarshal([]byte(podYAML), &pod); err != nil {
+	if err := yaml.Unmarshal([]byte(podYAML), &pod); err != nil {
 		return false
 	}

Also update the imports from "encoding/json" to include a YAML unmarshaller such as sigs.k8s.io/yaml.

🤖 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 91 - 94, podHasKMSSidecar is
unmarshalling YAML manifest using json.Unmarshal which fails; replace the json
unmarshal with a YAML unmarshal (e.g., use sigs.k8s.io/yaml.Unmarshal) when
decoding podYAML into the corev1.Pod, and update the imports to remove
"encoding/json" and add the YAML package import so the ConfigMap key "pod.yaml"
is parsed correctly.


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-20260423111950-e491ed3b41e5
## 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-20260423111950-e491ed3b41e5