Skip to content

fix(flux): authenticate to ACR explicitly instead of by discovery - #3912

Open
arealmaas wants to merge 1 commit into
mainfrom
arealmaas/flux-acr-explicit-auth
Open

fix(flux): authenticate to ACR explicitly instead of by discovery#3912
arealmaas wants to merge 1 commit into
mainfrom
arealmaas/flux-acr-explicit-auth

Conversation

@arealmaas

Copy link
Copy Markdown
Contributor

Makes flux/setup-flux-acr hand the Azure SDK explicit federated credentials, so flux --provider=azure authenticates the same way regardless of which runner the job lands on.

Refs #3838

The problem

Altinn/info.altinn.no hit this when moving a workflow that uses flux/build-push-image onto the self-hosted runners:

✗ error during login with provider: ... DefaultAzureCredential: failed to acquire a token
    ManagedIdentityCredential authentication failed.
    GET http://localhost:12356/msi/token
    RESPONSE 400: Unable to load the proper Managed Identity

flux --provider=azure authenticates through DefaultAzureCredential, which resolves a credential by discovery rather than using the session azure/login just established — and stops at the first credential that fails outright.

Runner Chain behaviour
ubuntu-latest Nothing earlier is available → ManagedIdentityCredential reports unavailable → falls through to AzureCLICredential → uses the azure/login session ✅
Self-hosted (Container App Job) Azure injects a managed identity endpoint → ManagedIdentityCredential is attempted → job has a user-assigned identity and flux supplies no client id → HTTP 400 → chain aborts before AzureCLICredential

So azure/login succeeds and its session is then never consulted.

Why this hasn't surfaced in this repo

All eight workflows here that use this action run on ubuntu-latest:

default-syncroot.yml            dis-apim-release.yml
dis-syncroot-release.yml        dis-identity-release.yml
dis-syncroot-admin-release.yml  dis-pgsql-release.yml
lakmus-release.yml              dis-vault-release.yml

And the self-hosted jobs that do reach altinncr (syncroot-deployment.yml) go through the az CLI (az acr import), which uses its own session and never enters this chain. The two ingredients exist separately here, never together — so this will hit any team that moves a flux workflow onto the self-hosted runners.

The fix

After azure/login, fetch the GitHub OIDC token and point the SDK at the same federated credentials:

AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE

WorkloadIdentityCredential sits ahead of ManagedIdentityCredential in the chain, so it resolves first and the managed identity endpoint is never consulted. Same application, same permissions as today — just selected explicitly rather than by discovery.

Requires permissions: id-token: write on the calling job, which OIDC-based azure/login already required. The step fails with an explicit message if the OIDC endpoint isn't available, rather than silently falling back to the broken path.

Verification

Against flux 2.6.4 in gh-runner:v0.10.0, with a mock Container Apps identity endpoint returning the same 400:

Without the change — chain stops at managed identity, matching the reported failure:

EnvironmentCredential: missing environment variable AZURE_TENANT_ID
WorkloadIdentityCredential: no client ID specified
ManagedIdentityCredential authentication failed.  RESPONSE 400

With the change — workload identity resolves first, managed identity never attempted:

EnvironmentCredential: incomplete environment variable configuration
WorkloadIdentityCredential authentication failed.
    POST https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
    RESPONSE 400

That second 400 is AAD rejecting a deliberately fake token — I have no real federated credential locally — but it confirms the chain reaches workload identity, targets the right tenant, and never falls through to the managed identity endpoint, which is the actual bug.

To be explicit about the limit of that: I have not exercised a real token exchange end to end. The mechanism is verified; the successful-auth path needs a real run. I deliberately ran nothing against this repo's workflows or infrastructure.

Related

Altinn/info.altinn.no#706 unblocks the affected workflow today by clearing the endpoint variables for that job. That's a per-repo workaround; this is the durable fix, and #706 can be reverted once this ships.

🤖 Generated with Claude Code

Altinn/info.altinn.no hit this when moving a workflow that uses
flux/build-push-image onto self-hosted runners:

  error during login with provider: failed to create provider access
  token for the controller: DefaultAzureCredential: failed to acquire a
  token ... ManagedIdentityCredential authentication failed
  RESPONSE 400: Unable to load the proper Managed Identity

flux --provider=azure authenticates through the Azure SDK's
DefaultAzureCredential, which resolves a credential by discovery rather
than using the session azure/login just established, and stops at the
first credential that fails outright.

On GitHub-hosted runners nothing earlier in the chain is available, so it
falls through to the CLI credential and picks up that session. On
self-hosted runners, which are Azure Container App Jobs, a managed
identity endpoint is present, so ManagedIdentityCredential is attempted
instead. It returns HTTP 400 because the job has a user-assigned identity
and flux supplies no client id, and the chain aborts before the CLI
credential is reached.

Every workflow in this repository that uses this action runs on
ubuntu-latest, which is why it has not surfaced here. Self-hosted jobs
that reach altinncr do so through the az CLI, which uses its own session
and never enters this chain. Any team moving a flux workflow onto the
self-hosted runners will hit it.

Handing the SDK the same federated credentials azure/login uses makes
WorkloadIdentityCredential resolve first, so flux authenticates as the
same application regardless of runner type. This requires
'permissions: id-token: write' on the calling job, which OIDC-based
azure/login already required.

Verified against flux 2.6.4 with a mock Container Apps identity endpoint:
without these variables the chain stops at ManagedIdentityCredential with
the 400 above, and with them set the chain resolves
WorkloadIdentityCredential first and never reaches managed identity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@arealmaas
arealmaas requested a review from a team as a code owner August 20, 2026 09:44
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39f0e3e9-3622-404a-a9d4-17199e8733a0


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants