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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/name: external-secrets-operator
config.openshift.io/inject-trusted-cabundle: "true"
control-plane: controller-manager
name: external-secrets-operator-trusted-ca-bundle
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ metadata:
categories: Security
console.openshift.io/disable-operand-delete: "true"
containerImage: openshift.io/external-secrets-operator:latest
createdAt: "2025-10-07T03:20:14Z"
createdAt: "2025-10-07T09:42:32Z"
features.operators.openshift.io/cnf: "false"
features.operators.openshift.io/cni: "false"
features.operators.openshift.io/csi: "false"
features.operators.openshift.io/disconnected: "false"
features.operators.openshift.io/fips-compliant: "true"
features.operators.openshift.io/proxy-aware: "false"
features.operators.openshift.io/proxy-aware: "true"
features.operators.openshift.io/tls-profiles: "false"
features.operators.openshift.io/token-auth-aws: "false"
features.operators.openshift.io/token-auth-azure: "false"
Expand Down Expand Up @@ -763,6 +763,9 @@ spec:
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /etc/pki/tls/certs
name: trusted-ca-bundle
readOnly: true
- mountPath: /etc/metrics-certs
name: metrics-serving-cert
readOnly: true
Expand All @@ -773,6 +776,9 @@ spec:
serviceAccountName: external-secrets-operator-controller-manager
terminationGracePeriodSeconds: 10
volumes:
- configMap:
name: external-secrets-operator-trusted-ca-bundle
name: trusted-ca-bundle
- name: metrics-serving-cert
secret:
secretName: metrics-serving-cert
Expand Down
6 changes: 0 additions & 6 deletions config/default/manager_metrics_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@
- op: add
path: /spec/template/spec/containers/0/args/-
value: --metrics-cert-dir=/etc/metrics-certs
- op: add
path: /spec/template/spec/containers/0/volumeMounts
value: []
- op: add
path: /spec/template/spec/containers/0/volumeMounts/-
value:
name: metrics-serving-cert
mountPath: /etc/metrics-certs
readOnly: true
- op: add
path: /spec/template/spec/volumes
value: []
- op: add
path: /spec/template/spec/volumes/-
value:
Expand Down
15 changes: 15 additions & 0 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,18 @@ images:
- name: controller
newName: openshift.io/external-secrets-operator
newTag: latest
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: trusted-ca-bundle
options:
labels:
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/name: external-secrets-operator
config.openshift.io/inject-trusted-cabundle: "true"
control-plane: controller-manager
patches:
- path: trusted-ca-patch.yaml
target:
kind: Deployment
name: controller-manager
18 changes: 18 additions & 0 deletions config/manager/trusted-ca-patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
containers:
- name: manager
volumeMounts:
- name: trusted-ca-bundle
mountPath: /etc/pki/tls/certs
readOnly: true
volumes:
- name: trusted-ca-bundle
configMap:
name: trusted-ca-bundle
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

ConfigMap name likely mismatched with the injected bundle manifest.

This references ConfigMap "trusted-ca-bundle", while the PR context mentions creating "external-secrets-operator-trusted-ca-bundle". If the names differ, the volume will mount an empty/non‑existent CM at runtime.

Apply this diff to align with the injected bundle name:

           configMap:
-            name: trusted-ca-bundle
+            name: external-secrets-operator-trusted-ca-bundle

Run this script to verify CM names across the repo and ensure consistency:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== kustomization in config/manager =="
fd -t f kustomization.yaml | rg -n '^config/manager/.*' -n
rg -n -C2 'configMapGenerator|name:\s*trusted-ca-bundle|external-secrets-operator-trusted-ca-bundle' config/manager/kustomization.yaml || true

echo "== CM manifests mentioning trusted CA bundle =="
fd -H -t f '.*(trusted|ca-bundle).*\.ya?ml' | rg -n -C2 'external-secrets-operator-trusted-ca-bundle|trusted-ca-bundle' || true

Length of output: 375


Align ConfigMap name to the injected bundle
Update both the volume patch and the configMapGenerator in the manager overlay to use the actual ConfigMap name:

  • In config/manager/trusted-ca-patch.yaml, change
                name: trusted-ca-bundle
    to
                name: external-secrets-operator-trusted-ca-bundle
  • In config/manager/kustomization.yaml’s configMapGenerator, change
    - name: trusted-ca-bundle
    to
    - name: external-secrets-operator-trusted-ca-bundle
