Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .github/linters/.markdown-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: true
MD013:
line_length: 800
tables: false
17 changes: 17 additions & 0 deletions .github/linters/.yaml-lint.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
templates/
2 changes: 1 addition & 1 deletion Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
75 changes: 73 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,75 @@
# 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

## 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.<global.hubClusterDomain>` 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. **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. **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.

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
global:
secretStore:
backend: vault

ocpExternalSecrets:
vault:
externalAddress: "https://vault.example.corp:8200"
external:
kvPath: "kv/my-team"
kvVersion: "v2"
auth:
appRole:
path: "approle"
roleRef:
name: "vault-approle"
key: "role-id"
secretRef:
name: "vault-approle"
key: "secret-id"
caProvider:
enabled: true
hostCluster:
type: Secret
name: corp-vault-ca
key: ca.crt
namespace: external-secrets
```

<!-- prettier-ignore-start -->
## 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 |
Expand All @@ -36,7 +95,19 @@ 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.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)
<!-- prettier-ignore-end -->
60 changes: 60 additions & 0 deletions README.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,64 @@

## Notable changes

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.<global.hubClusterDomain>` 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. **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. **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.

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
global:
secretStore:
backend: vault

ocpExternalSecrets:
vault:
externalAddress: "https://vault.example.corp:8200"
external:
kvPath: "kv/my-team"
kvVersion: "v2"
auth:
appRole:
path: "approle"
roleRef:
name: "vault-approle"
key: "role-id"
secretRef:
name: "vault-approle"
key: "secret-id"
caProvider:
enabled: true
hostCluster:
type: Secret
name: corp-vault-ca
key: ca.crt
namespace: external-secrets
```

{{ template "chart.homepageLine" . }}

{{ template "chart.maintainersSection" . }}
Expand All @@ -15,6 +73,8 @@

{{ template "chart.requirementsSection" . }}

<!-- prettier-ignore-start -->
{{ template "chart.valuesSection" . }}

{{ template "helm-docs.versionFooter" . }}
<!-- prettier-ignore-end -->
49 changes: 45 additions & 4 deletions templates/vault/external-secrets-hub-secretstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@
{{- end }}
{{- end }}
{{- 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 "" }}
{{- $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 "") }}
{{- $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
Expand All @@ -16,10 +39,14 @@ metadata:
spec:
provider:
vault:
server: https://vault-vault.{{ .Values.global.hubClusterDomain }}
path: secret
{{- if ne $extVault "" }}
server: {{ $extVault | quote }}
{{- else }}
server: "https://vault-vault.{{ .Values.global.hubClusterDomain }}"
{{- end }}
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:
Expand All @@ -35,17 +62,31 @@ spec:
namespace: {{ .Values.ocpExternalSecrets.caProvider.clientCluster.namespace }}
{{ end }}
{{- end }}
{{- if $useExtAuth }}
auth:
{{ toYaml $extAuth | nindent 8 }}
{{- else }}
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 }}
mountPath: {{ $.Values.global.clusterDomain }}
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 }}
2 changes: 1 addition & 1 deletion tests/edge_cases_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,4 @@ tests:
asserts:
- equal:
path: apiVersion
value: external-secrets.io/v1
value: external-secrets.io/v1
2 changes: 1 addition & 1 deletion tests/external_secrets_config_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ tests:
value: TCP
- equal:
path: spec.controllerConfig.networkPolicies[0].egress[0].ports[0].port
value: 443
value: 443
Loading
Loading