From 02601b154a4edb9e63dff92bd64dc82510609ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Fernando=20Carri=C3=B3n?= Date: Thu, 3 Sep 2026 12:19:18 +0200 Subject: [PATCH] feat(azureblob): support Azure Managed/Workload Identity Add config.backends.azureblob.managedIdentity. When true the chart renders jclouds.identity and jclouds.credential as empty strings, which is the only state that makes the azureblob-sdk provider use Azure's DefaultAzureCredential (the AKS workload-identity-injected AZURE_CLIENT_ID/TENANT_ID/FEDERATED_TOKEN_FILE). Before this, jclouds.identity was emitted only when 'account' was set, and always as the account name, so an empty/empty identity+credential was unexpressible: with account set identity was the account (a static identity disables managed identity), and with account empty the identity line was absent, not an empty string. The account name now goes in the endpoint (unchanged default), never in identity. A render-time guard rejects the auth-breaking misconfigs (non-sdk provider, a static key/SAS alongside managed identity, or no endpoint). Static-key and SAS paths are untouched. No image bump (andrewgaul/s3proxy:3.3.0 already supports it). --- .github/workflows/lint-render.yaml | 1 + charts/s3proxy/Chart.yaml | 2 +- charts/s3proxy/README.md.gotmpl | 36 ++++++++++++++++++++- charts/s3proxy/templates/configmap.yaml | 35 ++++++++++++++++---- charts/s3proxy/values.yaml | 2 ++ test-values/azureblob-managed-identity.yaml | 25 ++++++++++++++ 6 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 test-values/azureblob-managed-identity.yaml diff --git a/.github/workflows/lint-render.yaml b/.github/workflows/lint-render.yaml index f4276b1..d3846fe 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/azureblob-managed-identity.yaml - test-values/gcs.yaml - test-values/b2.yaml - test-values/openstack-swift.yaml diff --git a/charts/s3proxy/Chart.yaml b/charts/s3proxy/Chart.yaml index dd00589..705360d 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.2 +version: 0.4.3 # 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 25fb8e2..3d2d2ac 100644 --- a/charts/s3proxy/README.md.gotmpl +++ b/charts/s3proxy/README.md.gotmpl @@ -26,7 +26,7 @@ The chart tracks S3Proxy (`andrewgaul/s3proxy`) through `appVersion` (currently S3Proxy `3.0.0` deprecated the Apache jclouds storage backends (`s3`, `aws-s3`, `azureblob`, `filesystem`, `transient`) in favor of SDK / NIO2 providers. Upstream has announced that `3.3.0` is the last release to bundle jclouds and that future releases "will lack its Atmos and B2 storage backends" (no jclouds-free release has shipped yet; `3.3.0` remains the latest). The jclouds providers still work on 3.x, but are deprecated: - `filesystem` / `transient`: already default to the non-deprecated `*-nio2` variants (`nio2: true`). -- `azureblob`: **defaults to `provider: azureblob-sdk`** (the Azure SDK provider, which signs correctly against custom endpoints such as Azurite). The legacy jclouds `azureblob` provider is deprecated and mis-signs against custom endpoints; set `provider: azureblob` only if you specifically need it. On real Azure, `azureblob-sdk` may require `config.backends.azureblob.regions` for bucket creation. +- `azureblob`: **defaults to `provider: azureblob-sdk`** (the Azure SDK provider, which signs correctly against custom endpoints such as Azurite). The legacy jclouds `azureblob` provider is deprecated and mis-signs against custom endpoints; set `provider: azureblob` only if you specifically need it. On real Azure, `azureblob-sdk` may require `config.backends.azureblob.regions` for bucket creation. For AKS Managed/Workload Identity (no static key), set `config.backends.azureblob.managedIdentity: true` — see "Example 3b" below. - `s3`, `googleCloudStorage`, `openstackSwift`: SDK providers exist upstream (`aws-s3-sdk`, `google-cloud-storage-sdk`, `openstack-swift-sdk`). `rackspaceCloudfiles` is OpenStack-Swift-compatible and may be served by `openstack-swift-sdk`. Migrating the chart defaults to the SDK providers is tracked separately. - `b2` (and Atmos, if ever added) are jclouds-only with **no SDK successor**. These are the backends upstream has said future releases will drop. @@ -163,6 +163,40 @@ Install: helm install s3proxy-azure ./{{ template "chart.name" . }} -f values-azure.yaml ``` +### Example 3b: Azure Blob with Managed / Workload Identity (no static key) + +On AKS with Workload Identity, set `managedIdentity: true` instead of an account +key or SAS token. The chart then renders both `jclouds.identity` and +`jclouds.credential` empty, which makes the `azureblob-sdk` provider fall back to +Azure's `DefaultAzureCredential` — it reads the federated token AKS injects +(`AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_FEDERATED_TOKEN_FILE`). The storage +account name goes in the endpoint, never in the identity. + +```yaml +# values-azure-mi.yaml +config: + backends: + filesystem: + enabled: false + azureblob: + enabled: true + provider: "azureblob-sdk" # required: the legacy jclouds provider can't do token auth + managedIdentity: true + account: "mystorageaccount" # used for the endpoint; NOT emitted as jclouds.identity + # endpoint: "https://mystorageaccount.blob.core.windows.net" # or set explicitly + +# Bind the pod to the AKS user-assigned identity: +serviceAccount: + create: true + annotations: + azure.workload.identity/client-id: "" +podLabels: + azure.workload.identity/use: "true" + +persistence: + enabled: false +``` + ### Example 4: Google Cloud Storage Backend ```yaml diff --git a/charts/s3proxy/templates/configmap.yaml b/charts/s3proxy/templates/configmap.yaml index e79212b..a7c8ac4 100644 --- a/charts/s3proxy/templates/configmap.yaml +++ b/charts/s3proxy/templates/configmap.yaml @@ -87,6 +87,19 @@ s3proxy.large-object-mocking=true {{- end }} {{- end }} {{- end }} +{{- /* Fail fast on an Azure managed-identity config that can't authenticate, since + the failure otherwise surfaces only at runtime as an opaque Azure SDK error. */}} +{{- if and .Values.config.backends.azureblob.enabled .Values.config.backends.azureblob.managedIdentity }} + {{- if ne .Values.config.backends.azureblob.provider "azureblob-sdk" }} + {{- fail "config.backends.azureblob.managedIdentity requires provider=azureblob-sdk; the legacy jclouds azureblob provider cannot use Azure token auth." }} + {{- end }} + {{- if or .Values.config.backends.azureblob.key.value .Values.config.backends.azureblob.key.existingSecret .Values.config.backends.azureblob.sasToken.value }} + {{- fail "config.backends.azureblob.managedIdentity conflicts with a static credential; a non-empty key.*/sasToken.* disables DefaultAzureCredential. Unset them." }} + {{- end }} + {{- if and (not .Values.config.backends.azureblob.account) (not .Values.config.backends.azureblob.endpoint) }} + {{- fail "config.backends.azureblob.managedIdentity needs the storage endpoint; set config.backends.azureblob.account (default endpoint) or endpoint." }} + {{- end }} +{{- end }} apiVersion: v1 kind: ConfigMap metadata: @@ -174,15 +187,23 @@ data: # (otherwise "no jclouds.regions configured" -> InvalidLocationConstraint). jclouds.regions={{ .Values.config.backends.azureblob.regions }} {{- end }} - {{- if .Values.config.backends.azureblob.account }} + {{- if .Values.config.backends.azureblob.managedIdentity }} + # Managed/Workload Identity: azureblob-sdk falls back to Azure's + # DefaultAzureCredential (reads the AKS-injected AZURE_CLIENT_ID/TENANT_ID/ + # FEDERATED_TOKEN_FILE) only when BOTH identity and credential are empty. The + # account name lives in jclouds.endpoint above, never in the identity. + jclouds.identity= + jclouds.credential= + {{- else }} + {{- if .Values.config.backends.azureblob.account }} jclouds.identity={{ .Values.config.backends.azureblob.account }} - {{- end }} - {{- /* With a key (key.value or key.existingSecret) the initContainer merges - jclouds.credential in from a Secret. S3Proxy requires the property in every - backend file, so emit it empty when neither is set (SAS-token or Azure - Workload Identity auth). */}} - {{- if and (not .Values.config.backends.azureblob.key.value) (not .Values.config.backends.azureblob.key.existingSecret) }} + {{- end }} + {{- /* With a key (key.value or key.existingSecret) the initContainer merges + jclouds.credential in from a Secret. S3Proxy requires the property in every + backend file, so emit it empty when neither is set (SAS-token auth). */}} + {{- if and (not .Values.config.backends.azureblob.key.value) (not .Values.config.backends.azureblob.key.existingSecret) }} jclouds.credential= + {{- end }} {{- end }} {{- range $index, $bucket := .Values.config.backends.azureblob.bucketLocators }} s3proxy.bucket-locator.{{ add $index 1 }}={{ $bucket }} diff --git a/charts/s3proxy/values.yaml b/charts/s3proxy/values.yaml index 6a2f777..38d2432 100644 --- a/charts/s3proxy/values.yaml +++ b/charts/s3proxy/values.yaml @@ -234,6 +234,8 @@ config: enabled: false # -- Provider type. Defaults to `azureblob-sdk` (Azure SDK): it signs correctly against custom endpoints (Azurite, Azure Gov/China, private endpoints) and is the non-deprecated provider on S3Proxy 3.x. The legacy jclouds `azureblob` provider is deprecated upstream and mis-signs against custom endpoints; on real Azure `azureblob-sdk` may require `regions` to be set for bucket creation. provider: "azureblob-sdk" + # -- Use Azure Managed/Workload Identity instead of a static account key or SAS token. Requires `provider: azureblob-sdk`; mutually exclusive with `key.*` and `sasToken.*`. When true the chart renders both `jclouds.identity` and `jclouds.credential` empty, which makes azureblob-sdk fall back to Azure's `DefaultAzureCredential` (consuming the AKS workload-identity env `AZURE_CLIENT_ID`/`AZURE_TENANT_ID`/`AZURE_FEDERATED_TOKEN_FILE`). Set `account` (or `endpoint`) for the storage endpoint, annotate the ServiceAccount with `azure.workload.identity/client-id` (`serviceAccount.annotations`) and label the pod `azure.workload.identity/use: "true"` (`podLabels`). + managedIdentity: false # -- Storage account name account: "" # -- Storage account key configuration diff --git a/test-values/azureblob-managed-identity.yaml b/test-values/azureblob-managed-identity.yaml new file mode 100644 index 0000000..e6e265d --- /dev/null +++ b/test-values/azureblob-managed-identity.yaml @@ -0,0 +1,25 @@ +# Render/lint scenario: Azure Blob with Managed/Workload Identity (no static key). +# Proves the chart can render both jclouds.identity and jclouds.credential EMPTY so +# azureblob-sdk uses DefaultAzureCredential — the account name lives in the endpoint, +# never in jclouds.identity. Also exercises the workload-identity SA/pod wiring. +config: + auth: + type: aws-v4 + identity: test-access-key + secret: test-secret-key + backends: + filesystem: + enabled: false + azureblob: + enabled: true + provider: azureblob-sdk + managedIdentity: true + account: teststorageaccount +serviceAccount: + create: true + annotations: + azure.workload.identity/client-id: "00000000-0000-0000-0000-000000000000" +podLabels: + azure.workload.identity/use: "true" +persistence: + enabled: false