From 99b411f403ac642c438fb840484748157d5cb7b2 Mon Sep 17 00:00:00 2001 From: Adam Weingarten <6517820+aweingarten@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:02:26 -0400 Subject: [PATCH] feat(obj): source linode accessKeyId from obj-secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two halves of the Linode object-storage credential were sourced asymmetrically: `secretAccessKey` came from the `obj-secrets` Secret (ESO-friendly, rotatable without touching git), while `accessKeyId` was inlined into every consumer's ExternalSecret template straight from git settings. Linode OBJ keys rotate as a pair, so an operator that rotates them out-of-band had to push the new `accessKeyId` back into the values repo on every rotation — carrying a non-secret identifier through git purely because its paired secret is handled cleanly, and requiring a git-sync reconciler with a write-scoped token to do it. Mark `accessKeyId` as an x-secret so it lands in `obj-secrets` next to `secretAccessKey`, and template it from there in all six consumers (loki, harbor ×2, gitea, keycloak, kubeflow-pipelines). Rotation now means writing one Secret, no git writes at all. The field stays `required`, exactly like `secretAccessKey` — this changes where the value is stored, not whether it must be supplied. Refs #3459 --- src/common/obj-secret-paths.test.ts | 13 +++++++++++++ .../apl-secrets/sealedsecrets/obj-secrets.yaml | 1 + tests/fixtures/env/settings/obj.yaml | 1 - values-schema.yaml | 1 + values/gitea/gitea-raw.gotmpl | 6 +++++- values/harbor/harbor-raw.gotmpl | 12 ++++++++++-- values/keycloak/keycloak-raw.gotmpl | 6 +++++- .../kubeflow-pipelines-raw.gotmpl | 6 +++++- values/loki/loki-raw.gotmpl | 6 +++++- 9 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/common/obj-secret-paths.test.ts diff --git a/src/common/obj-secret-paths.test.ts b/src/common/obj-secret-paths.test.ts new file mode 100644 index 0000000000..9df06a296d --- /dev/null +++ b/src/common/obj-secret-paths.test.ts @@ -0,0 +1,13 @@ +import { getSchemaSecretsPaths } from './utils' + +describe('object storage credential paths', () => { + it('should classify both halves of the linode credential as secrets', async () => { + // Linode OBJ keys rotate as a pair, so the id has to travel the same route as its secret half: + // into obj-secrets, out via ExternalSecret. Keeping the id in git settings forces a values-repo + // write on every rotation just to keep the pair together. + const secretPaths = await getSchemaSecretsPaths([]) + + expect(secretPaths).toContain('obj.provider.linode.accessKeyId') + expect(secretPaths).toContain('obj.provider.linode.secretAccessKey') + }) +}) diff --git a/tests/fixtures/env/manifests/namespaces/apl-secrets/sealedsecrets/obj-secrets.yaml b/tests/fixtures/env/manifests/namespaces/apl-secrets/sealedsecrets/obj-secrets.yaml index e8e1f6336f..512f5f12a0 100644 --- a/tests/fixtures/env/manifests/namespaces/apl-secrets/sealedsecrets/obj-secrets.yaml +++ b/tests/fixtures/env/manifests/namespaces/apl-secrets/sealedsecrets/obj-secrets.yaml @@ -7,6 +7,7 @@ metadata: namespace: apl-secrets spec: encryptedData: + provider_linode_accessKeyId: someaccessKeyId provider_linode_secretAccessKey: somesecretvalue template: immutable: false diff --git a/tests/fixtures/env/settings/obj.yaml b/tests/fixtures/env/settings/obj.yaml index 25305114f7..6005e99649 100644 --- a/tests/fixtures/env/settings/obj.yaml +++ b/tests/fixtures/env/settings/obj.yaml @@ -4,7 +4,6 @@ metadata: spec: provider: linode: - accessKeyId: someaccessKeyId buckets: cnpg: my-clusterid-cnpg gitea: my-clusterid-gitea diff --git a/values-schema.yaml b/values-schema.yaml index ce355a21ee..596ab01e5e 100644 --- a/values-schema.yaml +++ b/values-schema.yaml @@ -2899,6 +2899,7 @@ properties: type: string description: The ID of the access key $ref: '#/definitions/wordCharacterPattern' + x-secret: '' secretAccessKey: type: string description: The secret of the access key diff --git a/values/gitea/gitea-raw.gotmpl b/values/gitea/gitea-raw.gotmpl index 822312c8be..50d7944244 100644 --- a/values/gitea/gitea-raw.gotmpl +++ b/values/gitea/gitea-raw.gotmpl @@ -61,9 +61,13 @@ resources: template: type: Opaque data: - S3_STORAGE_ACCOUNT: {{ $obj.linode.accessKeyId }} + S3_STORAGE_ACCOUNT: '{{ "{{ .accessKeyId | toString }}" }}' S3_STORAGE_KEY: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets diff --git a/values/harbor/harbor-raw.gotmpl b/values/harbor/harbor-raw.gotmpl index df47f16e42..3be816950c 100644 --- a/values/harbor/harbor-raw.gotmpl +++ b/values/harbor/harbor-raw.gotmpl @@ -207,9 +207,13 @@ resources: template: type: Opaque data: - S3_STORAGE_ACCOUNT: {{ $obj.linode.accessKeyId }} + S3_STORAGE_ACCOUNT: '{{ "{{ .accessKeyId | toString }}" }}' S3_STORAGE_KEY: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets @@ -229,9 +233,13 @@ resources: template: type: Opaque data: - REGISTRY_STORAGE_S3_ACCESSKEY: {{ $obj.linode.accessKeyId }} + REGISTRY_STORAGE_S3_ACCESSKEY: '{{ "{{ .accessKeyId | toString }}" }}' REGISTRY_STORAGE_S3_SECRETKEY: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets diff --git a/values/keycloak/keycloak-raw.gotmpl b/values/keycloak/keycloak-raw.gotmpl index cfc3803051..eb0fc7f795 100644 --- a/values/keycloak/keycloak-raw.gotmpl +++ b/values/keycloak/keycloak-raw.gotmpl @@ -48,9 +48,13 @@ resources: template: type: Opaque data: - S3_STORAGE_ACCOUNT: {{ $obj.linode.accessKeyId }} + S3_STORAGE_ACCOUNT: '{{ "{{ .accessKeyId | toString }}" }}' S3_STORAGE_KEY: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets diff --git a/values/kubeflow-pipelines/kubeflow-pipelines-raw.gotmpl b/values/kubeflow-pipelines/kubeflow-pipelines-raw.gotmpl index 6a07f53487..4b8b0cf419 100644 --- a/values/kubeflow-pipelines/kubeflow-pipelines-raw.gotmpl +++ b/values/kubeflow-pipelines/kubeflow-pipelines-raw.gotmpl @@ -41,9 +41,13 @@ resources: template: type: Opaque data: - accesskey: {{ $obj.linode.accessKeyId }} + accesskey: '{{ "{{ .accessKeyId | toString }}" }}' secretkey: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets diff --git a/values/loki/loki-raw.gotmpl b/values/loki/loki-raw.gotmpl index 57c57f89f2..280f4ec68f 100644 --- a/values/loki/loki-raw.gotmpl +++ b/values/loki/loki-raw.gotmpl @@ -62,9 +62,13 @@ resources: template: type: Opaque data: - AWS_ACCESS_KEY_ID: {{ $obj.linode.accessKeyId }} + AWS_ACCESS_KEY_ID: '{{ "{{ .accessKeyId | toString }}" }}' AWS_SECRET_ACCESS_KEY: '{{ "{{ .secretAccessKey | toString }}" }}' data: + - secretKey: accessKeyId + remoteRef: + key: obj-secrets + property: provider_linode_accessKeyId - secretKey: secretAccessKey remoteRef: key: obj-secrets