Skip to content

feat: add Azure DevOps OIDC detector#301

Merged
cloudsmith-iduffy merged 2 commits into
masterfrom
iduffy/azure-devops
Jun 10, 2026
Merged

feat: add Azure DevOps OIDC detector#301
cloudsmith-iduffy merged 2 commits into
masterfrom
iduffy/azure-devops

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffy cloudsmith-iduffy commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an Azure DevOps environment detector to the OIDC credential auto-discovery chain, mirroring the existing AWS (#276) and GitHub Actions (#300) detectors. When running in an Azure DevOps pipeline, the CLI fetches an OIDC JWT from the SYSTEM_OIDCREQUESTURI endpoint using the pipeline's SYSTEM_ACCESSTOKEN and exchanges it for a short-lived Cloudsmith API token.

  • Detects via SYSTEM_OIDCREQUESTURI + SYSTEM_ACCESSTOKEN
  • Empty POST to {SYSTEM_OIDCREQUESTURI}?api-version=7.1 with Authorization: Bearer $SYSTEM_ACCESSTOKEN and X-TFS-FedAuthRedirect: Suppress, reading the JWT from the oidcToken field — matching the Azure SDK's AzurePipelinesCredential
  • Ordered before the AWS detector so an Azure pipeline with incidental AWS credentials still authenticates as Azure DevOps
  • Needs no extra dependencies (plain HTTP)

Note: SYSTEM_ACCESSTOKEN is not exposed to steps by default — the pipeline must map it into the environment (e.g. env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)).

Two behaviours confirmed empirically against a real pipeline

  • api-version is required. Without it the endpoint returns HTTP 400: "No api-version was supplied"; with ?api-version=7.1 it returns HTTP 200. (Initial commit omitted it; fixed.)
  • The audience is not caller-configurable. Azure DevOps always mints the token with a fixed audience (api://AzureADTokenExchange) and ignores any requested audience — verified by requesting audience=cloudsmith and decoding the returned JWT. So the detector sends no audience body, and the Cloudsmith OIDC provider must expect api://AzureADTokenExchange. The token's sub is p://{org}/{project}/{pipeline} and iss is https://vstoken.dev.azure.com/{accountId}.

Testing

Unit tests cover detection (all env permutations), token extraction, the ?api-version=7.1 URL (incl. & when the URI already has a query), the empty body + auth/X-TFS-FedAuthRedirect headers, that a custom audience is ignored, and the missing-token error.

Live verification: end-to-end run on a real Azure DevOps pipeline (org iduffy-demo, project cloudsmith-oidc-test, service slug azure-devops-gqgp), installing the CLI from this branch on a self-hosted agent:

➡️ Azure DevOps build #29 (project is private; reviewers have been granted access)

CI log — OIDC smoke test step (build #29)
Collecting git+https://github.com/cloudsmith-io/cloudsmith-cli.git@iduffy/azure-devops
  Resolved https://github.com/cloudsmith-io/cloudsmith-cli.git to commit a8d4f82
[... dependency resolution / install ...]

=== whoami: OIDC auto-discovery ===
Retrieving your authentication status from the API ... OK

User: azure-devops (slug: azure-devops-gqgp)

Authentication Method: OIDC Auto-Discovery
  Source: OIDC via Azure DevOps (org: iduffy-demo, service: azure-devops-gqgp)
  Token Slug: exhuQQK1QUgD
  Created: 2026-06-08T15:21:43.180264Z

SSO Status: Not configured
  Keyring: Enabled (no tokens stored)

=== authenticated API call ===
Getting list of repositories ... OK
[... 11 repositories listed ...]
Results: 1-11 (11) of 11 repositories visible (page: 1/1, page size: 30)

References:

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 8, 2026 15:10
@cloudsmith-iduffy cloudsmith-iduffy requested a review from a team as a code owner June 8, 2026 15:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds an Azure DevOps environment detector to the OIDC credential auto-discovery chain so the CLI can fetch an Azure-issued OIDC JWT (via SYSTEM_OIDCREQUESTURI + SYSTEM_ACCESSTOKEN) and exchange it for a short-lived Cloudsmith API token.

Changes:

  • Added AzureDevOpsDetector that POSTs {"audience": ...} to SYSTEM_OIDCREQUESTURI using Authorization: Bearer $SYSTEM_ACCESSTOKEN, extracting oidcToken from the JSON response.
  • Updated the detector registry to prefer Azure DevOps detection ahead of AWS.
  • Added unit tests and README documentation for Azure DevOps OIDC support.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
README.md Documents Azure DevOps OIDC environment requirements and links to the integration guide.
cloudsmith_cli/core/tests/test_azure_devops_detector.py Adds unit tests covering detection and token retrieval behavior for Azure DevOps.
cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py Implements the Azure DevOps OIDC detector and token fetch via HTTP POST.
cloudsmith_cli/core/credentials/oidc/detectors/init.py Registers Azure DevOps detector before AWS in the detection order.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py
@cloudsmith-iduffy cloudsmith-iduffy marked this pull request as draft June 8, 2026 15:35
@cloudsmith-iduffy

Copy link
Copy Markdown
Contributor Author

Verified in a real Azure DevOps pipeline ✅ (and fixed a bug it surfaced)

Tested end-to-end on a self-hosted agent in the cloudsmith-oidc-test Azure DevOps project, installing the CLI from this branch and running OIDC auto-discovery against iduffy-demo / azure-devops-gqgp.

The test caught a real bug: the original detector POSTed to the bare SYSTEM_OIDCREQUESTURI, which Azure rejects:

HTTP 400: "No api-version was supplied for the POST request."

Adding ?api-version=7.1 (commit b311068) fixes it — the same request then returns HTTP 200. After the fix, the full round-trip succeeds:

=== Phase 1: detection + OIDC token fetch (debug) ===
User: azure-devops (slug: azure-devops-gqgp)
=== Phase 2: full auth round-trip ===
Authentication Method: OIDC Auto-Discovery
  Source: OIDC [cached] (org: iduffy-demo, service: azure-devops-gqgp)
  Token Slug: bIblouD9Syie
Getting list of repositories ... OK
Results: 1-11 (11) of 11 repositories visible

One behavioural note for reviewers: Azure DevOps ignores the requested audience — the minted token's aud is always api://AzureADTokenExchange regardless of the {"audience": ...} body. I confirmed this by requesting audience=cloudsmith and decoding the returned JWT (still api://AzureADTokenExchange). Sending the audience body is therefore harmless but a no-op on Azure's side; the Cloudsmith OIDC provider must be configured to expect api://AzureADTokenExchange. The token's sub is p://{org}/{project}/{pipeline} and iss is https://vstoken.dev.azure.com/{accountId}.

@cloudsmith-iduffy cloudsmith-iduffy marked this pull request as ready for review June 8, 2026 16:24
Add Azure DevOps to OIDC credential auto-discovery. When running in an
Azure DevOps pipeline, the CLI fetches an OIDC token from the
SYSTEM_OIDCREQUESTURI endpoint using the pipeline's SYSTEM_ACCESSTOKEN and
exchanges it for a Cloudsmith access token. The api-version query parameter
is always supplied (the endpoint returns HTTP 400 without it), and no
request body is sent because Azure DevOps mints a fixed audience
(api://AzureADTokenExchange) and ignores any requested audience.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudsmith-iduffy cloudsmith-iduffy merged commit 19b865b into master Jun 10, 2026
40 checks passed
@cloudsmith-iduffy cloudsmith-iduffy deleted the iduffy/azure-devops branch June 10, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants