Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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
8 changes: 7 additions & 1 deletion cmd/cluster-kube-apiserver-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/openshift/cluster-kube-apiserver-operator/pkg/cmd/resourcegraph"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/startupmonitorreadiness"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/targetconfigcontroller"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/version"
)

Expand All @@ -48,9 +49,10 @@ func NewOperatorCommand(ctx context.Context) *cobra.Command {
cmd.Version = v
}

opts := installerpod.NewInstallOptions().WithPodMutationFactory(targetconfigcontroller.AddKMSPluginToPodSpecFn)
cmd.AddCommand(installerpod.NewInstallerWithInstallOptions(ctx, opts))
Comment on lines +52 to +53
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for AddKMSPluginToPodSpecFn definition and usage
rg -n "AddKMSPluginToPodSpecFn" --type go -A 5

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 1363


🏁 Script executed:

#!/bin/bash
# Check the main.go file to see the full context including ptrBool
cat -n cmd/cluster-kube-apiserver-operator/main.go | head -80

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 3423


🏁 Script executed:

#!/bin/bash
# Search for ptrBool usage across the codebase
rg -n "ptrBool" --type go

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 414


Remove unused ptrBool function and fix installer wiring.

The installer command wiring at lines 52-53 is correct—AddKMSPluginToPodSpecFn is properly defined in the targetconfigcontroller package with the correct signature and integrates the KMS sidecar injection pathway as expected.

However, the ptrBool function at lines 73-75 is dead code and should be removed. It is not called anywhere in this file. (Note: a separate ptrBool function exists in kms_sidecar.go and is used there, but the one in main.go serves no purpose.)

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

In `@cmd/cluster-kube-apiserver-operator/main.go` around lines 52 - 53, Remove the
dead helper ptrBool function from this file: it is unused and duplicates the
real ptrBool in kms_sidecar.go; leave the installer wiring as-is (the call to
installerpod.NewInstallOptions().WithPodMutationFactory(targetconfigcontroller.AddKMSPluginToPodSpecFn)
and cmd.AddCommand(installerpod.NewInstallerWithInstallOptions(ctx, opts))
should remain unchanged), so delete the ptrBool declaration (the function named
ptrBool) and run a quick build to confirm no references remain.

cmd.AddCommand(operatorcmd.NewOperator())
cmd.AddCommand(render.NewRenderCommand())
cmd.AddCommand(installerpod.NewInstaller(ctx))
cmd.AddCommand(prune.NewPrune())
cmd.AddCommand(resourcegraph.NewResourceChainCommand())
cmd.AddCommand(certsyncpod.NewCertSyncControllerCommand(operator.CertConfigMaps, operator.CertSecrets))
Expand All @@ -67,3 +69,7 @@ func NewOperatorCommand(ctx context.Context) *cobra.Command {

return cmd
}

func ptrBool(b bool) *bool {
return &b
}
Comment on lines +72 to +75
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "ptrBool" --type go

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 414


🏁 Script executed:

head -20 cmd/cluster-kube-apiserver-operator/main.go

Repository: openshift/cluster-kube-apiserver-operator

Length of output: 833


Remove unused ptrBool function.

This helper function is defined but never called in this file. Since main.go is a package main entry point, it cannot be imported elsewhere. Remove the unused function at lines 73-75.

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

In `@cmd/cluster-kube-apiserver-operator/main.go` around lines 72 - 75, Remove the
unused helper function ptrBool from the package main entry point: delete the
function definition func ptrBool(b bool) *bool { return &b } (it is not
referenced anywhere in main.go and package main files should not keep orphan
helpers); ensure no other code relies on ptrBool before removing it.

182 changes: 182 additions & 0 deletions pkg/operator/targetconfigcontroller/kms_sidecar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package targetconfigcontroller

import (
"context"
"fmt"

"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
"github.com/openshift/library-go/pkg/operator/staticpod/installerpod"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
)

// AddKMSPluginToPodSpec 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 AddKMSPluginToPodSpec(podSpec *corev1.PodSpec, featureGateAccessor featuregates.FeatureGateAccess, secretLister corev1listers.SecretLister, targetNamespace string, kmsPluginImage string) 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) {
// klog.Infof("kms is disabled: feature gate %s is disabled", features.FeatureGateKMSEncryption)
// return nil
// }

creds, err := secretLister.Secrets(targetNamespace).Get("vault-kms-credentials")
if err != nil {
klog.Infof("kms is disabled: could not get vault-kms-credentials secret: %v", err)
return nil
Comment on lines +37 to +40
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

Handle non-NotFound secret retrieval errors explicitly.

This currently treats every secret retrieval error as “KMS disabled,” which can silently bypass KMS on transient API/cache failures. Only “not found” should be treated as disabled; other errors should fail fast.

🔧 Proposed fix
+import apierrors "k8s.io/apimachinery/pkg/api/errors"
...
  creds, err := secretLister.Secrets(targetNamespace).Get("vault-kms-credentials")
  if err != nil {
-   klog.Infof("kms is disabled: could not get vault-kms-credentials secret: %v", err)
-   return nil
+   if apierrors.IsNotFound(err) {
+     klog.Infof("kms is disabled: could not get vault-kms-credentials secret: %v", err)
+     return nil
+   }
+   return fmt.Errorf("failed to get vault-kms-credentials secret: %w", err)
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/targetconfigcontroller/kms_sidecar.go` around lines 37 - 40, The
code calling secretLister.Secrets(targetNamespace).Get("vault-kms-credentials")
treats any error as “KMS disabled”; change the logic to only swallow NotFound
errors and treat other errors as hard failures: use the k8s API error helper
(apierrors.IsNotFound(err)) to detect missing secret and keep the current
log/return-nil path for that case, but for all other errors log an error
(klog.Errorf) with the error details and return the error instead of nil so
transient API/cache failures surface and stop startup; update the block around
creds, err to reference secretLister, targetNamespace and the
"vault-kms-credentials" call accordingly.

}

// At this point we know we should deploy the KMS plugin
if err := addKMSPluginSidecarToPodSpec(podSpec, "kms-plugin", kmsPluginImage, creds); err != nil {
return err
}

if err := addKMSPluginVolumeAndMountToPodSpec(podSpec, "kms-plugin"); err != nil {
return err
}

if err := addKMSPluginVolumeAndMountToPodSpec(podSpec, "kube-apiserver"); err != nil {
return err
}

return nil
}

func addKMSPluginSidecarToPodSpec(podSpec *corev1.PodSpec, containerName string, image string, creds *corev1.Secret) error {
if podSpec == nil {
return fmt.Errorf("pod spec cannot be nil")
}

podSpec.Containers = append(podSpec.Containers, corev1.Container{
Name: containerName,
Image: image,
ImagePullPolicy: corev1.PullAlways,
Command: []string{"/bin/sh", "-c"},
Args: []string{fmt.Sprintf(`
echo "%s" > /tmp/secret-id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

some "secrets" should not be visible in the pod template.
i think we need to mount the secret/keys.

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.

discussed offline, but just for context:

right, this implementation is just a poc, once openshift/enhancements#1960 is implemented we'll be able to access the credentials as a mounted secret

exec /vault-kube-kms \
-listen-address=unix:///var/run/kmsplugin/kms.sock \
-vault-address=%s \
-vault-namespace=%s \
-transit-mount=transit \
-transit-key=%s \
-log-level=debug-extended \
-approle-role-id=%s \
-approle-secret-id-path=/tmp/secret-id`,
string(creds.Data["VAULT_SECRET_ID"]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is VAULT_SECRET_ID ?

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.

it's like an api key

string(creds.Data["VAULT_ADDR"]),
string(creds.Data["VAULT_NAMESPACE"]),
string(creds.Data["VAULT_KEY_NAME"]),
string(creds.Data["VAULT_ROLE_ID"])),
Comment on lines +69 to +84
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 | 🟡 Minor

Missing validation for required secret keys.

The code assumes all required keys (VAULT_SECRET_ID, VAULT_ADDR, VAULT_NAMESPACE, VAULT_KEY_NAME, VAULT_ROLE_ID) exist in the secret. If any key is missing, the resulting command will have empty values, leading to runtime failures that are hard to diagnose.

🛡️ Suggested validation
func validateCredentials(creds *corev1.Secret) error {
	requiredKeys := []string{"VAULT_SECRET_ID", "VAULT_ADDR", "VAULT_NAMESPACE", "VAULT_KEY_NAME", "VAULT_ROLE_ID"}
	for _, key := range requiredKeys {
		if _, ok := creds.Data[key]; !ok {
			return fmt.Errorf("missing required key %q in vault-kms-credentials secret", key)
		}
	}
	return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/targetconfigcontroller/kms_sidecar.go` around lines 66 - 81, Add
explicit validation to ensure the secret contains VAULT_SECRET_ID, VAULT_ADDR,
VAULT_NAMESPACE, VAULT_KEY_NAME, and VAULT_ROLE_ID before building the Args for
the sidecar; implement a helper like validateCredentials(creds *corev1.Secret)
that checks each required key and returns an error if any are missing, and call
it from the code path that constructs the fmt.Sprintf command (the block
creating Args in kms_sidecar.go) so you abort or surface a clear error instead
of producing empty values in the command.

},
SecurityContext: &corev1.SecurityContext{
Privileged: ptrBool(true),
},
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return nil
}

func addKMSPluginVolumeAndMountToPodSpec(podSpec *corev1.PodSpec, containerName string) error {
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",
},
)

foundVolumeMount := false
for _, volumeMount := range podSpec.Volumes {
if volumeMount.Name == "kms-plugin-socket" {
foundVolumeMount = true
}
}

if !foundVolumeMount {
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
}

func ptrBool(b bool) *bool {
return &b
}

func AddKMSPluginToPodSpecFn(o *installerpod.InstallOptions) installerpod.PodMutationFunc {
return func(pod *corev1.Pod) error {
creds, err := o.KubeClient.CoreV1().Secrets("openshift-kube-apiserver").Get(context.TODO(), "vault-kms-credentials", v1.GetOptions{})
if err != nil {
klog.Infof("kms is disabled: could not get vault-kms-credentials secret: %v", err)
return nil
}
klog.Infof("kms is ENABLED")

pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
Name: "kms-plugin",
Image: "quay.io/bertinatto/vault:v1",
ImagePullPolicy: corev1.PullAlways,
Command: []string{"/bin/sh", "-c"},
Args: []string{fmt.Sprintf(`
echo "%s" > /tmp/secret-id
exec /vault-kube-kms \
-listen-address=unix:///var/run/kmsplugin/kms.sock \
-vault-address=%s \
-vault-namespace=%s \
-transit-mount=transit \
-transit-key=%s \
-log-level=debug-extended \
-approle-role-id=%s \
-approle-secret-id-path=/tmp/secret-id`,
string(creds.Data["VAULT_SECRET_ID"]),
string(creds.Data["VAULT_ADDR"]),
string(creds.Data["VAULT_NAMESPACE"]),
string(creds.Data["VAULT_KEY_NAME"]),
string(creds.Data["VAULT_ROLE_ID"])),
},
SecurityContext: &corev1.SecurityContext{
Privileged: ptrBool(true),
},
})

return nil
Comment on lines +144 to +168
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

Pod mutation path is incomplete and can produce a non-functional KMS setup.

AddKMSPluginToPodSpecFn appends only the sidecar and hardcodes the image, but it never wires shared socket mounts for both kms-plugin and kube-apiserver. This diverges from the main helper path and breaks socket sharing.

♻️ Proposed consolidation/fix
-func AddKMSPluginToPodSpecFn(o *installerpod.InstallOptions) installerpod.PodMutationFunc {
+func AddKMSPluginToPodSpecFn(o *installerpod.InstallOptions, kmsPluginImage string) installerpod.PodMutationFunc {
 	return func(pod *corev1.Pod) error {
+		if pod == nil {
+			return fmt.Errorf("pod cannot be nil")
+		}
 		creds, err := o.KubeClient.CoreV1().Secrets("openshift-kube-apiserver").Get(context.TODO(), "vault-kms-credentials", v1.GetOptions{})
 		if err != nil {
 			klog.Infof("kms is disabled: could not get vault-kms-credentials secret: %v", err)
 			return nil
 		}
-		klog.Infof("kms is ENABLED")
-
-		pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
-			Name:            "kms-plugin",
-			Image:           "quay.io/bertinatto/vault:v1",
-			ImagePullPolicy: corev1.PullAlways,
-			Command:         []string{"/bin/sh", "-c"},
-			Args:            []string{...},
-			SecurityContext: &corev1.SecurityContext{
-				Privileged: ptrBool(true),
-			},
-		})
+		if err := addKMSPluginSidecarToPodSpec(&pod.Spec, "kms-plugin", kmsPluginImage, creds); err != nil {
+			return err
+		}
+		if err := addKMSPluginVolumeAndMountToPodSpec(&pod.Spec, "kms-plugin"); err != nil {
+			return err
+		}
+		if err := addKMSPluginVolumeAndMountToPodSpec(&pod.Spec, "kube-apiserver"); err != nil {
+			return err
+		}
 
 		return nil
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/targetconfigcontroller/kms_sidecar.go` around lines 144 - 180,
Add the missing shared socket wiring and avoid hardcoding the image: in
AddKMSPluginToPodSpecFn create a pod.Spec.Volumes entry (e.g., name
"kms-socket") using an EmptyDir, add a VolumeMount to the kms-plugin container
mounting that volume at /var/run/kmsplugin (so the -listen-address unix path is
actually backed by a shared FS), and locate the kube-apiserver container in
pod.Spec.Containers (match by Name "kube-apiserver") and add the same
VolumeMount to it so both containers share the socket; also stop hardcoding the
image string and use a configurable field from InstallOptions (e.g.,
o.KMSPluginImage) with a sensible fallback. Ensure all referenced symbols
(AddKMSPluginToPodSpecFn, kms-plugin container Name, kube-apiserver container
Name, and the volume name "kms-socket") are updated consistently.

}
}
15 changes: 9 additions & 6 deletions pkg/operator/targetconfigcontroller/targetconfigcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/certrotation"
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
encryptionkms "github.com/openshift/library-go/pkg/operator/encryption/kms"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
"github.com/openshift/library-go/pkg/operator/resource/resourcehelper"
Expand Down Expand Up @@ -58,6 +57,7 @@ type TargetConfigController struct {

kubeClient kubernetes.Interface
configMapLister corev1listers.ConfigMapLister
secretLister corev1listers.SecretLister
featureGateAccessor featuregates.FeatureGateAccess

isStartupMonitorEnabledFn func() (bool, error)
Expand All @@ -82,6 +82,7 @@ func NewTargetConfigController(
operatorClient: operatorClient,
kubeClient: kubeClient,
configMapLister: kubeInformersForNamespaces.ConfigMapLister(),
secretLister: kubeInformersForNamespaces.SecretLister(),
featureGateAccessor: featureGateAccessor,
isStartupMonitorEnabledFn: isStartupMonitorEnabledFn,
requireMultipleEtcdEndpointsFn: requireMultipleEtcdEndpointsFn,
Expand Down Expand Up @@ -224,7 +225,7 @@ func createTargetConfig(ctx context.Context, c TargetConfigController, recorder
if err != nil {
errors = append(errors, fmt.Errorf("%q: %v", "configmap/config", err))
}
_, _, err = managePods(ctx, c.kubeClient.CoreV1(), c.featureGateAccessor, c.isStartupMonitorEnabledFn, recorder, operatorSpec, c.targetImagePullSpec, c.operatorImagePullSpec, c.operatorImageVersion)
_, _, err = managePods(ctx, c.kubeClient.CoreV1(), c.featureGateAccessor, c.isStartupMonitorEnabledFn, recorder, operatorSpec, c.targetImagePullSpec, c.operatorImagePullSpec, c.operatorImageVersion, c.secretLister)
if err != nil {
errors = append(errors, fmt.Errorf("%q: %v", "configmap/kube-apiserver-pod", err))
}
Expand Down Expand Up @@ -308,7 +309,7 @@ func manageKubeAPIServerConfig(ctx context.Context, client coreclientv1.ConfigMa
return resourceapply.ApplyConfigMap(ctx, client, recorder, requiredConfigMap)
}

func managePods(ctx context.Context, client coreclientv1.ConfigMapsGetter, featureGateAccessor featuregates.FeatureGateAccess, isStartupMonitorEnabledFn func() (bool, error), recorder events.Recorder, operatorSpec *operatorv1.StaticPodOperatorSpec, imagePullSpec, operatorImagePullSpec, operatorImageVersion string) (*corev1.ConfigMap, bool, error) {
func managePods(ctx context.Context, client coreclientv1.ConfigMapsGetter, featureGateAccessor featuregates.FeatureGateAccess, isStartupMonitorEnabledFn func() (bool, error), recorder events.Recorder, operatorSpec *operatorv1.StaticPodOperatorSpec, imagePullSpec, operatorImagePullSpec, operatorImageVersion string, secretLister corev1listers.SecretLister) (*corev1.ConfigMap, bool, error) {
appliedPodTemplate, err := manageTemplate(string(bindata.MustAsset("assets/kube-apiserver/pod.yaml")), imagePullSpec, operatorImagePullSpec, operatorImageVersion, operatorSpec)
if err != nil {
return nil, false, err
Expand All @@ -329,9 +330,11 @@ func managePods(ctx context.Context, client coreclientv1.ConfigMapsGetter, featu
required.Spec.Containers[i].Env = append(container.Env, proxyEnvVars...)
}

if err := encryptionkms.AddKMSPluginVolumeAndMountToPodSpec(&required.Spec, "kube-apiserver", featureGateAccessor); err != nil {
return nil, false, fmt.Errorf("failed to add KMS encryption volumes: %w", err)
}
// // TODO: placeholder. I (fbertina) need to grant you read permissions. Please ask
// kmsPluginImage := "quay.io/bertinatto/vault:v1"
// if err := AddKMSPluginToPodSpec(&required.Spec, featureGateAccessor, secretLister, operatorclient.TargetNamespace, kmsPluginImage); err != nil {
// return nil, false, fmt.Errorf("failed to add KMS plugin sidecar: %w", err)
// }

configMap := resourceread.ReadConfigMapV1OrDie(bindata.MustAsset("assets/kube-apiserver/pod-cm.yaml"))
configMap.Data["pod.yaml"] = resourceread.WritePodV1OrDie(required)
Expand Down

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