Skip to content

auth/azure: use ACR-scoped token for registry authentication#1130

Merged
matheuscscp merged 1 commit intofluxcd:mainfrom
kukacz:fix-acr-scope-auth
Feb 26, 2026
Merged

auth/azure: use ACR-scoped token for registry authentication#1130
matheuscscp merged 1 commit intofluxcd:mainfrom
kukacz:fix-acr-scope-auth

Conversation

@kukacz
Copy link
Copy Markdown
Contributor

@kukacz kukacz commented Feb 23, 2026

Summary

This PR fixes Azure Container Registry authentication for workload identity scenarios by requesting an ACR-scoped token instead of an ARM-scoped token.

Problem

The token audience was derived from ARM (https://management.azure.com/.default), which is rejected when ACR authentication-as-arm is disabled (recommended by Microsoft for least-privilege security).

Change

  • Switch token scope to https://containerregistry.azure.net/.default.
  • Populate ACR service config in cloud environment setup so custom environment file paths also resolve the correct ACR scope.

Impact

Prevents unauthorized errors against ACR when ARM audience authentication is disabled.

Notes

First contribution from me to this repository. I used AI assistance to help construct the fix, and I manually reviewed and tested the final changes.

@kukacz kukacz requested a review from a team as a code owner February 23, 2026 08:51
@stefanprodan
Copy link
Copy Markdown
Member

We need an Azure engineer to review this change. @dipti-pai can you please take a look?

@dipti-pai
Copy link
Copy Markdown
Member

dipti-pai commented Feb 24, 2026

From the docs, this scenario is supported. Validated from azure-go-sdk that the constant is defined here and that the value is same for the 3 Azure clouds.

@kukacz, Can you confirm that you tested this with container registry accepting the ACR-scoped token only and the broader ARM-scoped token (default). It will help if you paste the screenshot from your test results.

For custom configuration, I did not find the override, we can skip the changes there, i.e fallback to ARM scope and take the ACR scope change as a separate change if/when this is requested. The primary reason for this is it is hard to validate the correctness without running it in the custom cloud environment.

@kukacz kukacz force-pushed the fix-acr-scope-auth branch 2 times, most recently from 3853030 to 4664b1d Compare February 25, 2026 09:22
@kukacz
Copy link
Copy Markdown
Contributor Author

kukacz commented Feb 25, 2026

@dipti-pai Thanks for your comments! I removed the custom configuration part and substituted it with fall back to ARM-scoped token for backwards compatibility.

@kukacz, Can you confirm that you tested this with container registry accepting the ACR-scoped token only and the broader ARM-scoped token (default). It will help if you paste the screenshot from your test results.

Sure, attaching 3 screenshots illustrating different test scenarios:

  1. Pull from OCI repository with authentication-as-arm enabled on ACR. Flux source-controller patched.
    test1-arm-enabled
  2. Pull the same with authentication-as-arm disabled on ACR. Flux source-controller patched.
    test2-arm-disabled
  3. Pull with authentication-as-arm disabled and unpatched source-controller release 1.7.3.
    test3-arm-disabled-unpatched

@dipti-pai
Copy link
Copy Markdown
Member

Thank you @kukacz. The changes LGTM. Another small request for completeness - could you run the integration tests defined here, workload identity test scenarios are covered for Azure in these tests.

The pre-requisites for Azure and the steps to run the test are described in the README. Let us know if you hit any issues running these. Thanks again!

@matheuscscp
Copy link
Copy Markdown
Member

Thank you @kukacz. The changes LGTM. Another small request for completeness - could you run the integration tests defined here, workload identity test scenarios are covered for Azure in these tests.

The pre-requisites for Azure and the steps to run the test are described in the README. Let us know if you hit any issues running these. Thanks again!

I cloned the branch and pushed to the fluxcd/pkg repo, tests running here:

https://github.com/fluxcd/pkg/actions/runs/22434632076

@kukacz
Copy link
Copy Markdown
Contributor Author

kukacz commented Feb 26, 2026

Another small request for completeness - could you run the integration tests defined here, workload identity test scenarios are covered for Azure in these tests.

Sure, I ran the integration tests with workload identity enabled - few of them skipped, the rest passed:

lukas in ~/src/fluxcd/pkg on   pr/kukacz/1130 
❯  cd /home/lukas/src/fluxcd/pkg/tests/integration && source .env && make test-a
zure 2>&1 | tee /tmp/azure-integration-test-full.log
make test PROVIDER_ARG="-provider azure"
make[1]: Entering directory '/home/lukas/src/fluxcd/pkg/tests/integration'
docker image inspect fluxcd/testapp:test >/dev/null
TEST_IMG=fluxcd/testapp:test go test -timeout 50m -v ./ -run "^.*"  -provider azure --tags=integration
2026/02/26 09:09:39 Terraform binary:  /home/lukas/.local/share/mise/installs/terraform/1.14.6/terraform
2026/02/26 09:09:39 Init Terraform
2026/02/26 09:09:42 Checking for an empty Terraform state
2026/02/26 09:09:43 Applying Terraform
2026/02/26 09:18:18 pushing flux test image fluxtestfineromite.azurecr.io/app:test
2026/02/26 09:18:24 push oci test images
2026/02/26 09:18:26 pushing test image fluxtestfineromite.azurecr.io/xvnvx:v0.1.0
2026/02/26 09:18:28 pushing test image fluxtestfineromite.azurecr.io/xvnvx:v0.1.2
2026/02/26 09:18:29 pushing test image fluxtestfineromite.azurecr.io/xvnvx:v0.1.3
2026/02/26 09:18:29 pushing test image fluxtestfineromite.azurecr.io/xvnvx:v0.1.4
2026/02/26 09:18:30 Git is enabled, granting permissions to workload identity to access repository
2026/02/26 09:18:34 Added service principal entitlement!
2026/02/26 09:18:35 Workload identity is enabled, initializing service account with annotations
=== RUN   TestGitCloneUsingProvider
=== RUN   TestGitCloneUsingProvider/controller-level_workload_identity
=== RUN   TestGitCloneUsingProvider/object-level_workload_identity_(impersonation)
=== RUN   TestGitCloneUsingProvider/object-level_workload_identity_(direct_access)
    git_test.go:65: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestGitCloneUsingProvider/object-level_workload_identity_(impersonation,_federation)
    git_test.go:65: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestGitCloneUsingProvider/object-level_workload_identity_(direct_access,_federation)
    git_test.go:65: Skipping test, feature not supported by the provider or by the current cluster configuration
--- PASS: TestGitCloneUsingProvider (21.64s)
    --- PASS: TestGitCloneUsingProvider/controller-level_workload_identity (15.09s)
    --- PASS: TestGitCloneUsingProvider/object-level_workload_identity_(impersonation) (6.55s)
    --- SKIP: TestGitCloneUsingProvider/object-level_workload_identity_(direct_access) (0.00s)
    --- SKIP: TestGitCloneUsingProvider/object-level_workload_identity_(impersonation,_federation) (0.00s)
    --- SKIP: TestGitCloneUsingProvider/object-level_workload_identity_(direct_access,_federation) (0.00s)
=== RUN   TestGitCloneUsingSSH
=== RUN   TestGitCloneUsingSSH/Git_ssh_credential_test
--- PASS: TestGitCloneUsingSSH (8.37s)
    --- PASS: TestGitCloneUsingSSH/Git_ssh_credential_test (8.24s)
=== RUN   TestOciImageRepositoryListTags
=== RUN   TestOciImageRepositoryListTags/node-level_or_controller-level_workload_identity
=== RUN   TestOciImageRepositoryListTags/node-level_or_controller-level_workload_identity/acr
=== RUN   TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation)
=== RUN   TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation)/acr
=== RUN   TestOciImageRepositoryListTags/object-level_workload_identity_(direct_access)
    oci_test.go:69: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation,_federation)
    oci_test.go:69: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestOciImageRepositoryListTags/object-level_workload_identity_(direct_access,_federation)
    oci_test.go:69: Skipping test, feature not supported by the provider or by the current cluster configuration
--- PASS: TestOciImageRepositoryListTags (16.51s)
    --- PASS: TestOciImageRepositoryListTags/node-level_or_controller-level_workload_identity (8.24s)
        --- PASS: TestOciImageRepositoryListTags/node-level_or_controller-level_workload_identity/acr (8.24s)
    --- PASS: TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation) (8.26s)
        --- PASS: TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation)/acr (8.26s)
    --- SKIP: TestOciImageRepositoryListTags/object-level_workload_identity_(direct_access) (0.00s)
    --- SKIP: TestOciImageRepositoryListTags/object-level_workload_identity_(impersonation,_federation) (0.00s)
    --- SKIP: TestOciImageRepositoryListTags/object-level_workload_identity_(direct_access,_federation) (0.00s)
=== RUN   TestOciRepositoryRootLoginListTags
=== RUN   TestOciRepositoryRootLoginListTags/acr
--- PASS: TestOciRepositoryRootLoginListTags (4.17s)
    --- PASS: TestOciRepositoryRootLoginListTags/acr (4.17s)
=== RUN   TestOciOIDCLoginListTags
=== RUN   TestOciOIDCLoginListTags/acr
--- PASS: TestOciOIDCLoginListTags (10.38s)
    --- PASS: TestOciOIDCLoginListTags/acr (10.38s)
=== RUN   TestRESTConfig
=== RUN   TestRESTConfig/controller-level_workload_identity
=== RUN   TestRESTConfig/object-level_workload_identity_(impersonation)
=== RUN   TestRESTConfig/object-level_workload_identity_(direct_access)
    restconfig_test.go:63: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestRESTConfig/object-level_workload_identity_(impersonation,_federation)
    restconfig_test.go:63: Skipping test, feature not supported by the provider or by the current cluster configuration
