feat: add Azure DevOps OIDC detector#301
Conversation
There was a problem hiding this comment.
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
AzureDevOpsDetectorthat POSTs{"audience": ...}toSYSTEM_OIDCREQUESTURIusingAuthorization: Bearer $SYSTEM_ACCESSTOKEN, extractingoidcTokenfrom 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.
Verified in a real Azure DevOps pipeline ✅ (and fixed a bug it surfaced)Tested end-to-end on a self-hosted agent in the The test caught a real bug: the original detector POSTed to the bare Adding One behavioural note for reviewers: Azure DevOps ignores the requested audience — the minted token's |
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>
d3f5743 to
4b3c60d
Compare
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_OIDCREQUESTURIendpoint using the pipeline'sSYSTEM_ACCESSTOKENand exchanges it for a short-lived Cloudsmith API token.SYSTEM_OIDCREQUESTURI+SYSTEM_ACCESSTOKENPOSTto{SYSTEM_OIDCREQUESTURI}?api-version=7.1withAuthorization: Bearer $SYSTEM_ACCESSTOKENandX-TFS-FedAuthRedirect: Suppress, reading the JWT from theoidcTokenfield — matching the Azure SDK'sAzurePipelinesCredentialTwo behaviours confirmed empirically against a real pipeline
api-versionis required. Without it the endpoint returnsHTTP 400: "No api-version was supplied"; with?api-version=7.1it returnsHTTP 200. (Initial commit omitted it; fixed.)api://AzureADTokenExchange) and ignores any requested audience — verified by requestingaudience=cloudsmithand decoding the returned JWT. So the detector sends no audience body, and the Cloudsmith OIDC provider must expectapi://AzureADTokenExchange. The token'ssubisp://{org}/{project}/{pipeline}andissishttps://vstoken.dev.azure.com/{accountId}.Testing
Unit tests cover detection (all env permutations), token extraction, the
?api-version=7.1URL (incl.&when the URI already has a query), the empty body + auth/X-TFS-FedAuthRedirectheaders, 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, projectcloudsmith-oidc-test, service slugazure-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)
References:
🤖 Generated with Claude Code