Skip to content

Commit 94e02de

Browse files
committed
ESO-435: Extends ExternalSecretsConfig with trustedCABundle for custom CA support
Signed-off-by: Bharath B <bhb@redhat.com>
1 parent f418404 commit 94e02de

9 files changed

Lines changed: 324 additions & 24 deletions

api/v1alpha1/external_secrets_config_types.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ type ControllerConfig struct {
159159
// +listMapKey=componentName
160160
// +optional
161161
ComponentConfigs []ComponentConfig `json:"componentConfigs,omitempty"`
162+
163+
// trustedCABundle references a ConfigMap containing PEM-encoded CA certificates for the external-secrets core controller to trust when making outbound TLS connections.
164+
// If specified, this bundle is used for all outbound TLS traffic, including connections to external secret management systems and configured proxies.
165+
// The ConfigMap must exist in the external-secrets operand namespace.
166+
// When omitted, external providers fall back to standard system certificates, while proxy connections use the OpenShift trusted CA bundle by default.
167+
// +optional
168+
TrustedCABundle *ConfigMapKeyReference `json:"trustedCABundle,omitempty"`
162169
}
163170

164171
// ComponentConfig defines configuration overrides for a specific external-secrets component.
@@ -175,9 +182,10 @@ type ComponentConfig struct {
175182
DeploymentConfigs *DeploymentConfig `json:"deploymentConfigs,omitempty"`
176183

177184
// overrideEnv specifies custom environment variables for this component's container. These are merged with operator-managed environment variables, with user-defined values taking precedence.
178-
// Keys starting with 'HOSTNAME', 'KUBERNETES_', or 'EXTERNAL_SECRETS_' are reserved and will be rejected.
185+
// Names starting with 'KUBERNETES_' or 'EXTERNAL_SECRETS_' are reserved prefixes and will be rejected.
186+
// The exact names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are also reserved.
179187
// +kubebuilder:validation:MaxItems:=50
180-
// +kubebuilder:validation:XValidation:rule="self.all(e, !['HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p, e.name.startsWith(p)))",message="Environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed"
188+
// +kubebuilder:validation:XValidation:rule="self.all(e, !['KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p, e.name.startsWith(p)) && e.name != 'HOSTNAME' && e.name != 'SSL_CERT_DIR' && e.name != 'SSL_CERT_FILE')",message="Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
181189
// +listType=map
182190
// +listMapKey=name
183191
// +optional

api/v1alpha1/meta.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ type ProxyConfig struct {
123123
NetworkPolicyProvisioning ManagementState `json:"networkPolicyProvisioning,omitempty"`
124124
}
125125

126+
// ConfigMapKeyReference refers to a specific key within a ConfigMap.
127+
type ConfigMapKeyReference struct {
128+
// name of the ConfigMap resource being referred to.
129+
// +kubebuilder:validation:MinLength:=1
130+
// +kubebuilder:validation:MaxLength:=253
131+
// +required
132+
Name string `json:"name,omitempty"`
133+
134+
// key is the specific key in the ConfigMap to be utilized.
135+
// When omitted, defaults to "ca-bundle.crt".
136+
// +kubebuilder:validation:MinLength:=1
137+
// +kubebuilder:validation:MaxLength:=253
138+
// +kubebuilder:validation:Pattern:=^[-._a-zA-Z0-9]+$
139+
// +kubebuilder:default:="ca-bundle.crt"
140+
// +optional
141+
Key string `json:"key,omitempty"`
142+
}
143+
126144
// ManagementState controls whether the operator manages the resource lifecycle.
127145
type ManagementState string
128146

api/v1alpha1/tests/externalsecretsconfig.operator.openshift.io/externalsecretsconfig.testsuite.yaml

Lines changed: 187 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ tests:
987987
- componentName: Webhook
988988
deploymentConfigs:
989989
revisionHistoryLimit: 50
990-
- name: Should fail with overrideEnv starting with HOSTNAME
990+
- name: Should fail with overrideEnv using reserved name HOSTNAME
991991
resourceName: cluster
992992
initial: |
993993
apiVersion: operator.openshift.io/v1alpha1
@@ -999,7 +999,7 @@ tests:
999999
overrideEnv:
10001000
- name: HOSTNAME
10011001
value: "test"
1002-
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed"
1002+
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
10031003
- name: Should fail with overrideEnv starting with KUBERNETES_
10041004
resourceName: cluster
10051005
initial: |
@@ -1012,7 +1012,7 @@ tests:
10121012
overrideEnv:
10131013
- name: KUBERNETES_SERVICE_HOST
10141014
value: "test"
1015-
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed"
1015+
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
10161016
- name: Should fail with overrideEnv starting with EXTERNAL_SECRETS_
10171017
resourceName: cluster
10181018
initial: |
@@ -1025,7 +1025,190 @@ tests:
10251025
overrideEnv:
10261026
- name: EXTERNAL_SECRETS_CONFIG
10271027
value: "test"
1028-
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names with reserved prefixes 'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not allowed"
1028+
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
1029+
- name: Should fail with overrideEnv using reserved name SSL_CERT_DIR
1030+
resourceName: cluster
1031+
initial: |
1032+
apiVersion: operator.openshift.io/v1alpha1
1033+
kind: ExternalSecretsConfig
1034+
spec:
1035+
controllerConfig:
1036+
componentConfigs:
1037+
- componentName: ExternalSecretsCoreController
1038+
overrideEnv:
1039+
- name: SSL_CERT_DIR
1040+
value: "/custom/certs"
1041+
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
1042+
- name: Should fail with overrideEnv using reserved name SSL_CERT_FILE
1043+
resourceName: cluster
1044+
initial: |
1045+
apiVersion: operator.openshift.io/v1alpha1
1046+
kind: ExternalSecretsConfig
1047+
spec:
1048+
controllerConfig:
1049+
componentConfigs:
1050+
- componentName: ExternalSecretsCoreController
1051+
overrideEnv:
1052+
- name: SSL_CERT_FILE
1053+
value: "/custom/ca.crt"
1054+
expectedError: "ExternalSecretsConfig.operator.openshift.io \"cluster\" is invalid: spec.controllerConfig.componentConfigs[0].overrideEnv: Invalid value: \"array\": Environment variable names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are reserved, along with any names that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'."
1055+
- name: Should allow overrideEnv starting with HOSTNAME as prefix (only exact HOSTNAME is reserved)
1056+
resourceName: cluster
1057+
initial: |
1058+
apiVersion: operator.openshift.io/v1alpha1
1059+
kind: ExternalSecretsConfig
1060+
spec:
1061+
controllerConfig:
1062+
componentConfigs:
1063+
- componentName: ExternalSecretsCoreController
1064+
overrideEnv:
1065+
- name: HOSTNAME_SUFFIX
1066+
value: "allowed"
1067+
expected: |
1068+
apiVersion: operator.openshift.io/v1alpha1
1069+
kind: ExternalSecretsConfig
1070+
spec:
1071+
controllerConfig:
1072+
componentConfigs:
1073+
- componentName: ExternalSecretsCoreController
1074+
overrideEnv:
1075+
- name: HOSTNAME_SUFFIX
1076+
value: "allowed"
1077+
- name: Should allow overrideEnv with SSL_CERT_DIR as substring (not exact name)
1078+
resourceName: cluster
1079+
initial: |
1080+
apiVersion: operator.openshift.io/v1alpha1
1081+
kind: ExternalSecretsConfig
1082+
spec:
1083+
controllerConfig:
1084+
componentConfigs:
1085+
- componentName: ExternalSecretsCoreController
1086+
overrideEnv:
1087+
- name: MY_SSL_CERT_DIR
1088+
value: "/custom/certs"
1089+
expected: |
1090+
apiVersion: operator.openshift.io/v1alpha1
1091+
kind: ExternalSecretsConfig
1092+
spec:
1093+
controllerConfig:
1094+
componentConfigs:
1095+
- componentName: ExternalSecretsCoreController
1096+
overrideEnv:
1097+
- name: MY_SSL_CERT_DIR
1098+
value: "/custom/certs"
1099+
- name: Should allow trustedCABundle with required name field only (defaults applied)
1100+
resourceName: cluster
1101+
initial: |
1102+
apiVersion: operator.openshift.io/v1alpha1
1103+
kind: ExternalSecretsConfig
1104+
spec:
1105+
controllerConfig:
1106+
trustedCABundle:
1107+
name: trusted-ca-bundle
1108+
expected: |
1109+
apiVersion: operator.openshift.io/v1alpha1
1110+
kind: ExternalSecretsConfig
1111+
spec:
1112+
controllerConfig:
1113+
trustedCABundle:
1114+
name: trusted-ca-bundle
1115+
key: ca-bundle.crt
1116+
- name: Should allow trustedCABundle with explicit key
1117+
resourceName: cluster
1118+
initial: |
1119+
apiVersion: operator.openshift.io/v1alpha1
1120+
kind: ExternalSecretsConfig
1121+
spec:
1122+
controllerConfig:
1123+
trustedCABundle:
1124+
name: my-ca-bundle
1125+
key: ca-chain.crt
1126+
expected: |
1127+
apiVersion: operator.openshift.io/v1alpha1
1128+
kind: ExternalSecretsConfig
1129+
spec:
1130+
controllerConfig:
1131+
trustedCABundle:
1132+
name: my-ca-bundle
1133+
key: ca-chain.crt
1134+
- name: Should fail with trustedCABundle name empty
1135+
resourceName: cluster
1136+
initial: |
1137+
apiVersion: operator.openshift.io/v1alpha1
1138+
kind: ExternalSecretsConfig
1139+
spec:
1140+
controllerConfig:
1141+
trustedCABundle:
1142+
name: ""
1143+
expectedError: "spec.controllerConfig.trustedCABundle.name: Invalid value: \"\": spec.controllerConfig.trustedCABundle.name in body should be at least 1 chars long"
1144+
- name: Should fail with trustedCABundle name too long
1145+
resourceName: cluster
1146+
initial: |
1147+
apiVersion: operator.openshift.io/v1alpha1
1148+
kind: ExternalSecretsConfig
1149+
spec:
1150+
controllerConfig:
1151+
trustedCABundle:
1152+
name: "this-configmap-name-is-extremely-long-and-exceeds-the-kubernetes-maximum-name-length-limit-of-two-hundred-fifty-three-characters-which-is-quite-a-lot-of-characters-but-we-need-to-test-this-validation-constraint-properly-to-ensure-it-works-as-expected-ok1"
1153+
expectedError: "spec.controllerConfig.trustedCABundle.name: Too long: may not be more than 253 bytes"
1154+
- name: Should fail with trustedCABundle key empty
1155+
resourceName: cluster
1156+
initial: |
1157+
apiVersion: operator.openshift.io/v1alpha1
1158+
kind: ExternalSecretsConfig
1159+
spec:
1160+
controllerConfig:
1161+
trustedCABundle:
1162+
name: trusted-ca-bundle
1163+
key: ""
1164+
expectedError: "spec.controllerConfig.trustedCABundle.key: Invalid value: \"\": spec.controllerConfig.trustedCABundle.key in body should be at least 1 chars long"
1165+
- name: Should fail with trustedCABundle key containing invalid characters
1166+
resourceName: cluster
1167+
initial: |
1168+
apiVersion: operator.openshift.io/v1alpha1
1169+
kind: ExternalSecretsConfig
1170+
spec:
1171+
controllerConfig:
1172+
trustedCABundle:
1173+
name: trusted-ca-bundle
1174+
key: "invalid key with spaces"
1175+
expectedError: "spec.controllerConfig.trustedCABundle.key: Invalid value: \"invalid key with spaces\": spec.controllerConfig.trustedCABundle.key in body should match"
1176+
- name: Should allow trustedCABundle combined with componentConfigs and annotations
1177+
resourceName: cluster
1178+
initial: |
1179+
apiVersion: operator.openshift.io/v1alpha1
1180+
kind: ExternalSecretsConfig
1181+
spec:
1182+
controllerConfig:
1183+
annotations:
1184+
example.com/team: "platform"
1185+
componentConfigs:
1186+
- componentName: ExternalSecretsCoreController
1187+
deploymentConfigs:
1188+
revisionHistoryLimit: 5
1189+
overrideEnv:
1190+
- name: GOMAXPROCS
1191+
value: "4"
1192+
trustedCABundle:
1193+
name: trusted-ca-bundle
1194+
key: ca-bundle.crt
1195+
expected: |
1196+
apiVersion: operator.openshift.io/v1alpha1
1197+
kind: ExternalSecretsConfig
1198+
spec:
1199+
controllerConfig:
1200+
annotations:
1201+
example.com/team: "platform"
1202+
componentConfigs:
1203+
- componentName: ExternalSecretsCoreController
1204+
deploymentConfigs:
1205+
revisionHistoryLimit: 5
1206+
overrideEnv:
1207+
- name: GOMAXPROCS
1208+
value: "4"
1209+
trustedCABundle:
1210+
name: trusted-ca-bundle
1211+
key: ca-bundle.crt
10291212
- name: Should allow componentConfigs with revisionHistoryLimit
10301213
resourceName: cluster
10311214
initial: |

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/openshift-external-secrets-operator.clusterserviceversion.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ metadata:
220220
categories: Security
221221
console.openshift.io/disable-operand-delete: "true"
222222
containerImage: openshift.io/external-secrets-operator:latest
223-
createdAt: "2026-05-18T08:51:19Z"
223+
createdAt: "2026-05-28T12:12:06Z"
224224
features.operators.openshift.io/cnf: "false"
225225
features.operators.openshift.io/cni: "false"
226226
features.operators.openshift.io/csi: "false"

bundle/manifests/operator.openshift.io_externalsecretsconfigs.yaml

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,7 +1334,8 @@ spec:
13341334
overrideEnv:
13351335
description: |-
13361336
overrideEnv specifies custom environment variables for this component's container. These are merged with operator-managed environment variables, with user-defined values taking precedence.
1337-
Keys starting with 'HOSTNAME', 'KUBERNETES_', or 'EXTERNAL_SECRETS_' are reserved and will be rejected.
1337+
Names starting with 'KUBERNETES_' or 'EXTERNAL_SECRETS_' are reserved prefixes and will be rejected.
1338+
The exact names 'HOSTNAME', 'SSL_CERT_DIR', and 'SSL_CERT_FILE' are also reserved.
13381339
items:
13391340
description: EnvVar represents an environment variable
13401341
present in a Container.
@@ -1496,11 +1497,12 @@ spec:
14961497
- name
14971498
x-kubernetes-list-type: map
14981499
x-kubernetes-validations:
1499-
- message: Environment variable names with reserved prefixes
1500-
'HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_' are not
1501-
allowed
1502-
rule: self.all(e, !['HOSTNAME', 'KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p,
1503-
e.name.startsWith(p)))
1500+
- message: Environment variable names 'HOSTNAME', 'SSL_CERT_DIR',
1501+
and 'SSL_CERT_FILE' are reserved, along with any names
1502+
that start with 'KUBERNETES_' or 'EXTERNAL_SECRETS_'.
1503+
rule: self.all(e, !['KUBERNETES_', 'EXTERNAL_SECRETS_'].exists(p,
1504+
e.name.startsWith(p)) && e.name != 'HOSTNAME' && e.name
1505+
!= 'SSL_CERT_DIR' && e.name != 'SSL_CERT_FILE')
15041506
required:
15051507
- componentName
15061508
type: object
@@ -1766,6 +1768,31 @@ spec:
17661768
immutable
17671769
rule: oldSelf.all(op, self.exists(p, p.name == op.name && p.componentName
17681770
== op.componentName))
1771+
trustedCABundle:
1772+
description: |-
1773+
trustedCABundle references a ConfigMap containing PEM-encoded CA certificates for the external-secrets core controller to trust when making outbound TLS connections.
1774+
If specified, this bundle is used for all outbound TLS traffic, including connections to external secret management systems and configured proxies.
1775+
The ConfigMap must exist in the external-secrets operand namespace.
1776+
When omitted, external providers fall back to standard system certificates, while proxy connections use the OpenShift trusted CA bundle by default.
1777+
properties:
1778+
key:
1779+
default: ca-bundle.crt
1780+
description: |-
1781+
key is the specific key in the ConfigMap to be utilized.
1782+
When omitted, defaults to "ca-bundle.crt".
1783+
maxLength: 253
1784+
minLength: 1
1785+
pattern: ^[-._a-zA-Z0-9]+$
1786+
type: string
1787+
name:
1788+
description: name of the ConfigMap resource being referred
1789+
to.
1790+
maxLength: 253
1791+
minLength: 1
1792+
type: string
1793+
required:
1794+
- name
1795+
type: object
17691796
type: object
17701797
plugins:
17711798
description: plugins is for configuring the optional provider plugins.

0 commit comments

Comments
 (0)