🤖 Prompt for AI Agents
In config/manager/trusted-ca-patch.yaml around line 18 change the volume's name
from "trusted-ca-bundle" to "external-secrets-operator-trusted-ca-bundle"; also
update config/manager/kustomization.yaml (configMapGenerator) to rename the
generated ConfigMap from "trusted-ca-bundle" to
"external-secrets-operator-trusted-ca-bundle" so both the volume patch and the
generated ConfigMap names match the injected bundle.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ metadata:
features.operators.openshift.io/csi: "false"
features.operators.openshift.io/disconnected: "false"
features.operators.openshift.io/fips-compliant: "true"
features.operators.openshift.io/proxy-aware: "false"
features.operators.openshift.io/proxy-aware: "true"
features.operators.openshift.io/tls-profiles: "false"
features.operators.openshift.io/token-auth-aws: "false"
features.operators.openshift.io/token-auth-azure: "false"
Expand Down
86 changes: 86 additions & 0 deletions pkg/controller/external_secrets/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package external_secrets

import (
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

operatorv1alpha1 "github.com/openshift/external-secrets-operator/api/v1alpha1"
)

// ensureTrustedCABundleConfigMap creates or ensures the trusted CA bundle ConfigMap exists
// in the operand namespace when proxy configuration is present. The ConfigMap is labeled
// with the injection label required by the Cluster Network Operator (CNO), which watches
// for this label and injects the cluster's trusted CA bundle into the ConfigMap's data.
// This function ensures the correct labels are present so that CNO can manage the CA bundle
// content as expected.
func (r *Reconciler) ensureTrustedCABundleConfigMap(esc *operatorv1alpha1.ExternalSecretsConfig, resourceLabels map[string]string) error {
proxyConfig := r.getProxyConfiguration(esc)

// Only create ConfigMap if proxy is configured
if proxyConfig == nil {
return nil
}

namespace := getNamespace(esc)
expectedLabels := getTrustedCABundleLabels(resourceLabels)

configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: trustedCABundleConfigMapName,
Namespace: namespace,
Labels: expectedLabels,
},
}

// Check if the ConfigMap already exists
existingConfigMap := &corev1.ConfigMap{}
exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(configMap), existingConfigMap)
if err != nil {
return fmt.Errorf("failed to check if trusted CA bundle ConfigMap exists: %w", err)
}

if !exist {
// Create the ConfigMap
if err := r.Create(r.ctx, configMap); err != nil {
return fmt.Errorf("failed to create trusted CA bundle ConfigMap: %w", err)
}
return nil
}

// ConfigMap exists, ensure it has the correct labels
// Do not update the data of the ConfigMap since it is managed by CNO
if existingConfigMap.Labels == nil {
existingConfigMap.Labels = make(map[string]string)
}

expectedLabels = getTrustedCABundleLabels(resourceLabels)
needsUpdate := false
for k, expectedValue := range expectedLabels {
if existingValue, exists := existingConfigMap.Labels[k]; !exists || existingValue != expectedValue {
existingConfigMap.Labels[k] = expectedValue
needsUpdate = true
}
}

// Update the ConfigMap if any labels changed
if needsUpdate {
if err := r.Update(r.ctx, existingConfigMap); err != nil {
return fmt.Errorf("failed to update trusted CA bundle ConfigMap labels: %w", err)
}
}

return nil
}

// getTrustedCABundleLabels merges resource labels with the injection label
func getTrustedCABundleLabels(resourceLabels map[string]string) map[string]string {
labels := make(map[string]string)
for k, v := range resourceLabels {
labels[k] = v
}
labels[trustedCABundleInjectLabel] = "true"
return labels
}
19 changes: 19 additions & 0 deletions pkg/controller/external_secrets/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ const (
// externalsecretsDefaultNamespace is the namespace where the `external-secrets` operand required resources
// will be created, when ExternalSecretsConfig.Spec.Namespace is not set.
externalsecretsDefaultNamespace = "external-secrets"

// trustedCABundleConfigMapName is the name of the ConfigMap containing the trusted CA bundle
trustedCABundleConfigMapName = externalsecretsCommonName + "-trusted-ca-bundle"

// trustedCABundleInjectLabel is the label that triggers OpenShift CNO to inject cluster-wide CA certificates
trustedCABundleInjectLabel = "config.openshift.io/inject-trusted-cabundle"

// trustedCABundleVolumeName is the name of the volume for mounting the CA bundle
trustedCABundleVolumeName = "trusted-ca-bundle"

// trustedCABundleMountPath is the path where the CA bundle should be mounted in containers
// Default certificate path is taken from the golang source:
// https://cs.opensource.google/go/go/+/refs/tags/go1.24.4:src/crypto/x509/root_linux.go;l=22
trustedCABundleMountPath = "/etc/pki/tls/certs"

// Proxy environment variable names
httpProxyEnvVar = "HTTP_PROXY"
httpsProxyEnvVar = "HTTPS_PROXY"
noProxyEnvVar = "NO_PROXY"
)

var (
Expand Down
34 changes: 21 additions & 13 deletions pkg/controller/external_secrets/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ var (
&corev1.Secret{},
&corev1.Service{},
&corev1.ServiceAccount{},
&corev1.ConfigMap{},
&webhook.ValidatingWebhookConfiguration{},
}
)
Expand Down Expand Up @@ -251,28 +252,32 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
mapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request {
r.log.V(4).Info("received reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace())

objLabels := obj.GetLabels()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is required for Secret resource filtering scenario.

if objLabels != nil {
if objLabels[requestEnqueueLabelKey] == requestEnqueueLabelValue {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Name: common.ExternalSecretsConfigObjectName,
},
},
}
}
// Since predicate already filtered, all objects that reach here should trigger reconciliation
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Name: common.ExternalSecretsConfigObjectName,
},
},
}
r.log.V(4).Info("object not of interest, ignoring reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace())
return []reconcile.Request{}
}

// predicate function to ignore events for objects not managed by controller.
managedResources := predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue
})

// predicate function for ConfigMaps to include both managed resources and trusted CA bundle ConfigMap
configMapPredicate := predicate.NewPredicateFuncs(func(object client.Object) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

The trusted-ca configmap will have the requestEnqueueLabel set, I don' think we need a different predicate, managedResources should suffice.

if object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue {
return true
}
return object.GetName() == trustedCABundleConfigMapName
})

withIgnoreStatusUpdatePredicates := builder.WithPredicates(predicate.GenerationChangedPredicate{}, managedResources)
managedResourcePredicate := builder.WithPredicates(managedResources)
configMapResourcePredicate := builder.WithPredicates(configMapPredicate)

mgrBuilder := ctrl.NewControllerManagedBy(mgr).
For(&operatorv1alpha1.ExternalSecretsConfig{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Expand All @@ -288,6 +293,8 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
}
case &corev1.Secret{}:
mgrBuilder.WatchesMetadata(res, handler.EnqueueRequestsFromMapFunc(mapFunc), builder.WithPredicates(predicate.LabelChangedPredicate{}))
case &corev1.ConfigMap{}:
mgrBuilder.Watches(res, handler.EnqueueRequestsFromMapFunc(mapFunc), configMapResourcePredicate)
Comment on lines 294 to +297
Copy link
Contributor

Choose a reason for hiding this comment

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

We can do something like below for current configmap usage too, and later this would need an update. Or we can just use the managedResources and default case works well.

Suggested change
case &corev1.Secret{}:
mgrBuilder.WatchesMetadata(res, handler.EnqueueRequestsFromMapFunc(mapFunc), builder.WithPredicates(predicate.LabelChangedPredicate{}))
case &corev1.ConfigMap{}:
mgrBuilder.Watches(res, handler.EnqueueRequestsFromMapFunc(mapFunc), configMapResourcePredicate)
case &corev1.Secret{}, &corev1.ConfigMap{}:
mgrBuilder.WatchesMetadata(res, handler.EnqueueRequestsFromMapFunc(mapFunc), builder.WithPredicates(predicate.LabelChangedPredicate{}))

default:
mgrBuilder.Watches(res, handler.EnqueueRequestsFromMapFunc(mapFunc), managedResourcePredicate)
}
Expand Down Expand Up @@ -457,5 +464,6 @@ func (r *Reconciler) cleanUp(esc *operatorv1alpha1.ExternalSecretsConfig, req ct
}
r.log.V(1).Info("removed finalizer, cleanup complete", "request", req.NamespacedName)

// TODO: Cleanup trusted CA bundle ConfigMap if it exists
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Not mandatory to act. The TODO above covers all resources created for the operand installation, which would also include trusted CA ConfigMap.

return false, nil
}
Loading