From c0ff8684ce1bd0d170eba58432b750625716081b Mon Sep 17 00:00:00 2001 From: astro-stan <36302090+astro-stan@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:04:39 +0100 Subject: [PATCH] feat(common): Add support for secretRefs when defining custom CA for use with S3 credentials (#40385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description** This PR builds up on #40000 by adding an additional key - `credentials.$name.customCASecretRef`. This allows referencing secrets via the already familiar `configMapRef`/`secretRef` pattern, using the `name` and `expandObjectName` keys to reference secrets defined under `.Values.secret`. Additionally, it also adds a `credentials.$name.customCASecretRef.key` forcing users to specify the key in the secret which contains the CA, for maximum flexibility. ⚒️ Fixes # **⚙️ Type of change** - [X] ⚙️ Feature/App addition - [ ] 🪛 Bugfix - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] 🔃 Refactor of current code - [X] 📜 Documentation Changes **🧪 How Has This Been Tested?** I tested rendering the same CA secret using both the current `customCA` implementation and the new `.secret.$name` + `customCASecretRef` way. Also added unit tests for CI. **📃 Notes:** Regarding CNPG support - the changes in this PR and from #40000 should also work for CNPG. As far as i can tell it requires the CA secret to be referenced under a key called `endpointCA` in the following way ([docs](https://cloudnative-pg.io/documentation/1.16/api_reference/#backupstatus), [CRD def](https://github.com/cloudnative-pg/cloudnative-pg/blob/6ae2fb61bee4f959545bceeacfc5a209b2358668/config/crd/bases/postgresql.cnpg.io_backups.yaml#L256)): ```yaml ... barmanObjectStore: endpointURL: ... ... endpointCA: name: secret-name key: secret-key ``` Note that while the current [in-tree barman cloud support is being deprecated in favour of a barman cloud plugin](https://cloudnative-pg.io/releases/cloudnative-pg-1-26.0-released/#barman-cloud-deprecation-begins), the [migration guide](https://cloudnative-pg.io/plugin-barman-cloud/docs/migration/) says the new `ObjectStore` CRD has a **direct mapping** between it and the legacy `barmanObjectStore`. Therefore, the example above can be translated to: ```yaml apiVersion: barmancloud.cnpg.io/v1 kind: ObjectStore ... spec: configuration: endpointURL: ... ... endpointCA: name: secret-name key: secret-key ``` **✔️ Checklist:** - [X] ⚖️ My code follows the style guidelines of this project - [X] 👀 I have performed a self-review of my own code - [X] #️⃣ I have commented my code, particularly in hard-to-understand areas - [X] 📄 I have made changes to the documentation - [X] 🧪 I have added tests to this description that prove my fix is effective or that my feature works - [X] ⬆️ I increased versions for any altered app according to semantic versioning - [X] I made sure the title starts with `feat(chart-name):`, `fix(chart-name):`, `chore(chart-name):`, `docs(chart-name):` or `fix(docs):` **➕ App addition** If this PR is an app addition please make sure you have done the following. - [ ] 🖼️ I have added an icon in the Chart's root directory called `icon.png` --- _Please don't blindly check all the boxes. Read them and only check those that apply. Those checkboxes are there for the reviewer to see what is this all about and the status of this PR with a quick glance._ --------- Signed-off-by: Kjeld Schouten Co-authored-by: Kjeld Schouten --- .../volsync/replication_dest_spec_test.yaml | 82 ++++++++++++++ .../volsync/replication_src_spec_test.yaml | 93 ++++++++++++++++ charts/library/common/Chart.yaml | 2 +- .../class/volsync/_replicationDestination.tpl | 4 +- .../class/volsync/_replicationSource.tpl | 4 +- .../templates/lib/credentials/_validation.tpl | 41 +++++++ .../library/common/templates/spawner/_pvc.tpl | 27 ++++- charts/library/common/values.yaml | 5 + .../src/content/docs/common/credentials.md | 105 ++++++++++++++++++ 9 files changed, 354 insertions(+), 9 deletions(-) diff --git a/charts/library/common-test/tests/volsync/replication_dest_spec_test.yaml b/charts/library/common-test/tests/volsync/replication_dest_spec_test.yaml index 3391e3c7184..787cfafa755 100644 --- a/charts/library/common-test/tests/volsync/replication_dest_spec_test.yaml +++ b/charts/library/common-test/tests/volsync/replication_dest_spec_test.yaml @@ -425,3 +425,85 @@ tests: fsGroup: 568 runAsUser: 568 runAsGroup: 568 + + - it: should generate correct spec with customCASecretRef + set: + secret: + mys3-ca-secret: + enabled: true + data: + ca.crt: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + persistence: + destbackup: + enabled: true + type: pvc + mountPath: /backedup + volsync: + - name: mybackup1 + type: restic + credentials: mys3 + dest: + enabled: true + src: + enabled: false + credentials: + mys3: + type: s3 + url: https://s3.some-url + bucket: some-bucket + encrKey: some-key + accessKey: some-access-key + secretKey: some-secret-key + customCASecretRef: + name: mys3-ca-secret + key: ca.crt + asserts: + - documentIndex: *secretDoc + isKind: + of: Secret + - documentIndex: *secretDoc + isAPIVersion: + of: v1 + - documentIndex: *secretDoc + equal: + path: metadata.name + value: test-release-name-common-test-mys3-ca-secret + - documentIndex: *secretDoc + equal: + path: stringData + value: + ca.crt: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + - documentIndex: *replicationCaDestDoc + isKind: + of: ReplicationDestination + - documentIndex: *replicationCaDestDoc + isAPIVersion: + of: volsync.backube/v1alpha1 + - documentIndex: *replicationCaDestDoc + equal: + path: spec + value: + trigger: + manual: restore-once + restic: + repository: test-release-name-common-test-destbackup-volsync-mybackup1 + customCA: + secretName: test-release-name-common-test-mys3-ca-secret + key: ca.crt + copyMethod: Snapshot + cleanupTempPVC: false + cleanupCachePVC: false + cacheCapacity: 10Gi + capacity: 100Gi + accessModes: + - ReadWriteOnce + moverSecurityContext: + fsGroup: 568 + runAsUser: 568 + runAsGroup: 568 diff --git a/charts/library/common-test/tests/volsync/replication_src_spec_test.yaml b/charts/library/common-test/tests/volsync/replication_src_spec_test.yaml index 63b2a185b29..7d2f8452853 100644 --- a/charts/library/common-test/tests/volsync/replication_src_spec_test.yaml +++ b/charts/library/common-test/tests/volsync/replication_src_spec_test.yaml @@ -425,3 +425,96 @@ tests: matchRegex: path: spec.restic.unlock pattern: "^[0-9]{14}$" + + - it: should generate correct spec with customCASecretRef + set: + secret: + mys3-ca-secret: + enabled: true + data: + ca.crt: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + persistence: + srcbackup: + enabled: true + type: pvc + mountPath: /backedup + volsync: + - name: mybackup1 + type: restic + credentials: mys3 + dest: + enabled: false + src: + enabled: true + credentials: + mys3: + type: s3 + url: http://some-url + bucket: some-bucket + encrKey: some-key + accessKey: some-access-key + secretKey: some-secret-key + customCASecretRef: + name: mys3-ca-secret + key: ca.crt + asserts: + - documentIndex: *secretDoc + isKind: + of: Secret + - documentIndex: *secretDoc + isAPIVersion: + of: v1 + - documentIndex: *secretDoc + equal: + path: metadata.name + value: test-release-name-common-test-mys3-ca-secret + - documentIndex: *secretDoc + equal: + path: stringData + value: + ca.crt: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + - documentIndex: *replicationCaDestDoc + isKind: + of: ReplicationSource + - documentIndex: *replicationCaDestDoc + isAPIVersion: + of: volsync.backube/v1alpha1 + - documentIndex: *replicationCaDestDoc + isSubset: + path: spec + content: + sourcePVC: test-release-name-common-test-srcbackup + trigger: + schedule: "0 0 * * *" + - documentIndex: *replicationCaDestDoc + isSubset: + path: spec.restic + content: + repository: test-release-name-common-test-srcbackup-volsync-mybackup1 + customCA: + secretName: test-release-name-common-test-mys3-ca-secret + key: ca.crt + copyMethod: Snapshot + pruneIntervalDays: 7 + retain: + hourly: 6 + daily: 5 + weekly: 4 + monthly: 3 + yearly: 1 + accessModes: + - ReadWriteOnce + moverSecurityContext: + fsGroup: 568 + runAsUser: 568 + runAsGroup: 568 + - documentIndex: *replicationCaDestDoc + matchRegex: + path: spec.restic.unlock + pattern: "^[0-9]{14}$" diff --git a/charts/library/common/Chart.yaml b/charts/library/common/Chart.yaml index f4b8d952055..0d339cffa14 100644 --- a/charts/library/common/Chart.yaml +++ b/charts/library/common/Chart.yaml @@ -50,4 +50,4 @@ sources: - https://hub.docker.com/_/ - https://hub.docker.com/r/mikefarah/yq type: library -version: 28.22.0 +version: 28.23.0 diff --git a/charts/library/common/templates/class/volsync/_replicationDestination.tpl b/charts/library/common/templates/class/volsync/_replicationDestination.tpl index 70ffd895a4c..831c4b00d88 100644 --- a/charts/library/common/templates/class/volsync/_replicationDestination.tpl +++ b/charts/library/common/templates/class/volsync/_replicationDestination.tpl @@ -57,8 +57,8 @@ spec: repository: {{ $volsyncData.repository }} {{- if $volsyncData.customCA }} customCA: - secretName: {{ $volsyncData.customCA }} - key: ca.crt + secretName: {{ $volsyncData.customCA.name }} + key: {{ $volsyncData.customCA.key }} {{- end }} copyMethod: {{ $copyMethod }} capacity: {{ $capacity }} diff --git a/charts/library/common/templates/class/volsync/_replicationSource.tpl b/charts/library/common/templates/class/volsync/_replicationSource.tpl index 0403d21f21b..dd98fcb8392 100644 --- a/charts/library/common/templates/class/volsync/_replicationSource.tpl +++ b/charts/library/common/templates/class/volsync/_replicationSource.tpl @@ -55,8 +55,8 @@ spec: repository: {{ $volsyncData.repository }} {{- if $volsyncData.customCA }} customCA: - secretName: {{ $volsyncData.customCA }} - key: ca.crt + secretName: {{ $volsyncData.customCA.name }} + key: {{ $volsyncData.customCA.key }} {{- end }} copyMethod: {{ $volsyncData.copyMethod | default "Snapshot" }} pruneIntervalDays: {{ $volsyncData.src.pruneIntervalDays | default 7 }} diff --git a/charts/library/common/templates/lib/credentials/_validation.tpl b/charts/library/common/templates/lib/credentials/_validation.tpl index 18ae90335a6..7b3a6787ba8 100644 --- a/charts/library/common/templates/lib/credentials/_validation.tpl +++ b/charts/library/common/templates/lib/credentials/_validation.tpl @@ -28,4 +28,45 @@ {{- fail (printf "%s - Expected [url] in [credentials.%s] to start with [http://] or [https://]. It was observed that sometimes can cause issues if it does not. Got [%s]" $caller $credName $url) -}} {{- end -}} + {{- $customCA := get $credentials "customCA" -}} + {{- $customCASecretRef := get $credentials "customCASecretRef" -}} + + {{- if and $customCA $customCASecretRef -}} + {{- fail (printf "%s - Both [customCA] and [customCASecretRef] defined in [credentials.%s]. Choose one" $caller $credName) -}} + {{- end -}} + + {{- if and $customCA (not (kindIs "string" $customCA )) -}} + {{- fail (printf "%s - Expected [customCA] in [credentials.%s] to be a string. Got [%s]" $caller $credName (kindOf $customCA)) -}} + {{- end -}} + + {{- if $customCASecretRef -}} + {{- if not (kindIs "map" $customCASecretRef) -}} + {{- fail (printf "%s - Expected [credentials.%s.customCASecretRef] to be a dictionary. Got [%s]" $caller $credName (kindOf $customCASecretRef)) -}} + {{- end -}} + {{- if not $customCASecretRef.name -}} + {{- fail (printf "%s - Expected [name] key in [credentials.%s.customCASecretRef] to exist" $caller $credName) -}} + {{- else if not (kindIs "string" $customCASecretRef.name) -}} + {{- fail (printf "%s - Expected [name] in [credentials.%s.customCASecretRef] to be a string. Got [%s]" $caller $credName (kindOf $customCASecretRef.name)) -}} + {{- end -}} + {{- if not $customCASecretRef.key -}} + {{- fail (printf "%s - Expected [key] key in [credentials.%s.customCASecretRef] to exist" $caller $credName) -}} + {{- else if not (kindIs "string" $customCASecretRef.key) -}} + {{- fail (printf "%s - Expected [key] in [credentials.%s.customCASecretRef] to be a string. Got [%s]" $caller $credName (kindOf $customCASecretRef.key)) -}} + {{- end -}} + + {{- $expandName := (include "tc.v1.common.lib.util.expandName" (dict + "rootCtx" $rootCtx "objectData" $customCASecretRef + "name" $customCASecretRef.name "caller" $caller + "key" (printf "credentials.%s.customCASecretRef.name" $credName))) -}} + {{- if eq $expandName "true" -}} + {{- $object := (get $rootCtx.Values.secret $customCASecretRef.name) | default dict -}} + {{- if not $object -}} + {{- fail (printf "%s - Expected Secret [%s] referenced in [credentials.%s.customCASecretRef.name] to exist" $caller $customCASecretRef.name $credName) -}} + {{- end -}} + {{- if not (hasKey $object.data $customCASecretRef.key) -}} + {{- fail (printf "%s - Expected Secret [%s] referenced in [credentials.%s.customCASecretRef.name] to contain key [%s]" $caller $customCASecretRef.name $credName $customCASecretRef.key) -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} diff --git a/charts/library/common/templates/spawner/_pvc.tpl b/charts/library/common/templates/spawner/_pvc.tpl index a0a67b113cc..17854b3ccc4 100644 --- a/charts/library/common/templates/spawner/_pvc.tpl +++ b/charts/library/common/templates/spawner/_pvc.tpl @@ -129,9 +129,27 @@ {{- include "tc.v1.common.class.secret" (dict "rootCtx" $ "objectData" $volsyncSecretData) -}} - {{/* Create Custom CA Secret for VolSync */}} - {{- if $credentials.customCA -}} + {{- if $credentials.customCASecretRef -}} + {{/* Get the customCA secret name */}} + + {{- $customCASecretRef := $credentials.customCASecretRef -}} + {{- $expandName := (include "tc.v1.common.lib.util.expandName" (dict + "rootCtx" $ "objectData" $customCASecretRef + "name" $customCASecretRef.name "caller" "PVC - VolSync" + "key" (printf "credentials.%s.customCASecretRef.name" $volsyncData.credentials))) -}} + + {{- $CAsecretName := tpl $customCASecretRef.name $ -}} + {{- if eq $expandName "true" -}} + {{- $fullname := include "tc.v1.common.lib.chart.names.fullname" $ -}} + {{- $CAsecretName = (printf "%s-%s" $fullname $CAsecretName) -}} + {{- end -}} + + {{- $_ := set $volsyncData "customCA" (dict "name" $CAsecretName "key" $customCASecretRef.key) -}} + {{- else if $credentials.customCA -}} + {{/* Create Custom CA Secret for VolSync */}} + {{- $volsyncCASecretName := printf "%s-volsync-ca-%s" (include "tc.v1.common.lib.chart.names.fullname" $ ) $volsync.credentials -}} + {{- $volsyncCAKey := "ca.crt" -}} {{- $_ := set $volsyncData "customCA" $volsyncCASecretName -}} @@ -140,14 +158,15 @@ "labels" ($volsync.labels | default dict) "annotations" ($volsync.annotations | default dict) "data" (dict - "ca.crt" $credentials.customCA + $volsyncCAKey $credentials.customCA ) ) -}} {{- include "tc.v1.common.class.secret" (dict "rootCtx" $ "objectData" $volsyncCASecretData) -}} + {{- $_ := set $volsyncData "customCA" (dict "name" $volsyncCASecretName "key" $volsyncCAKey) -}} {{- end -}} - {{/* Create VolSync resources*/}} + {{/* Create VolSync resources*/}} {{- if $srcEnabled -}} {{- include "tc.v1.common.class.replicationsource" (dict "rootCtx" $ "objectData" $objectData "volsyncData" $volsyncData) -}} {{- end -}} diff --git a/charts/library/common/values.yaml b/charts/library/common/values.yaml index 66eb81002a7..81e5de8998f 100644 --- a/charts/library/common/values.yaml +++ b/charts/library/common/values.yaml @@ -297,7 +297,12 @@ credentials: # url: "" # ## Provide a custom certificate authority in cases where the URL endpoint # ## uses a self-signed certificate + # ## NOTE: DEPRECATED, use customCASecretRef instead # customCA: "" + # customCASecretRef: + # name: my-secret + # key: ca.crt + # # expandObjectName: false # default true # path: "" # bucket: "" # accessKey: "" diff --git a/website/src/content/docs/common/credentials.md b/website/src/content/docs/common/credentials.md index df0ac644c87..a363092ab4d 100644 --- a/website/src/content/docs/common/credentials.md +++ b/website/src/content/docs/common/credentials.md @@ -106,11 +106,116 @@ credentials: --- +#### `customCASecretRef` + +Reference a secret containing a custom CA to be used when connecting to the +endpoint defined by `url` over HTTPS. + +:::note + +Defining both this and [customCA](/common/credentials#customca) is invalid and +will result in an error. + +::: + +| | | +| ---------- | ------------------------------------------------------------- | +| Key | `credentials.$name.customCASecretRef` | +| Type | `map` | +| Required | ❌ | +| Helm `tpl` | ❌ | +| Example | `{}` | + +```yaml +credentials: + credentials-name: + customCASecretRef: {} +``` + +--- + +##### `customCASecretRef.name` + +Define the secret name + +:::note + +This will be automatically expanded to `fullname-secret-name`. +You can opt out of this by setting [`expandObjectName`](/common/credentials#customcasecretrefexpandobjectname) to `false` + +::: + +| | | +| ---------- | ------------------------------------------ | +| Key | `credentials.$name.customCASecretRef.name` | +| Type | `string` | +| Required | ✅ | +| Helm `tpl` | ✅ | +| Example | `""` | + +```yaml +credentials: + credentials-name: + customCASecretRef: + name: secret-name +``` + +--- + +##### `customCASecretRef.key` + +Define the key in the secret data containing the CA + +| | | +| ---------- | ------------------------------------------ | +| Key | `credentials.$name.customCASecretRef.key` | +| Type | `string` | +| Required | ✅ | +| Helm `tpl` | ❌ | +| Example | `""` | + +```yaml +credentials: + credentials-name: + customCASecretRef: + key: ca.crt +``` + +--- + +##### `customCASecretRef.expandObjectName` + +Whether to expand (adding the fullname as prefix) the secret name + +| | | +| ---------- | ------------------------------------------------------ | +| Key | `credentials.$name.customCASecretRef.expandObjectName` | +| Type | `bool` | +| Required | ❌ | +| Helm `tpl` | ❌ | +| Example | `true` | + +```yaml +credentials: + credentials-name: + customCASecretRef: + expandObjectName: false +``` + +--- + #### `customCA` Define a custom CA certificate to be used when connecting to the endpoint defined by `url` over HTTPS. +:::note + +Defining both this and [customCASecretRef](/common/credentials#customcasecretref) +is invalid and will result in an error. + +::: + | | | | ---------- | ------------------------------------------------------------- | | Key | `credentials.$name.customCA` |