From 63875a04a09405f218430f2f0e1783d390f6bc29 Mon Sep 17 00:00:00 2001 From: FreakIsTea Date: Wed, 23 Sep 2026 09:25:20 +0200 Subject: [PATCH] feat: allow secrets as source for auth identity/secret --- .github/workflows/functional-test.yaml | 13 +++++++ .github/workflows/lint-render.yaml | 1 + charts/s3proxy/Chart.yaml | 2 +- charts/s3proxy/README.md.gotmpl | 37 ++++++++++++++++++- charts/s3proxy/override-values.example.yaml | 5 +++ charts/s3proxy/templates/NOTES.txt | 4 +- charts/s3proxy/templates/deployment.yaml | 27 ++++++++++++++ charts/s3proxy/templates/secret.yaml | 14 ++++++- charts/s3proxy/values.yaml | 10 ++++- .../values/auth-existing-secret.yaml | 20 ++++++++++ test-values/auth-existing-secret.yaml | 14 +++++++ 11 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 ci/functional/values/auth-existing-secret.yaml create mode 100644 test-values/auth-existing-secret.yaml diff --git a/.github/workflows/functional-test.yaml b/.github/workflows/functional-test.yaml index e106d88..e1eb1a5 100644 --- a/.github/workflows/functional-test.yaml +++ b/.github/workflows/functional-test.yaml @@ -58,6 +58,9 @@ jobs: values: ci/functional/values/filesystem.yaml - backend: transient values: ci/functional/values/transient.yaml + - backend: auth-existing-secret + values: ci/functional/values/auth-existing-secret.yaml + authSecret: true - backend: azureblob values: ci/functional/values/azureblob.yaml mock: ci/functional/mocks/azurite.yaml @@ -111,6 +114,16 @@ jobs: --from-file=keystore.p12=/tmp/keystore.p12 \ --from-literal=keystore-password=changeit + - name: Create client auth Secret (auth existingSecret leg) + if: matrix.authSecret + # The identity/secret the smoke test signs with, in the Secret the chart + # references via config.auth.existingSecret. Broken wiring leaves + # s3proxy.identity/credential unset and the round-trip fails to authenticate. + run: | + kubectl -n "${NAMESPACE}" create secret generic s3proxy-auth \ + --from-literal=accessKeyId='test-access-key' \ + --from-literal=secretAccessKey='test-secret-key' + - name: Create Azure Blob key Secret (existingSecret leg) if: matrix.azureKeySecret # Azurite's devstoreaccount1 key in the Secret the chart references via diff --git a/.github/workflows/lint-render.yaml b/.github/workflows/lint-render.yaml index d3846fe..10d97f3 100644 --- a/.github/workflows/lint-render.yaml +++ b/.github/workflows/lint-render.yaml @@ -30,6 +30,7 @@ jobs: - test-values/s3.yaml - test-values/azureblob.yaml - test-values/azureblob-existing-secret.yaml + - test-values/auth-existing-secret.yaml - test-values/azureblob-managed-identity.yaml - test-values/gcs.yaml - test-values/b2.yaml diff --git a/charts/s3proxy/Chart.yaml b/charts/s3proxy/Chart.yaml index 705360d..916483b 100644 --- a/charts/s3proxy/Chart.yaml +++ b/charts/s3proxy/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.4.3 +version: 0.5.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/s3proxy/README.md.gotmpl b/charts/s3proxy/README.md.gotmpl index 3d2d2ac..faa4f4f 100644 --- a/charts/s3proxy/README.md.gotmpl +++ b/charts/s3proxy/README.md.gotmpl @@ -236,6 +236,41 @@ persistence: enabled: false ``` +## Client authentication credentials + +`config.auth.identity` / `config.auth.secret` are the S3 Access Key ID and Secret +Access Key that *clients* present to S3Proxy. + +### Option A: existing Secret (recommended) + +Point `config.auth.existingSecret` at a Secret you manage yourself, so the credentials +never appear in the values file (e.g. in GitOps). It takes precedence over inline +`identity`/`secret`; both credentials are read from it: + +```yaml +config: + auth: + type: "aws-v4" + existingSecret: my-s3proxy-auth + identityKey: accessKeyId # default; key holding the Access Key ID + secretKey: secretAccessKey # default; key holding the Secret Access Key +``` + +```bash +kubectl create secret generic my-s3proxy-auth \ + --from-literal=accessKeyId='myaccesskey' \ + --from-literal=secretAccessKey='mysecretkey' +``` + +Rotating the Secret does not restart the pod — roll the Deployment yourself, since the +credentials are read once at startup. + +### Option B: inline credentials + +Set `config.auth.identity` / `config.auth.secret` directly, as in +[Example 1](#example-1-filesystem-backend-with-authentication). Convenient for +testing; both values are stored in plaintext in the chart's own Secret. + ## Testing the Installation Once deployed, you can test S3Proxy using the AWS CLI: @@ -426,7 +461,7 @@ This will remove all resources created by the chart. If using persistence, the P ### Common Issues -1. **Authentication failures**: Ensure `config.auth.identity` and `config.auth.secret` are set correctly for client authentication. +1. **Authentication failures**: Ensure `config.auth.identity` and `config.auth.secret` are set correctly (or, with `config.auth.existingSecret`, that the Secret exists and its `identityKey`/`secretKey` entries are present) for client authentication. 2. **Backend connection issues**: Verify backend credentials are correctly configured in the appropriate section (e.g., `config.backend.awsS3.*`). diff --git a/charts/s3proxy/override-values.example.yaml b/charts/s3proxy/override-values.example.yaml index af9fa60..f40d458 100644 --- a/charts/s3proxy/override-values.example.yaml +++ b/charts/s3proxy/override-values.example.yaml @@ -26,6 +26,11 @@ config: # These should be changed to secure values identity: "admin" secret: "changeme123" + # Or keep them out of this file entirely and read both from a Secret you + # manage (takes precedence over the inline values above): + # existingSecret: "my-s3proxy-auth" + # identityKey: "accessKeyId" + # secretKey: "secretAccessKey" # Native in-pod HTTPS (S3Proxy secure-endpoint). When enabled, S3Proxy serves # HTTPS only on service.targetPort; TLS is terminated in the pod rather than at diff --git a/charts/s3proxy/templates/NOTES.txt b/charts/s3proxy/templates/NOTES.txt index ad8cd23..f9a7045 100644 --- a/charts/s3proxy/templates/NOTES.txt +++ b/charts/s3proxy/templates/NOTES.txt @@ -106,8 +106,8 @@ {{- if .Values.config.backends.filesystem.enabled }} - Using filesystem backend at: {{ .Values.config.backends.filesystem.basedir }} {{- end }} -{{- if not .Values.config.auth.identity }} - - WARNING: S3Proxy identity not configured. Remember to set config.auth.identity and config.auth.secret for authentication. +{{- if and (not .Values.config.auth.identity) (not .Values.config.auth.existingSecret) }} + - WARNING: S3Proxy identity not configured. Remember to set config.auth.identity and config.auth.secret (or config.auth.existingSecret) for authentication. {{- end }} {{- if .Values.config.backends.s3.enabled }} - Make sure to configure S3 backend credentials diff --git a/charts/s3proxy/templates/deployment.yaml b/charts/s3proxy/templates/deployment.yaml index 3dd2007..6a7d171 100644 --- a/charts/s3proxy/templates/deployment.yaml +++ b/charts/s3proxy/templates/deployment.yaml @@ -72,6 +72,18 @@ spec: cat "$COMMON_SECRET" >> "$output_file" fi + # Client auth from config.auth.existingSecret: each file under + # /auth-secret/ is named for the S3Proxy property it holds. + if [ -d "/auth-secret" ] + then + for prop_file in /auth-secret/* + do + [ -f "$prop_file" ] || continue + echo "" >> "$output_file" # Add newline separator + printf '%s=%s\n' "$(basename "$prop_file")" "$(cat "$prop_file")" >> "$output_file" + done + fi + # Append this backend's own secret: backend-.properties # pairs with secret-.properties backend="${filename#backend-}" @@ -122,6 +134,11 @@ spec: mountPath: /tls-password readOnly: true {{- end }} +{{- if .Values.config.auth.existingSecret }} + - name: auth-secret + mountPath: /auth-secret + readOnly: true +{{- end }} {{- range $s := (include "s3proxy.externalBackendSecrets" . | fromJsonArray) }} - name: {{ $s.name }} mountPath: /backend-secret/{{ $s.backend }}/{{ $s.prop }} @@ -291,6 +308,16 @@ spec: - key: {{ .Values.config.backends.googleCloudStorage.privateKey.secretKey }} path: gcs-private.key {{- end }} +{{- if .Values.config.auth.existingSecret }} + - name: auth-secret + secret: + secretName: {{ .Values.config.auth.existingSecret }} + items: + - key: {{ .Values.config.auth.identityKey }} + path: s3proxy.identity + - key: {{ .Values.config.auth.secretKey }} + path: s3proxy.credential +{{- end }} {{- range $s := (include "s3proxy.externalBackendSecrets" . | fromJsonArray) }} - name: {{ $s.name }} secret: diff --git a/charts/s3proxy/templates/secret.yaml b/charts/s3proxy/templates/secret.yaml index 87a6295..3b8d757 100644 --- a/charts/s3proxy/templates/secret.yaml +++ b/charts/s3proxy/templates/secret.yaml @@ -12,6 +12,7 @@ data: # into the text properties. Used only when keystore.existingSecret is empty. {{ .Values.config.tls.keystore.secretKey }}: {{ .Values.config.tls.keystore.value }} {{- end }} +{{- with include "s3proxy.secret.stringData" . }} stringData: # Sensitive properties merged into the backend properties files by the # merge-configs initContainer: @@ -20,7 +21,17 @@ stringData: # each backend gets its own jclouds.credential and never another backend's. # Local backends (filesystem/transient) need no secret file; their placeholder # jclouds.identity/credential come from the ConfigMap. -{{- $hasClientAuth := and .Values.config.auth.identity .Values.config.auth.secret }} +{{- . }} +{{- end }} + +{{- /* +The stringData properties, captured so the key above is emitted only when non-empty +(with every credential coming from an existingSecret, or auth disabled, a bare +`stringData:` would render as null). Client auth from config.auth.existingSecret is +mounted at /auth-secret and merged by the initContainer instead of landing here. +*/}} +{{- define "s3proxy.secret.stringData" -}} +{{- $hasClientAuth := and .Values.config.auth.identity .Values.config.auth.secret (not .Values.config.auth.existingSecret) }} {{- $inlineKeystorePassword := and .Values.config.tls.enabled .Values.config.tls.keystorePassword.value (not .Values.config.tls.keystorePassword.existingSecret) }} {{- if or $hasClientAuth $inlineKeystorePassword }} secret-common.properties: | @@ -71,3 +82,4 @@ stringData: # Rackspace Cloud Files backend credential jclouds.credential={{ .Values.config.backends.rackspaceCloudfiles.apiKey.value }} {{- end }} +{{- end }} diff --git a/charts/s3proxy/values.yaml b/charts/s3proxy/values.yaml index 38d2432..151c484 100644 --- a/charts/s3proxy/values.yaml +++ b/charts/s3proxy/values.yaml @@ -122,10 +122,16 @@ config: auth: # -- Authorization type (none, aws-v2, aws-v4, aws-v2-or-v4) type: "aws-v4" - # -- S3 Access Key ID for client authentication + # -- S3 Access Key ID for client authentication, stored in the chart-owned Secret. Ignored when `existingSecret` is set. identity: "" - # -- S3 Secret Access Key for client authentication + # -- S3 Secret Access Key for client authentication, stored in the chart-owned Secret. Ignored when `existingSecret` is set. secret: "" + # -- Name of an existing Secret holding both client authentication credentials (see `identityKey`/`secretKey`). Takes precedence over `identity`/`secret`, which are otherwise stored in plaintext in the chart-owned Secret; prefer this when the values file lives in Git. + existingSecret: "" + # -- Key within `existingSecret` that holds the S3 Access Key ID + identityKey: "accessKeyId" + # -- Key within `existingSecret` that holds the S3 Secret Access Key + secretKey: "secretAccessKey" tls: # -- Enable native in-pod HTTPS (S3Proxy `secure-endpoint`). When enabled, S3Proxy serves HTTPS only on `service.targetPort` (the plaintext endpoint is not bound), so TLS is terminated in the pod rather than at the ingress. Requires a PKCS12 (or JKS) keystore and its password. `tcpSocket` health probes are unaffected (they do not perform a TLS handshake). diff --git a/ci/functional/values/auth-existing-secret.yaml b/ci/functional/values/auth-existing-secret.yaml new file mode 100644 index 0000000..b298e8a --- /dev/null +++ b/ci/functional/values/auth-existing-secret.yaml @@ -0,0 +1,20 @@ +# Functional-test values: client auth sourced from a pre-existing Secret +# (config.auth.existingSecret, default identityKey/secretKey) on a self-contained +# filesystem backend. The workflow creates the Secret with the credentials the smoke +# test signs with. +config: + auth: + type: aws-v4 + existingSecret: s3proxy-auth + backends: + filesystem: + enabled: true + nio2: true + basedir: /data/s3proxy +persistence: + enabled: true + size: 1Gi +resources: + requests: + cpu: 100m + memory: 256Mi diff --git a/test-values/auth-existing-secret.yaml b/test-values/auth-existing-secret.yaml new file mode 100644 index 0000000..457eb8e --- /dev/null +++ b/test-values/auth-existing-secret.yaml @@ -0,0 +1,14 @@ +# Render/lint scenario: S3Proxy client credentials from a pre-existing Secret instead +# of inline values, so they never land in the values file (and thus in Git). +config: + logLevel: INFO + auth: + type: aws-v4 + existingSecret: my-s3proxy-auth + backends: + filesystem: + enabled: true + nio2: true + basedir: /data/s3proxy +persistence: + enabled: false