From 4ea19e7da668c0cc4311d58b07cef7f8ac36ce31 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Tue, 5 May 2026 13:19:01 -0500 Subject: [PATCH 1/6] Enable use of an address outside the cluster if desired --- Chart.yaml | 2 +- README.md | 7 ++++-- README.md.gotmpl | 2 ++ .../external-secrets-hub-secretstore.yaml | 7 +++++- tests/external_secrets_secretstore_test.yaml | 24 +++++++++++++++++++ values.yaml | 2 ++ 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index 0f6e9c0..39b1da6 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -3,4 +3,4 @@ description: A Helm chart to set up the Openshift External Secrets Operator keywords: - pattern name: openshift-external-secrets -version: 0.0.3 +version: 0.0.4 diff --git a/README.md b/README.md index ccd1f9e..08bab81 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # openshift-external-secrets -![Version: 0.0.3](https://img.shields.io/badge/Version-0.0.3-informational?style=flat-square) +![Version: 0.0.4](https://img.shields.io/badge/Version-0.0.4-informational?style=flat-square) A Helm chart to set up the Openshift External Secrets Operator ## Notable changes +v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged vault + ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| clusterGroup.isHubCluster | bool | `true` | The variable that defines when a cluster is the HUB | +| clusterGroup.applications | object | `{}` | | | global | object | depends on the individual settings | The global namespace containes some globally used variables used in patterns | | global.clusterDomain | string | `"foo.example.com"` | The DNS entry for the cluster the chart is being rendered on | | global.hubClusterDomain | string | `"hub.example.com"` | The DNS entry for the hub cluster | @@ -36,6 +38,7 @@ A Helm chart to set up the Openshift External Secrets Operator | ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | | ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | | ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | +| ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, used as spec.parameters.vaultAddress (e.g. https://vault.example.com for an external Vault). Wh en empty, the default hub route https://vault-vault. is used. | | ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | ---------------------------------------------- diff --git a/README.md.gotmpl b/README.md.gotmpl index 8875935..032ebe9 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -7,6 +7,8 @@ ## Notable changes +v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged vault + {{ template "chart.homepageLine" . }} {{ template "chart.maintainersSection" . }} diff --git a/templates/vault/external-secrets-hub-secretstore.yaml b/templates/vault/external-secrets-hub-secretstore.yaml index ebd6d34..01fa3a7 100644 --- a/templates/vault/external-secrets-hub-secretstore.yaml +++ b/templates/vault/external-secrets-hub-secretstore.yaml @@ -16,7 +16,12 @@ metadata: spec: provider: vault: - server: https://vault-vault.{{ .Values.global.hubClusterDomain }} +{{- $extVault := .Values.ocpExternalSecrets.vault.externalAddress | default "" | trim }} +{{- if ne $extVault "" }} + server: {{ $extVault | quote }} +{{- else }} + server: "https://vault-vault.{{ .Values.global.hubClusterDomain }}" +{{- end }} path: secret # Version of KV backend version: v2 diff --git a/tests/external_secrets_secretstore_test.yaml b/tests/external_secrets_secretstore_test.yaml index aefddf3..739c397 100644 --- a/tests/external_secrets_secretstore_test.yaml +++ b/tests/external_secrets_secretstore_test.yaml @@ -48,6 +48,30 @@ tests: path: spec.provider.vault.server value: "https://vault-vault.foo.bar.baz" + - it: should use ocpExternalSecrets.vault.externalAddress when set + set: + global: + hubClusterDomain: ignored.hub.example.com + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + asserts: + - equal: + path: spec.provider.vault.server + value: "https://vault.external.example:8200" + + - it: should use hub vault URL when externalAddress is empty + set: + global: + hubClusterDomain: my.hub.domain + ocpExternalSecrets: + vault: + externalAddress: "" + asserts: + - equal: + path: spec.provider.vault.server + value: "https://vault-vault.my.hub.domain" + - it: should set secretRef with default values asserts: - equal: diff --git a/values.yaml b/values.yaml index 3df2bb1..04a8edf 100644 --- a/values.yaml +++ b/values.yaml @@ -25,6 +25,8 @@ ocpExternalSecrets: # -- Some vault configuration entries # @default -- depends on the individual settings vault: + # -- If non-empty, used as spec.parameters.vaultAddress (e.g. https://vault.example.com for an external Vault). Wh en empty, the default hub route https://vault-vault. is used. + externalAddress: "" # -- The vault secrets' path when connecting to it from the hub mountPath: "hub" From 55f30098691397d6019c60ac43f1bfafa90b7e63 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Tue, 5 May 2026 13:40:37 -0500 Subject: [PATCH 2/6] Pacify super-linter --- .github/linters/.markdown-lint.yml | 5 ++ .github/linters/.yaml-lint.yml | 17 +++++++ .prettierignore | 1 + README.md | 65 +++++++++++++------------ tests/edge_cases_test.yaml | 2 +- tests/external_secrets_config_test.yaml | 2 +- values.yaml | 6 ++- 7 files changed, 62 insertions(+), 36 deletions(-) create mode 100644 .github/linters/.markdown-lint.yml create mode 100644 .github/linters/.yaml-lint.yml create mode 100644 .prettierignore diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml new file mode 100644 index 0000000..9da2123 --- /dev/null +++ b/.github/linters/.markdown-lint.yml @@ -0,0 +1,5 @@ +--- +default: true +MD013: + line_length: 800 + tables: false diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 0000000..7b179ac --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,17 @@ +--- +extends: default + +ignore: | + templates/ + +rules: + document-start: disable + line-length: + max: 150 + allow-non-breakable-inline-mappings: true + allow-non-breakable-words: true + braces: disable + brackets: disable + indentation: disable + truthy: disable + comments: disable diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0350652 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +templates/ diff --git a/README.md b/README.md index 08bab81..aa908b0 100644 --- a/README.md +++ b/README.md @@ -10,36 +10,37 @@ v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged ## Values -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| clusterGroup.applications | object | `{}` | | -| global | object | depends on the individual settings | The global namespace containes some globally used variables used in patterns | -| global.clusterDomain | string | `"foo.example.com"` | The DNS entry for the cluster the chart is being rendered on | -| global.hubClusterDomain | string | `"hub.example.com"` | The DNS entry for the hub cluster | -| global.secretStore.backend | string | `"vault"` | The backend of ESO being used in the pattern | -| ocpExternalSecrets | object | depends on the individual settings | Dictionary of all the settings to configure this chart | -| ocpExternalSecrets.caProvider | object | depends on the individual settings | This controls how ESO connects to vault and it allows to specify where the public key of the CA that signed the API endpoint to talke to the vault | -| ocpExternalSecrets.caProvider.clientCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on a spoke cluster | -| ocpExternalSecrets.caProvider.clientCluster.key | string | `"hub-kube-root-ca.crt"` | Key of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.name | string | `"hub-ca"` | Name of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.type | string | `"Secret"` | Type of object where the CA is stored | -| ocpExternalSecrets.caProvider.enabled | bool | `true` | When set to true this uses a custom CA to talk to vault | -| ocpExternalSecrets.caProvider.hostCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on the hub cluster | -| ocpExternalSecrets.caProvider.hostCluster.key | string | `"ca.crt"` | Key of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.name | string | `"kube-root-ca.crt"` | Name of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.type | string | `"ConfigMap"` | Type of object where the CA is stored | -| ocpExternalSecrets.kubernetes | object | depends on the individual settings | Settings relevant when using the kubernetes backend | -| ocpExternalSecrets.kubernetes.remoteNamespace | string | `"validated-patterns-secrets"` | The remote namespace used in the ClusterSecretStore | -| ocpExternalSecrets.kubernetes.server.url | string | `"https://kubernetes.default"` | The URL used in the ClusterSecretStore | -| ocpExternalSecrets.rbac.rolename | string | `"hub-role"` | The name of the vault role when connecting to the vault from the hub | -| ocpExternalSecrets.rbac.serviceAccount | object | depends on the individual settings | ServiceAccount configuration for external secrets | -| ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | -| ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | -| ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | -| ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, used as spec.parameters.vaultAddress (e.g. https://vault.example.com for an external Vault). Wh en empty, the default hub route https://vault-vault. is used. | -| ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | - ----------------------------------------------- +| Key | Type | Default | Description | +| ----------------------------------------------------- | ------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| clusterGroup.applications | object | `{}` | | +| global | object | depends on the individual settings | The global namespace containes some globally used variables used in patterns | +| global.clusterDomain | string | `"foo.example.com"` | The DNS entry for the cluster the chart is being rendered on | +| global.hubClusterDomain | string | `"hub.example.com"` | The DNS entry for the hub cluster | +| global.secretStore.backend | string | `"vault"` | The backend of ESO being used in the pattern | +| ocpExternalSecrets | object | depends on the individual settings | Dictionary of all the settings to configure this chart | +| ocpExternalSecrets.caProvider | object | depends on the individual settings | This controls how ESO connects to vault and it allows to specify where the public key of the CA that signed the API endpoint to talke to the vault | +| ocpExternalSecrets.caProvider.clientCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on a spoke cluster | +| ocpExternalSecrets.caProvider.clientCluster.key | string | `"hub-kube-root-ca.crt"` | Key of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.name | string | `"hub-ca"` | Name of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.type | string | `"Secret"` | Type of object where the CA is stored | +| ocpExternalSecrets.caProvider.enabled | bool | `true` | When set to true this uses a custom CA to talk to vault | +| ocpExternalSecrets.caProvider.hostCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on the hub cluster | +| ocpExternalSecrets.caProvider.hostCluster.key | string | `"ca.crt"` | Key of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.name | string | `"kube-root-ca.crt"` | Name of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.type | string | `"ConfigMap"` | Type of object where the CA is stored | +| ocpExternalSecrets.kubernetes | object | depends on the individual settings | Settings relevant when using the kubernetes backend | +| ocpExternalSecrets.kubernetes.remoteNamespace | string | `"validated-patterns-secrets"` | The remote namespace used in the ClusterSecretStore | +| ocpExternalSecrets.kubernetes.server.url | string | `"https://kubernetes.default"` | The URL used in the ClusterSecretStore | +| ocpExternalSecrets.rbac.rolename | string | `"hub-role"` | The name of the vault role when connecting to the vault from the hub | +| ocpExternalSecrets.rbac.serviceAccount | object | depends on the individual settings | ServiceAccount configuration for external secrets | +| ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | +| ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | +| ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | +| ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, sets the Vault API URL on the ClusterSecretStore (`spec.provider.vault.server`), for example an external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). | +| ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | + +--- + Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/tests/edge_cases_test.yaml b/tests/edge_cases_test.yaml index aea519c..f9b054e 100644 --- a/tests/edge_cases_test.yaml +++ b/tests/edge_cases_test.yaml @@ -121,4 +121,4 @@ tests: asserts: - equal: path: apiVersion - value: external-secrets.io/v1 \ No newline at end of file + value: external-secrets.io/v1 diff --git a/tests/external_secrets_config_test.yaml b/tests/external_secrets_config_test.yaml index 1d55e58..4148fbf 100644 --- a/tests/external_secrets_config_test.yaml +++ b/tests/external_secrets_config_test.yaml @@ -33,4 +33,4 @@ tests: value: TCP - equal: path: spec.controllerConfig.networkPolicies[0].egress[0].ports[0].port - value: 443 \ No newline at end of file + value: 443 diff --git a/values.yaml b/values.yaml index 04a8edf..66fd70f 100644 --- a/values.yaml +++ b/values.yaml @@ -20,12 +20,14 @@ ocpExternalSecrets: remoteNamespace: "validated-patterns-secrets" server: # -- The URL used in the ClusterSecretStore - url: 'https://kubernetes.default' + url: "https://kubernetes.default" # -- Some vault configuration entries # @default -- depends on the individual settings vault: - # -- If non-empty, used as spec.parameters.vaultAddress (e.g. https://vault.example.com for an external Vault). Wh en empty, the default hub route https://vault-vault. is used. + # -- If non-empty, sets the Vault API URL on the ClusterSecretStore (`spec.provider.vault.server`), for example an + # external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern + # `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). externalAddress: "" # -- The vault secrets' path when connecting to it from the hub mountPath: "hub" From 805615f0ced766772e419bb3025e78192b05023d Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Tue, 5 May 2026 14:15:58 -0500 Subject: [PATCH 3/6] Add some more tunables --- README.md | 43 ++++++ README.md.gotmpl | 38 +++++ .../external-secrets-hub-secretstore.yaml | 27 +++- tests/external_secrets_secretstore_test.yaml | 144 ++++++++++++++++++ values.yaml | 13 ++ 5 files changed, 261 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa908b0..c61d62e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,44 @@ A Helm chart to set up the Openshift External Secrets Operator v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged vault +## Using a completely external Vault + +Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the hub (for example a shared corporate Vault or a cluster-external service). + +1. **ClusterSecretStore backend** – Keep `global.secretStore.backend` as `vault` (or omit it; the chart defaults to Vault). + +2. **Vault API URL** – Set `ocpExternalSecrets.vault.externalAddress` to the reachable HTTPS base URL of your Vault (same value you would put in `spec.provider.vault.server`), for example `https://vault.example.corp:8200`. When this is empty, the chart targets the framework hub route `vault-vault.` instead. + +3. **KV engine** – Optional. Under `ocpExternalSecrets.vault.external`, set `kvPath` and/or `kvVersion` if your mount is not the default path `secret` or not KV v2. These keys are **only** read when `externalAddress` is non-empty; otherwise they are ignored. + +4. **Kubernetes auth on the external Vault** – On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. + +5. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. + +Example fragment: + +```yaml +global: + secretStore: + backend: vault + +ocpExternalSecrets: + vault: + externalAddress: "https://vault.example.corp:8200" + external: + kvPath: "kv/my-team" + kvVersion: "v2" + kubernetesMountPath: "openshift/prod" + kubernetesRole: "external-secrets-prod" + caProvider: + enabled: true + hostCluster: + type: Secret + name: corp-vault-ca + key: ca.crt + namespace: external-secrets +``` + ## Values | Key | Type | Default | Description | @@ -38,6 +76,11 @@ v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged | ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | | ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | | ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | +| ocpExternalSecrets.vault.external | object | depends on the individual settings | Settings below apply only when `externalAddress` is non-empty (ignored for framework-managed hub Vault). | +| ocpExternalSecrets.vault.external.kubernetesMountPath | string | `""` | Vault Kubernetes auth mount path for the external Vault. Must be set together with `kubernetesRole`; if either is empty, hub/spoke auth from this chart is used instead. | +| ocpExternalSecrets.vault.external.kubernetesRole | string | `""` | Vault Kubernetes auth role for the external Vault. Must be set together with `kubernetesMountPath`. | +| ocpExternalSecrets.vault.external.kvPath | string | `""` | KV mount path segment for `spec.provider.vault.path` (e.g. `secret` or a team-specific engine). Empty keeps the default `secret`. | +| ocpExternalSecrets.vault.external.kvVersion | string | `""` | KV version (`v1` or `v2`). Empty keeps the default `v2`. | | ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, sets the Vault API URL on the ClusterSecretStore (`spec.provider.vault.server`), for example an external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). | | ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | diff --git a/README.md.gotmpl b/README.md.gotmpl index 032ebe9..0025dac 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -9,6 +9,44 @@ v0.0.4: Add vault.externalAddress to allow configuration of separate, unmanaged vault +## Using a completely external Vault + +Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the hub (for example a shared corporate Vault or a cluster-external service). + +1. **ClusterSecretStore backend** – Keep `global.secretStore.backend` as `vault` (or omit it; the chart defaults to Vault). + +2. **Vault API URL** – Set `ocpExternalSecrets.vault.externalAddress` to the reachable HTTPS base URL of your Vault (same value you would put in `spec.provider.vault.server`), for example `https://vault.example.corp:8200`. When this is empty, the chart targets the framework hub route `vault-vault.` instead. + +3. **KV engine** – Optional. Under `ocpExternalSecrets.vault.external`, set `kvPath` and/or `kvVersion` if your mount is not the default path `secret` or not KV v2. These keys are **only** read when `externalAddress` is non-empty; otherwise they are ignored. + +4. **Kubernetes auth on the external Vault** – On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. + +5. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. + +Example fragment: + +```yaml +global: + secretStore: + backend: vault + +ocpExternalSecrets: + vault: + externalAddress: "https://vault.example.corp:8200" + external: + kvPath: "kv/my-team" + kvVersion: "v2" + kubernetesMountPath: "openshift/prod" + kubernetesRole: "external-secrets-prod" + caProvider: + enabled: true + hostCluster: + type: Secret + name: corp-vault-ca + key: ca.crt + namespace: external-secrets +``` + {{ template "chart.homepageLine" . }} {{ template "chart.maintainersSection" . }} diff --git a/templates/vault/external-secrets-hub-secretstore.yaml b/templates/vault/external-secrets-hub-secretstore.yaml index 01fa3a7..609c7da 100644 --- a/templates/vault/external-secrets-hub-secretstore.yaml +++ b/templates/vault/external-secrets-hub-secretstore.yaml @@ -7,6 +7,23 @@ {{- end }} {{- end }} {{- end }} +{{- $extVault := .Values.ocpExternalSecrets.vault.externalAddress | default "" | trim }} +{{- $extCfg := .Values.ocpExternalSecrets.vault.external | default dict }} +{{- $vaultPath := "secret" }} +{{- $vaultVersion := "v2" }} +{{- if ne $extVault "" }} + {{- $p := $extCfg.kvPath | default "" | trim }} + {{- if ne $p "" }} + {{- $vaultPath = $p }} + {{- end }} + {{- $ver := $extCfg.kvVersion | default "" | trim }} + {{- if ne $ver "" }} + {{- $vaultVersion = $ver }} + {{- end }} +{{- end }} +{{- $extK8sMount := $extCfg.kubernetesMountPath | default "" | trim }} +{{- $extK8sRole := $extCfg.kubernetesRole | default "" | trim }} +{{- $useExtK8sAuth := and (ne $extVault "") (ne $extK8sMount "") (ne $extK8sRole "") }} --- apiVersion: external-secrets.io/v1 kind: ClusterSecretStore @@ -16,15 +33,14 @@ metadata: spec: provider: vault: -{{- $extVault := .Values.ocpExternalSecrets.vault.externalAddress | default "" | trim }} {{- if ne $extVault "" }} server: {{ $extVault | quote }} {{- else }} server: "https://vault-vault.{{ .Values.global.hubClusterDomain }}" {{- end }} - path: secret + path: {{ $vaultPath | quote }} # Version of KV backend - version: v2 + version: {{ $vaultVersion | quote }} {{- if .Values.ocpExternalSecrets.caProvider.enabled }} {{- if or (eq (include "ocp_eso.ishubcluster" .) "true") $hashicorp_vault_found }} caProvider: @@ -42,7 +58,10 @@ spec: {{- end }} auth: kubernetes: -{{- if or (eq (include "ocp_eso.ishubcluster" .) "true") $hashicorp_vault_found }} +{{- if $useExtK8sAuth }} + mountPath: {{ $extK8sMount | quote }} + role: {{ $extK8sRole | quote }} +{{- else if or (eq (include "ocp_eso.ishubcluster" .) "true") $hashicorp_vault_found }} mountPath: {{ .Values.ocpExternalSecrets.vault.mountPath }} role: {{ .Values.ocpExternalSecrets.rbac.rolename }} {{ else }} diff --git a/tests/external_secrets_secretstore_test.yaml b/tests/external_secrets_secretstore_test.yaml index 739c397..c95a115 100644 --- a/tests/external_secrets_secretstore_test.yaml +++ b/tests/external_secrets_secretstore_test.yaml @@ -72,6 +72,150 @@ tests: path: spec.provider.vault.server value: "https://vault-vault.my.hub.domain" + - it: should ignore vault.external when externalAddress is empty + set: + global: + hubClusterDomain: my.hub.domain + clusterGroup: + isHubCluster: true + ocpExternalSecrets: + rbac: + rolename: hub-role + vault: + externalAddress: "" + external: + kvPath: custom-kv + kvVersion: v1 + kubernetesMountPath: custom-mount + kubernetesRole: custom-role + mountPath: hub + asserts: + - equal: + path: spec.provider.vault.path + value: secret + - equal: + path: spec.provider.vault.version + value: v2 + - equal: + path: spec.provider.vault.auth.kubernetes.mountPath + value: hub + - equal: + path: spec.provider.vault.auth.kubernetes.role + value: hub-role + + - it: should apply vault.external KV settings when externalAddress is set + set: + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + kvPath: team-secrets + kvVersion: v1 + asserts: + - equal: + path: spec.provider.vault.server + value: "https://vault.external.example:8200" + - equal: + path: spec.provider.vault.path + value: team-secrets + - equal: + path: spec.provider.vault.version + value: v1 + + - it: should use default KV path and version for external vault when external.kvPath and kvVersion are empty + set: + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + kvPath: "" + kvVersion: "" + asserts: + - equal: + path: spec.provider.vault.path + value: secret + - equal: + path: spec.provider.vault.version + value: v2 + + - it: should use vault.external kubernetes auth when externalAddress and both mount and role are set + set: + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + kubernetesMountPath: ocp/kubernetes/prod + kubernetesRole: eso-external-role + asserts: + - equal: + path: spec.provider.vault.auth.kubernetes.mountPath + value: ocp/kubernetes/prod + - equal: + path: spec.provider.vault.auth.kubernetes.role + value: eso-external-role + + - it: should fall back to hub kubernetes auth when external vault omits external kubernetes mount or role + set: + clusterGroup: + isHubCluster: true + ocpExternalSecrets: + rbac: + rolename: hub-rolename + vault: + externalAddress: "https://vault.external.example:8200" + external: + kubernetesMountPath: "" + kubernetesRole: "" + mountPath: hub-mount + asserts: + - equal: + path: spec.provider.vault.auth.kubernetes.mountPath + value: hub-mount + - equal: + path: spec.provider.vault.auth.kubernetes.role + value: hub-rolename + + - it: should not use partial external kubernetes auth when only mountPath is set + set: + clusterGroup: + isHubCluster: true + ocpExternalSecrets: + rbac: + rolename: hub-rolename + vault: + externalAddress: "https://vault.external.example:8200" + external: + kubernetesMountPath: partial-mount + kubernetesRole: "" + mountPath: hub-mount + asserts: + - equal: + path: spec.provider.vault.auth.kubernetes.mountPath + value: hub-mount + - equal: + path: spec.provider.vault.auth.kubernetes.role + value: hub-rolename + + - it: should fall back to spoke kubernetes auth for external vault when external k8s auth is not set + set: + global: + clusterDomain: spoke.example.org + clusterGroup: + isHubCluster: false + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + kubernetesMountPath: "" + kubernetesRole: "" + asserts: + - equal: + path: spec.provider.vault.auth.kubernetes.mountPath + value: spoke.example.org + - equal: + path: spec.provider.vault.auth.kubernetes.role + value: spoke.example.org-role + - it: should set secretRef with default values asserts: - equal: diff --git a/values.yaml b/values.yaml index 66fd70f..7ebfb98 100644 --- a/values.yaml +++ b/values.yaml @@ -29,6 +29,19 @@ ocpExternalSecrets: # external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern # `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). externalAddress: "" + # -- Settings below apply only when `externalAddress` is non-empty (ignored for framework-managed hub Vault). + # @default -- depends on the individual settings + external: + # -- KV mount path segment for `spec.provider.vault.path` (e.g. `secret` or a team-specific engine). Empty keeps + # the default `secret`. + kvPath: "" + # -- KV version (`v1` or `v2`). Empty keeps the default `v2`. + kvVersion: "" + # -- Vault Kubernetes auth mount path for the external Vault. Must be set together with `kubernetesRole`; if + # either is empty, hub/spoke auth from this chart is used instead. + kubernetesMountPath: "" + # -- Vault Kubernetes auth role for the external Vault. Must be set together with `kubernetesMountPath`. + kubernetesRole: "" # -- The vault secrets' path when connecting to it from the hub mountPath: "hub" From 9a68157a7c27ebf113f6c340f7182d5b735d67d1 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Thu, 7 May 2026 10:22:36 -0500 Subject: [PATCH 4/6] Allow more flexibility with external vault --- README.md | 104 +++++++++++------- README.md.gotmpl | 24 +++- .../external-secrets-hub-secretstore.yaml | 17 +++ tests/external_secrets_secretstore_test.yaml | 84 ++++++++++++++ values.yaml | 15 +++ 5 files changed, 198 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index c61d62e..363f46f 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,18 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h 3. **KV engine** – Optional. Under `ocpExternalSecrets.vault.external`, set `kvPath` and/or `kvVersion` if your mount is not the default path `secret` or not KV v2. These keys are **only** read when `externalAddress` is non-empty; otherwise they are ignored. -4. **Kubernetes auth on the external Vault** – On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. +4. **Arbitrary external auth provider** – When `ocpExternalSecrets.vault.externalAddress` is non-empty, you can provide `ocpExternalSecrets.vault.external.auth` to inject any supported ESO Vault auth block directly into `spec.provider.vault.auth` (for example AppRole, token, JWT/OIDC, LDAP, cert). This is the recommended path when your external Vault does not use Kubernetes auth from this chart. -5. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. +5. **Kubernetes auth on the external Vault** – If you do not set `ocpExternalSecrets.vault.external.auth`, the chart uses Kubernetes auth. On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. + +6. **External Kubernetes auth token Secret reference** – When `ocpExternalSecrets.vault.externalAddress` is non-empty and you use the Kubernetes auth fallback path, you must set: + - `ocpExternalSecrets.vault.external.secretRef.name` + - `ocpExternalSecrets.vault.external.secretRef.namespace` + - `ocpExternalSecrets.vault.external.secretRef.key` + + These values are used directly in `spec.provider.vault.auth.kubernetes.secretRef` and should point to an existing Secret that contains the JWT token expected by your external Vault Kubernetes auth mount. They are ignored when `ocpExternalSecrets.vault.external.auth` is set. + +7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. Example fragment: @@ -35,8 +44,15 @@ ocpExternalSecrets: external: kvPath: "kv/my-team" kvVersion: "v2" - kubernetesMountPath: "openshift/prod" - kubernetesRole: "external-secrets-prod" + auth: + appRole: + path: "approle" + roleRef: + name: "vault-approle" + key: "role-id" + secretRef: + name: "vault-approle" + key: "secret-id" caProvider: enabled: true hostCluster: @@ -48,42 +64,46 @@ ocpExternalSecrets: ## Values -| Key | Type | Default | Description | -| ----------------------------------------------------- | ------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| clusterGroup.applications | object | `{}` | | -| global | object | depends on the individual settings | The global namespace containes some globally used variables used in patterns | -| global.clusterDomain | string | `"foo.example.com"` | The DNS entry for the cluster the chart is being rendered on | -| global.hubClusterDomain | string | `"hub.example.com"` | The DNS entry for the hub cluster | -| global.secretStore.backend | string | `"vault"` | The backend of ESO being used in the pattern | -| ocpExternalSecrets | object | depends on the individual settings | Dictionary of all the settings to configure this chart | -| ocpExternalSecrets.caProvider | object | depends on the individual settings | This controls how ESO connects to vault and it allows to specify where the public key of the CA that signed the API endpoint to talke to the vault | -| ocpExternalSecrets.caProvider.clientCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on a spoke cluster | -| ocpExternalSecrets.caProvider.clientCluster.key | string | `"hub-kube-root-ca.crt"` | Key of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.name | string | `"hub-ca"` | Name of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | -| ocpExternalSecrets.caProvider.clientCluster.type | string | `"Secret"` | Type of object where the CA is stored | -| ocpExternalSecrets.caProvider.enabled | bool | `true` | When set to true this uses a custom CA to talk to vault | -| ocpExternalSecrets.caProvider.hostCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on the hub cluster | -| ocpExternalSecrets.caProvider.hostCluster.key | string | `"ca.crt"` | Key of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.name | string | `"kube-root-ca.crt"` | Name of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | -| ocpExternalSecrets.caProvider.hostCluster.type | string | `"ConfigMap"` | Type of object where the CA is stored | -| ocpExternalSecrets.kubernetes | object | depends on the individual settings | Settings relevant when using the kubernetes backend | -| ocpExternalSecrets.kubernetes.remoteNamespace | string | `"validated-patterns-secrets"` | The remote namespace used in the ClusterSecretStore | -| ocpExternalSecrets.kubernetes.server.url | string | `"https://kubernetes.default"` | The URL used in the ClusterSecretStore | -| ocpExternalSecrets.rbac.rolename | string | `"hub-role"` | The name of the vault role when connecting to the vault from the hub | -| ocpExternalSecrets.rbac.serviceAccount | object | depends on the individual settings | ServiceAccount configuration for external secrets | -| ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | -| ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | -| ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | -| ocpExternalSecrets.vault.external | object | depends on the individual settings | Settings below apply only when `externalAddress` is non-empty (ignored for framework-managed hub Vault). | -| ocpExternalSecrets.vault.external.kubernetesMountPath | string | `""` | Vault Kubernetes auth mount path for the external Vault. Must be set together with `kubernetesRole`; if either is empty, hub/spoke auth from this chart is used instead. | -| ocpExternalSecrets.vault.external.kubernetesRole | string | `""` | Vault Kubernetes auth role for the external Vault. Must be set together with `kubernetesMountPath`. | -| ocpExternalSecrets.vault.external.kvPath | string | `""` | KV mount path segment for `spec.provider.vault.path` (e.g. `secret` or a team-specific engine). Empty keeps the default `secret`. | -| ocpExternalSecrets.vault.external.kvVersion | string | `""` | KV version (`v1` or `v2`). Empty keeps the default `v2`. | -| ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, sets the Vault API URL on the ClusterSecretStore (`spec.provider.vault.server`), for example an external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). | -| ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | - ---- - +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| clusterGroup.applications | object | `{}` | | +| global | object | depends on the individual settings | The global namespace containes some globally used variables used in patterns | +| global.clusterDomain | string | `"foo.example.com"` | The DNS entry for the cluster the chart is being rendered on | +| global.hubClusterDomain | string | `"hub.example.com"` | The DNS entry for the hub cluster | +| global.secretStore.backend | string | `"vault"` | The backend of ESO being used in the pattern | +| ocpExternalSecrets | object | depends on the individual settings | Dictionary of all the settings to configure this chart | +| ocpExternalSecrets.caProvider | object | depends on the individual settings | This controls how ESO connects to vault and it allows to specify where the public key of the CA that signed the API endpoint to talke to the vault | +| ocpExternalSecrets.caProvider.clientCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on a spoke cluster | +| ocpExternalSecrets.caProvider.clientCluster.key | string | `"hub-kube-root-ca.crt"` | Key of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.name | string | `"hub-ca"` | Name of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | +| ocpExternalSecrets.caProvider.clientCluster.type | string | `"Secret"` | Type of object where the CA is stored | +| ocpExternalSecrets.caProvider.enabled | bool | `true` | When set to true this uses a custom CA to talk to vault | +| ocpExternalSecrets.caProvider.hostCluster | object | depends on the individual settings | Where to fetch the CA that signed the vault API endpoint when on the hub cluster | +| ocpExternalSecrets.caProvider.hostCluster.key | string | `"ca.crt"` | Key of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.name | string | `"kube-root-ca.crt"` | Name of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.namespace | string | `"external-secrets"` | Namespace of object where the CA is stored | +| ocpExternalSecrets.caProvider.hostCluster.type | string | `"ConfigMap"` | Type of object where the CA is stored | +| ocpExternalSecrets.kubernetes | object | depends on the individual settings | Settings relevant when using the kubernetes backend | +| ocpExternalSecrets.kubernetes.remoteNamespace | string | `"validated-patterns-secrets"` | The remote namespace used in the ClusterSecretStore | +| ocpExternalSecrets.kubernetes.server.url | string | `"https://kubernetes.default"` | The URL used in the ClusterSecretStore | +| ocpExternalSecrets.rbac.rolename | string | `"hub-role"` | The name of the vault role when connecting to the vault from the hub | +| ocpExternalSecrets.rbac.serviceAccount | object | depends on the individual settings | ServiceAccount configuration for external secrets | +| ocpExternalSecrets.rbac.serviceAccount.name | string | `"ocp-external-secrets"` | The name of the service account used by external secrets | +| ocpExternalSecrets.rbac.serviceAccount.namespace | string | `"external-secrets"` | The namespace where the service account is created | +| ocpExternalSecrets.vault | object | depends on the individual settings | Some vault configuration entries | +| ocpExternalSecrets.vault.external | object | depends on the individual settings | Settings below apply only when `externalAddress` is non-empty (ignored for framework-managed hub Vault). | +| ocpExternalSecrets.vault.external.auth | object | `{}` | Arbitrary auth stanza rendered directly into `spec.provider.vault.auth` when `externalAddress` is non-empty. Use this for non-Kubernetes auth methods (for example AppRole, JWT/OIDC, token, LDAP, cert). When set, this takes precedence over `kubernetesMountPath`/`kubernetesRole` and `secretRef`. | +| ocpExternalSecrets.vault.external.kubernetesMountPath | string | `""` | Vault Kubernetes auth mount path for the external Vault. Must be set together with `kubernetesRole`; if either is empty, hub/spoke auth from this chart is used instead. | +| ocpExternalSecrets.vault.external.kubernetesRole | string | `""` | Vault Kubernetes auth role for the external Vault. Must be set together with `kubernetesMountPath`. | +| ocpExternalSecrets.vault.external.kvPath | string | `""` | KV mount path segment for `spec.provider.vault.path` (e.g. `secret` or a team-specific engine). Empty keeps the default `secret`. | +| ocpExternalSecrets.vault.external.kvVersion | string | `""` | KV version (`v1` or `v2`). Empty keeps the default `v2`. | +| ocpExternalSecrets.vault.external.secretRef | object | depends on the individual settings | Secret reference used for `spec.provider.vault.auth.kubernetes.secretRef` when `externalAddress` is non-empty. All fields are required in that case and should point to an existing Secret holding the Kubernetes auth JWT. Ignored when `external.auth` is provided. | +| ocpExternalSecrets.vault.external.secretRef.key | string | `""` | Secret key containing the JWT token for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. | +| ocpExternalSecrets.vault.external.secretRef.name | string | `""` | Secret name for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. | +| ocpExternalSecrets.vault.external.secretRef.namespace | string | `""` | Namespace of the secret for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. | +| ocpExternalSecrets.vault.externalAddress | string | `""` | If non-empty, sets the Vault API URL on the ClusterSecretStore (`spec.provider.vault.server`), for example an external Vault reachable at an HTTPS URL you provide. When empty, the chart uses the in-cluster hub pattern `vault-vault` plus `global.hubClusterDomain` (no separate parameter required). | +| ocpExternalSecrets.vault.mountPath | string | `"hub"` | The vault secrets' path when connecting to it from the hub | + +---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/README.md.gotmpl b/README.md.gotmpl index 0025dac..ac6bb1f 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -19,9 +19,18 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h 3. **KV engine** – Optional. Under `ocpExternalSecrets.vault.external`, set `kvPath` and/or `kvVersion` if your mount is not the default path `secret` or not KV v2. These keys are **only** read when `externalAddress` is non-empty; otherwise they are ignored. -4. **Kubernetes auth on the external Vault** – On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. +4. **Arbitrary external auth provider** – When `ocpExternalSecrets.vault.externalAddress` is non-empty, you can provide `ocpExternalSecrets.vault.external.auth` to inject any supported ESO Vault auth block directly into `spec.provider.vault.auth` (for example AppRole, token, JWT/OIDC, LDAP, cert). This is the recommended path when your external Vault does not use Kubernetes auth from this chart. -5. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. +5. **Kubernetes auth on the external Vault** – If you do not set `ocpExternalSecrets.vault.external.auth`, the chart uses Kubernetes auth. On the Vault side, configure a Kubernetes auth mount and role that trust the External Secrets Operator service account (`ocpExternalSecrets.rbac.serviceAccount` in this chart). In values, you can pin the store to that Vault configuration by setting **both** `ocpExternalSecrets.vault.external.kubernetesMountPath` and `ocpExternalSecrets.vault.external.kubernetesRole`. If either is left empty, the chart falls back to the usual hub/spoke auth fields (`vault.mountPath`, `rbac.rolename`, or spoke `global.clusterDomain`), which may not match your external Vault and should be overridden for a fully external setup. + +6. **External Kubernetes auth token Secret reference** – When `ocpExternalSecrets.vault.externalAddress` is non-empty and you use the Kubernetes auth fallback path, you must set: + - `ocpExternalSecrets.vault.external.secretRef.name` + - `ocpExternalSecrets.vault.external.secretRef.namespace` + - `ocpExternalSecrets.vault.external.secretRef.key` + + These values are used directly in `spec.provider.vault.auth.kubernetes.secretRef` and should point to an existing Secret that contains the JWT token expected by your external Vault Kubernetes auth mount. They are ignored when `ocpExternalSecrets.vault.external.auth` is set. + +7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. Example fragment: @@ -36,8 +45,15 @@ ocpExternalSecrets: external: kvPath: "kv/my-team" kvVersion: "v2" - kubernetesMountPath: "openshift/prod" - kubernetesRole: "external-secrets-prod" + auth: + appRole: + path: "approle" + roleRef: + name: "vault-approle" + key: "role-id" + secretRef: + name: "vault-approle" + key: "secret-id" caProvider: enabled: true hostCluster: diff --git a/templates/vault/external-secrets-hub-secretstore.yaml b/templates/vault/external-secrets-hub-secretstore.yaml index 609c7da..c3ff5a9 100644 --- a/templates/vault/external-secrets-hub-secretstore.yaml +++ b/templates/vault/external-secrets-hub-secretstore.yaml @@ -9,6 +9,7 @@ {{- end }} {{- $extVault := .Values.ocpExternalSecrets.vault.externalAddress | default "" | trim }} {{- $extCfg := .Values.ocpExternalSecrets.vault.external | default dict }} +{{- $extAuth := $extCfg.auth | default dict }} {{- $vaultPath := "secret" }} {{- $vaultVersion := "v2" }} {{- if ne $extVault "" }} @@ -24,6 +25,11 @@ {{- $extK8sMount := $extCfg.kubernetesMountPath | default "" | trim }} {{- $extK8sRole := $extCfg.kubernetesRole | default "" | trim }} {{- $useExtK8sAuth := and (ne $extVault "") (ne $extK8sMount "") (ne $extK8sRole "") }} +{{- $useExtAuth := and (ne $extVault "") (not (empty $extAuth)) }} +{{- $extSecretRef := $extCfg.secretRef | default dict }} +{{- $extSecretRefName := $extSecretRef.name | default "" | trim }} +{{- $extSecretRefNamespace := $extSecretRef.namespace | default "" | trim }} +{{- $extSecretRefKey := $extSecretRef.key | default "" | trim }} --- apiVersion: external-secrets.io/v1 kind: ClusterSecretStore @@ -56,6 +62,10 @@ spec: namespace: {{ .Values.ocpExternalSecrets.caProvider.clientCluster.namespace }} {{ end }} {{- end }} +{{- if $useExtAuth }} + auth: +{{ toYaml $extAuth | nindent 8 }} +{{- else }} auth: kubernetes: {{- if $useExtK8sAuth }} @@ -69,7 +79,14 @@ spec: role: {{ $.Values.global.clusterDomain }}-role {{ end }} secretRef: +{{- if and (ne $extVault "") (not $useExtAuth) }} + name: {{ required "ocpExternalSecrets.vault.external.secretRef.name must be set when ocpExternalSecrets.vault.externalAddress is non-empty" $extSecretRefName | quote }} + namespace: {{ required "ocpExternalSecrets.vault.external.secretRef.namespace must be set when ocpExternalSecrets.vault.externalAddress is non-empty" $extSecretRefNamespace | quote }} + key: {{ required "ocpExternalSecrets.vault.external.secretRef.key must be set when ocpExternalSecrets.vault.externalAddress is non-empty" $extSecretRefKey | quote }} +{{- else }} name: {{ .Values.ocpExternalSecrets.rbac.serviceAccount.name }} namespace: {{ .Values.ocpExternalSecrets.rbac.serviceAccount.namespace }} key: "token" {{- end }} +{{- end }} +{{- end }} diff --git a/tests/external_secrets_secretstore_test.yaml b/tests/external_secrets_secretstore_test.yaml index c95a115..34a3ac2 100644 --- a/tests/external_secrets_secretstore_test.yaml +++ b/tests/external_secrets_secretstore_test.yaml @@ -55,6 +55,11 @@ tests: ocpExternalSecrets: vault: externalAddress: "https://vault.external.example:8200" + external: + secretRef: + name: external-auth + namespace: external-secrets + key: token asserts: - equal: path: spec.provider.vault.server @@ -111,6 +116,10 @@ tests: external: kvPath: team-secrets kvVersion: v1 + secretRef: + name: external-auth + namespace: external-secrets + key: token asserts: - equal: path: spec.provider.vault.server @@ -130,6 +139,10 @@ tests: external: kvPath: "" kvVersion: "" + secretRef: + name: external-auth + namespace: external-secrets + key: token asserts: - equal: path: spec.provider.vault.path @@ -146,6 +159,10 @@ tests: external: kubernetesMountPath: ocp/kubernetes/prod kubernetesRole: eso-external-role + secretRef: + name: external-auth + namespace: external-secrets + key: token asserts: - equal: path: spec.provider.vault.auth.kubernetes.mountPath @@ -166,6 +183,10 @@ tests: external: kubernetesMountPath: "" kubernetesRole: "" + secretRef: + name: external-auth + namespace: external-secrets + key: token mountPath: hub-mount asserts: - equal: @@ -187,6 +208,10 @@ tests: external: kubernetesMountPath: partial-mount kubernetesRole: "" + secretRef: + name: external-auth + namespace: external-secrets + key: token mountPath: hub-mount asserts: - equal: @@ -208,6 +233,10 @@ tests: external: kubernetesMountPath: "" kubernetesRole: "" + secretRef: + name: external-auth + namespace: external-secrets + key: token asserts: - equal: path: spec.provider.vault.auth.kubernetes.mountPath @@ -216,6 +245,61 @@ tests: path: spec.provider.vault.auth.kubernetes.role value: spoke.example.org-role + - it: should use external secretRef when externalAddress is set + set: + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + secretRef: + name: external-auth + namespace: external-secrets + key: vault-token + asserts: + - equal: + path: spec.provider.vault.auth.kubernetes.secretRef.name + value: external-auth + - equal: + path: spec.provider.vault.auth.kubernetes.secretRef.namespace + value: external-secrets + - equal: + path: spec.provider.vault.auth.kubernetes.secretRef.key + value: vault-token + + - it: should use arbitrary external auth provider settings when externalAddress is set + set: + ocpExternalSecrets: + vault: + externalAddress: "https://vault.external.example:8200" + external: + auth: + appRole: + path: approle + roleRef: + name: vault-approle + key: role-id + secretRef: + name: vault-approle + key: secret-id + asserts: + - equal: + path: spec.provider.vault.auth.appRole.path + value: approle + - equal: + path: spec.provider.vault.auth.appRole.roleRef.name + value: vault-approle + - equal: + path: spec.provider.vault.auth.appRole.roleRef.key + value: role-id + - equal: + path: spec.provider.vault.auth.appRole.secretRef.name + value: vault-approle + - equal: + path: spec.provider.vault.auth.appRole.secretRef.key + value: secret-id + - notExists: + path: spec.provider.vault.auth.kubernetes + - it: should set secretRef with default values asserts: - equal: diff --git a/values.yaml b/values.yaml index 7ebfb98..efbca7c 100644 --- a/values.yaml +++ b/values.yaml @@ -32,6 +32,10 @@ ocpExternalSecrets: # -- Settings below apply only when `externalAddress` is non-empty (ignored for framework-managed hub Vault). # @default -- depends on the individual settings external: + # -- Arbitrary auth stanza rendered directly into `spec.provider.vault.auth` when `externalAddress` is non-empty. + # Use this for non-Kubernetes auth methods (for example AppRole, JWT/OIDC, token, LDAP, cert). When set, this + # takes precedence over `kubernetesMountPath`/`kubernetesRole` and `secretRef`. + auth: {} # -- KV mount path segment for `spec.provider.vault.path` (e.g. `secret` or a team-specific engine). Empty keeps # the default `secret`. kvPath: "" @@ -42,6 +46,17 @@ ocpExternalSecrets: kubernetesMountPath: "" # -- Vault Kubernetes auth role for the external Vault. Must be set together with `kubernetesMountPath`. kubernetesRole: "" + # -- Secret reference used for `spec.provider.vault.auth.kubernetes.secretRef` when `externalAddress` is non-empty. + # All fields are required in that case and should point to an existing Secret holding the Kubernetes auth JWT. + # Ignored when `external.auth` is provided. + # @default -- depends on the individual settings + secretRef: + # -- Secret name for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. + name: "" + # -- Namespace of the secret for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. + namespace: "" + # -- Secret key containing the JWT token for external Vault Kubernetes auth. Required when `externalAddress` is non-empty. + key: "" # -- The vault secrets' path when connecting to it from the hub mountPath: "hub" From 2d06bd2053fe1efac4f9eb79cbcba0de87f30b58 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Thu, 7 May 2026 10:30:59 -0500 Subject: [PATCH 5/6] Fix linter markdown-prettier error --- README.md | 4 +++- README.md.gotmpl | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 363f46f..5a24043 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h - `ocpExternalSecrets.vault.external.secretRef.name` - `ocpExternalSecrets.vault.external.secretRef.namespace` - `ocpExternalSecrets.vault.external.secretRef.key` - + These values are used directly in `spec.provider.vault.auth.kubernetes.secretRef` and should point to an existing Secret that contains the JWT token expected by your external Vault Kubernetes auth mount. They are ignored when `ocpExternalSecrets.vault.external.auth` is set. 7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. @@ -62,6 +62,7 @@ ocpExternalSecrets: namespace: external-secrets ``` + ## Values | Key | Type | Default | Description | @@ -107,3 +108,4 @@ ocpExternalSecrets: ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) + diff --git a/README.md.gotmpl b/README.md.gotmpl index ac6bb1f..7b4ea97 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -27,7 +27,7 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h - `ocpExternalSecrets.vault.external.secretRef.name` - `ocpExternalSecrets.vault.external.secretRef.namespace` - `ocpExternalSecrets.vault.external.secretRef.key` - + These values are used directly in `spec.provider.vault.auth.kubernetes.secretRef` and should point to an existing Secret that contains the JWT token expected by your external Vault Kubernetes auth mount. They are ignored when `ocpExternalSecrets.vault.external.auth` is set. 7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. @@ -71,6 +71,8 @@ ocpExternalSecrets: {{ template "chart.requirementsSection" . }} + {{ template "chart.valuesSection" . }} {{ template "helm-docs.versionFooter" . }} + From e0deffb463de0d98a30bfbdc6ed9f968a37d13d1 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Thu, 14 May 2026 11:46:53 -0500 Subject: [PATCH 6/6] Add note about disabling secrets loader when using external vault with external secrets --- README.md | 2 ++ README.md.gotmpl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 5a24043..445ce82 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h 7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. +8. **Special Note** – The patterns framework will be unable to manage authentication, policy or inject secrets into a vault that it does not manage. In such cases, set `global.secretLoader.disabled` to `true` (in `values-global.yaml`) to prevent the secret loader from running locally during the `make install` phase. + Example fragment: ```yaml diff --git a/README.md.gotmpl b/README.md.gotmpl index 7b4ea97..957211b 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -32,6 +32,8 @@ Use this when HashiCorp Vault is **not** deployed by Validated Patterns on the h 7. **TLS / CA** – If Vault presents a certificate signed by a CA that is not the cluster default, keep `ocpExternalSecrets.caProvider.enabled` true and point `hostCluster` or `clientCluster` at a ConfigMap or Secret that holds the PEM for that CA, depending on whether you render this chart on the hub or a spoke. +8. **Special Note** – The patterns framework will be unable to manage authentication, policy or inject secrets into a vault that it does not manage. In such cases, set `global.secretLoader.disabled` to `true` (in `values-global.yaml`) to prevent the secret loader from running locally during the `make install` phase. + Example fragment: ```yaml