=== RUN   TestRESTConfig/object-level_workload_identity_(direct_access,_federation)
    restconfig_test.go:63: Skipping test, feature not supported by the provider or by the current cluster configuration
--- PASS: TestRESTConfig (14.45s)
    --- PASS: TestRESTConfig/controller-level_workload_identity (6.22s)
    --- PASS: TestRESTConfig/object-level_workload_identity_(impersonation) (8.23s)
    --- SKIP: TestRESTConfig/object-level_workload_identity_(direct_access) (0.00s)
    --- SKIP: TestRESTConfig/object-level_workload_identity_(impersonation,_federation) (0.00s)
    --- SKIP: TestRESTConfig/object-level_workload_identity_(direct_access,_federation) (0.00s)
PASS
2026/02/26 09:19:52 Destroying environment...
ok      github.com/fluxcd/pkg/tests/integration 858.311s
make[1]: Leaving directory '/home/lukas/src/fluxcd/pkg/tests/integration'

@matheuscscp
Copy link
Copy Markdown
Member

@kukacz Please rebase and force-push 🙏

@dipti-pai Is this something we should backport to a Flux 2.8 patch?

@kukacz kukacz force-pushed the fix-acr-scope-auth branch from 4664b1d to c8ad0ff Compare February 26, 2026 09:08
@kukacz
Copy link
Copy Markdown
Contributor Author

kukacz commented Feb 26, 2026

@kukacz Please rebase and force-push 🙏

Rebased.

Copy link
Copy Markdown
Member

@matheuscscp matheuscscp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🚀

@kukacz Thanks very much!

@dipti-pai @stefanprodan I think this should be backported to 2.8 in the next patch, let me know your thoughts 🙏

@stefanprodan stefanprodan added area/security Security related issues and pull requests backport:flux/v2.8.x To be backported to flux/v2.8.x labels Feb 26, 2026
Copy link
Copy Markdown
Member

@stefanprodan stefanprodan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

We'll backport this and include it in the next Flux 2.8 patch release

@matheuscscp
Copy link
Copy Markdown
Member

Thank you @kukacz. The changes LGTM. Another small request for completeness - could you run the integration tests defined here, workload identity test scenarios are covered for Azure in these tests.
The pre-requisites for Azure and the steps to run the test are described in the README. Let us know if you hit any issues running these. Thanks again!

I cloned the branch and pushed to the fluxcd/pkg repo, tests running here:

https://github.com/fluxcd/pkg/actions/runs/22434632076

These passed. Thanks @kukacz for running them as well 👍

@stefanprodan
Copy link
Copy Markdown
Member

Let's wait for @dipti-pai to stamp this PR, then we do the backport.

@dipti-pai
Copy link
Copy Markdown
Member

LGTM. Thanks @kukacz ☺️

@stefanprodan
Copy link
Copy Markdown
Member

@kukacz one final rebase please, so we can merge

@kukacz kukacz force-pushed the fix-acr-scope-auth branch from c8ad0ff to ac2e88e Compare February 26, 2026 15:57
The access token scope for Azure Container Registry was derived
from cloud.ResourceManager, which resolves to the ARM endpoint
(e.g. https://management.azure.com/.default\). This is an ARM-scoped
token, not an ACR-scoped one.

Microsoft recommends disabling ARM audience authentication on ACR
registries for enhanced security and least-privilege compliance.
When organizations follow this recommendation and disable
authentication-as-arm, Flux's ARM-scoped tokens are rejected,
causing unauthorized errors for workload identity users.

Use azcontainerregistry.ServiceName audience instead, which
resolves to https://containerregistry.azure.net/.default -- the
correct ACR scope as documented by Microsoft:
https://learn.microsoft.com/en-us/azure/container-registry/container-registry-disable-authentication-as-arm

Signed-off-by: Lukáš Kubín <lukas.kubin@gmail.com>
@kukacz kukacz force-pushed the fix-acr-scope-auth branch from ac2e88e to 68b8866 Compare February 26, 2026 16:10
@kukacz
Copy link
Copy Markdown
Contributor Author

kukacz commented Feb 26, 2026

@kukacz one final rebase please, so we can merge

Of course. Rebased now, with commit message update only.

@matheuscscp matheuscscp merged commit 0dd5550 into fluxcd:main Feb 26, 2026
13 checks passed
@fluxcdbot
Copy link
Copy Markdown
Member

Successfully created backport PR for flux/v2.8.x:

@matheuscscp
Copy link
Copy Markdown
Member

@kukacz This fix is scheduled for a Flux patch release on March 12

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

Labels

area/security Security related issues and pull requests backport:flux/v2.8.x To be backported to flux/v2.8.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants