From d42e1bf8b87258a5a1bc60156e9d6250b799fd33 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Sat, 14 Feb 2026 19:31:23 +0100 Subject: [PATCH] Added AI Generate Azure DevOps Task. --- .gitignore | 5 + README.md | 65 ++++ examples/azure-pipelines-smoke.yml | 41 ++ images/icon.png | Bin 0 -> 16503 bytes images/icon.svg | 39 ++ scripts/build.sh | 17 + scripts/publish.sh | 26 ++ task/AzureFederatedAuth/package-lock.json | 454 ++++++++++++++++++++++ task/AzureFederatedAuth/package.json | 18 + task/AzureFederatedAuth/src/index.ts | 182 +++++++++ task/AzureFederatedAuth/task.json | 40 ++ task/AzureFederatedAuth/tsconfig.json | 14 + vss-extension.json | 41 ++ 13 files changed, 942 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 examples/azure-pipelines-smoke.yml create mode 100644 images/icon.png create mode 100644 images/icon.svg create mode 100755 scripts/build.sh create mode 100755 scripts/publish.sh create mode 100644 task/AzureFederatedAuth/package-lock.json create mode 100644 task/AzureFederatedAuth/package.json create mode 100644 task/AzureFederatedAuth/src/index.ts create mode 100644 task/AzureFederatedAuth/task.json create mode 100644 task/AzureFederatedAuth/tsconfig.json create mode 100644 vss-extension.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10d07d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +*.vsix +task/AzureFederatedAuth/dist/ +build/ +AGENTS.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6bec3a --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Azure DevOps Azure Federated Auth Task + +Private Azure DevOps extension with a single task: `AzureFederatedAuth@1`. + +The task requests an OIDC token for a selected AzureRM service connection and exports: + +- `ARM_OIDC_TOKEN` (secret) +- `ARM_TENANT_ID` +- `ARM_CLIENT_ID` +- `GIT_ACCESS_TOKEN` (secret, optional) + +## Requirements + +- Linux agents (YAML pipelines) +- Job setting that exposes OAuth token (`System.AccessToken`) +- AzureRM service connection with workload identity federation +- Visual Studio Marketplace publisher account (required to publish/share this extension, even for private org-only usage) + +## Build + +```bash +./scripts/build.sh +``` + +This builds the TypeScript task and creates a `.vsix` extension package in `build/`. + +## Publish privately + +Publishing (CLI or Web UI) uses the same model: +- Upload extension version under a Visual Studio Marketplace publisher +- Share that published extension with your Azure DevOps organization(s) + +There is no direct local `.vsix` install path to an org that bypasses the publisher model. + +```bash +AZDO_PAT='' ./scripts/publish.sh +``` + +Example: + +```bash +AZDO_PAT="$AZDO_PAT" ./scripts/publish.sh ./build/private.azuredevops-get-oidc-token-task-1.0.0.vsix private org-a org-b org-c +``` + +### Manual publish (Web UI) + +You can publish the generated `.vsix` manually in the Visual Studio Marketplace publisher portal: + +1. Build/package first (`./scripts/build.sh`) and note the `.vsix` path. +2. Open your publisher in Visual Studio Marketplace. +3. Upload the `.vsix` as a new extension version. +4. Share the published extension with the target Azure DevOps organization(s). + +## YAML usage + +```yaml +- task: AzureFederatedAuth@1 + inputs: + serviceConnectionARM: 'my-arm-service-connection' + setGitAccessToken: true +``` + +See `examples/azure-pipelines-smoke.yml` for a full smoke validation pipeline. + +When `setGitAccessToken: true`, the task exchanges the OIDC assertion against Entra ID and requests scope `499b84ac-1321-427f-aa17-267ca6975798/.default`, then sets `GIT_ACCESS_TOKEN`. diff --git a/examples/azure-pipelines-smoke.yml b/examples/azure-pipelines-smoke.yml new file mode 100644 index 0000000..c50cb63 --- /dev/null +++ b/examples/azure-pipelines-smoke.yml @@ -0,0 +1,41 @@ +trigger: none +pr: none + +pool: + vmImage: ubuntu-latest + +steps: + - checkout: self + + - task: AzureFederatedAuth@1 + displayName: Get ARM OIDC token + inputs: + serviceConnectionARM: 'my-arm-service-connection' + setGitAccessToken: true + + - bash: | + set -euo pipefail + if [[ -z "${ARM_OIDC_TOKEN:-}" ]]; then + echo "ARM_OIDC_TOKEN is missing" + exit 1 + fi + + if [[ -z "${ARM_TENANT_ID:-}" ]]; then + echo "ARM_TENANT_ID is missing" + exit 1 + fi + + if [[ -z "${ARM_CLIENT_ID:-}" ]]; then + echo "ARM_CLIENT_ID is missing" + exit 1 + fi + + if [[ -z "${GIT_ACCESS_TOKEN:-}" ]]; then + echo "GIT_ACCESS_TOKEN is missing" + exit 1 + fi + + echo "ARM variables are populated." + echo "Tenant: $ARM_TENANT_ID" + echo "Client: $ARM_CLIENT_ID" + displayName: Validate exported variables diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dca403f93245f7400a31b4e96dd688f3807384fc GIT binary patch literal 16503 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 F{2$ceJB0uM literal 0 HcmV?d00001 diff --git a/images/icon.svg b/images/icon.svg new file mode 100644 index 0000000..695efbf --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..8af146c --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TASK_DIR="$ROOT_DIR/task/AzureFederatedAuth" +BUILD_DIR="$ROOT_DIR/build" + +cd "$TASK_DIR" +rm -rf dist node_modules +npm ci +npm run build + +cd "$ROOT_DIR" +mkdir -p "$BUILD_DIR" +npx tfx-cli extension create \ + --manifest-globs vss-extension.json \ + --output-path "$BUILD_DIR" diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 0000000..83717d8 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 3 ]]; then + echo "Usage: $0 [org2] [org3] ..." + echo "Requires environment variable AZDO_PAT to be set." + exit 1 +fi + +if [[ -z "${AZDO_PAT:-}" ]]; then + echo "AZDO_PAT is not set." + exit 1 +fi + +VSIX_PATH="$1" +PUBLISHER_ID="$2" +shift 2 + +for ORG in "$@"; do + echo "Publishing to organization: $ORG" + npx tfx-cli extension publish \ + --vsix "$VSIX_PATH" \ + --publisher "$PUBLISHER_ID" \ + --token "$AZDO_PAT" \ + --share-with "$ORG" +done diff --git a/task/AzureFederatedAuth/package-lock.json b/task/AzureFederatedAuth/package-lock.json new file mode 100644 index 0000000..6e825f0 --- /dev/null +++ b/task/AzureFederatedAuth/package-lock.json @@ -0,0 +1,454 @@ +{ + "name": "azure-federated-auth-task", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-federated-auth-task", + "version": "1.0.0", + "dependencies": { + "azure-pipelines-task-lib": "^4.10.2" + }, + "devDependencies": { + "@types/node": "^20.11.30", + "typescript": "^5.4.5" + } + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/azure-pipelines-task-lib": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.3.tgz", + "integrity": "sha512-UxfH5pk3uOHTi9TtLtdDyugQVkFES5A836ZEePjcs3jYyxm3EJ6IlFYq6gbfd6mNBhrM9fxG2u/MFYIJ+Z0cxQ==", + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.10", + "minimatch": "3.0.5", + "nodejs-file-downloader": "^4.11.1", + "q": "^1.5.1", + "semver": "^5.7.2", + "shelljs": "^0.8.5", + "uuid": "^3.0.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nodejs-file-downloader": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/nodejs-file-downloader/-/nodejs-file-downloader-4.13.0.tgz", + "integrity": "sha512-nI2fKnmJWWFZF6SgMPe1iBodKhfpztLKJTtCtNYGhm/9QXmWa/Pk9Sv00qHgzEvNLe1x7hjGDRor7gcm/ChaIQ==", + "license": "ISC", + "dependencies": { + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/task/AzureFederatedAuth/package.json b/task/AzureFederatedAuth/package.json new file mode 100644 index 0000000..79a5245 --- /dev/null +++ b/task/AzureFederatedAuth/package.json @@ -0,0 +1,18 @@ +{ + "name": "azure-federated-auth-task", + "version": "1.0.0", + "private": true, + "description": "Azure DevOps private task to fetch OIDC token for AzureRM service connection.", + "main": "dist/index.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "clean": "rm -rf dist" + }, + "dependencies": { + "azure-pipelines-task-lib": "^4.10.2" + }, + "devDependencies": { + "@types/node": "^20.11.30", + "typescript": "^5.4.5" + } +} diff --git a/task/AzureFederatedAuth/src/index.ts b/task/AzureFederatedAuth/src/index.ts new file mode 100644 index 0000000..4393835 --- /dev/null +++ b/task/AzureFederatedAuth/src/index.ts @@ -0,0 +1,182 @@ +import * as crypto from 'crypto'; +import * as tl from 'azure-pipelines-task-lib/task'; + +type OidcResponse = { + oidcToken?: string; +}; + +type EntraTokenResponse = { + access_token?: string; + error?: string; + error_description?: string; +}; + +const AZDO_APP_SCOPE = '499b84ac-1321-427f-aa17-267ca6975798/.default'; +const CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; + +function requireVariable(name: string): string { + const value = tl.getVariable(name); + if (!value) { + throw new Error(`Missing required pipeline variable: ${name}.`); + } + + return value; +} + +function getServiceConnectionMetadata(endpointId: string): { tenantId: string; clientId: string } { + const tenantId = + tl.getEndpointAuthorizationParameter(endpointId, 'tenantid', true) || + tl.getEndpointDataParameter(endpointId, 'tenantid', true); + + const clientId = + tl.getEndpointAuthorizationParameter(endpointId, 'serviceprincipalid', true) || + tl.getEndpointAuthorizationParameter(endpointId, 'clientid', true) || + tl.getEndpointDataParameter(endpointId, 'serviceprincipalid', true); + + if (!tenantId) { + throw new Error('Could not resolve tenant ID from the selected AzureRM service connection.'); + } + + if (!clientId) { + throw new Error('Could not resolve client ID from the selected AzureRM service connection.'); + } + + return { tenantId, clientId }; +} + +function buildOidcUrl(baseUrl: string, serviceConnectionId: string): string { + const url = new URL(baseUrl); + url.searchParams.set('api-version', '7.1'); + url.searchParams.set('serviceConnectionId', serviceConnectionId); + return url.toString(); +} + +function isJwtLike(value: string): boolean { + const parts = value.split('.'); + return parts.length === 3 && parts.every((part) => part.length > 0); +} + +async function requestOidcToken(requestUrl: string, accessToken: string): Promise { + const response = await fetch(requestUrl, { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Content-Length': '0' + } + }); + + if (!response.ok) { + const responseBody = await response.text(); + throw new Error( + `OIDC request failed with status ${response.status} ${response.statusText}. Response: ${responseBody}` + ); + } + + const data = (await response.json()) as OidcResponse; + const token = data.oidcToken?.trim(); + + if (!token) { + throw new Error('OIDC response did not include a non-empty oidcToken field.'); + } + + if (!isJwtLike(token)) { + throw new Error('OIDC token format is invalid (expected JWT).'); + } + + return token; +} + +async function exchangeOidcForAzureDevOpsToken( + tenantId: string, + clientId: string, + oidcToken: string +): Promise { + const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`; + const body = new URLSearchParams({ + grant_type: 'client_credentials', + client_id: clientId, + scope: AZDO_APP_SCOPE, + client_assertion_type: CLIENT_ASSERTION_TYPE, + client_assertion: oidcToken + }); + + const response = await fetch(tokenUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: body.toString() + }); + + const rawBody = await response.text(); + let data: EntraTokenResponse = {}; + + if (rawBody.trim().length > 0) { + try { + data = JSON.parse(rawBody) as EntraTokenResponse; + } catch { + // Keep rawBody for error details when the response is not JSON. + } + } + + const token = data.access_token?.trim(); + + if (!response.ok) { + const errorDetails = + data.error_description || data.error || rawBody.trim() || 'Unknown token exchange error.'; + throw new Error( + `Failed to exchange OIDC token for Azure DevOps Git token (${response.status} ${response.statusText}): ${errorDetails}` + ); + } + + if (!token) { + throw new Error('Token exchange succeeded but no access_token was returned.'); + } + + return token; +} + +async function run(): Promise { + try { + const endpointId = tl.getInput('serviceConnectionARM', true); + const setGitAccessToken = tl.getBoolInput('setGitAccessToken', false); + if (!endpointId) { + throw new Error('Task input serviceConnectionARM is required.'); + } + + const oidcBaseUrl = requireVariable('System.OidcRequestUri'); + const accessToken = requireVariable('System.AccessToken'); + + console.log('Requesting OIDC token for ARM authentication...'); + + const requestUrl = buildOidcUrl(oidcBaseUrl, endpointId); + const token = await requestOidcToken(requestUrl, accessToken); + const metadata = getServiceConnectionMetadata(endpointId); + + const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); + + tl.setVariable('ARM_OIDC_TOKEN', token, true); + tl.setVariable('ARM_TENANT_ID', metadata.tenantId); + tl.setVariable('ARM_CLIENT_ID', metadata.clientId); + + console.log('Successfully retrieved OIDC token.'); + console.log(`OIDC Token SHA256: ${tokenHash}`); + + if (setGitAccessToken) { + console.log('Exchanging OIDC token for Azure DevOps scoped Git access token...'); + const gitToken = await exchangeOidcForAzureDevOpsToken(metadata.tenantId, metadata.clientId, token); + const gitTokenHash = crypto.createHash('sha256').update(gitToken).digest('hex'); + tl.setVariable('GIT_ACCESS_TOKEN', gitToken, true); + console.log(`GIT Access Token SHA256: ${gitTokenHash}`); + } + + tl.setResult(tl.TaskResult.Succeeded, 'ARM OIDC variables configured.'); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + tl.error(message); + tl.setResult(tl.TaskResult.Failed, `Failed to configure ARM OIDC variables: ${message}`); + } +} + +void run(); diff --git a/task/AzureFederatedAuth/task.json b/task/AzureFederatedAuth/task.json new file mode 100644 index 0000000..16c59f4 --- /dev/null +++ b/task/AzureFederatedAuth/task.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "11c532b8-f2bd-45f8-ac94-3b7e44608cc4", + "name": "AzureFederatedAuth", + "friendlyName": "Azure Federated Auth", + "description": "Requests an OIDC token for an AzureRM service connection and exports ARM_OIDC_TOKEN, ARM_TENANT_ID, ARM_CLIENT_ID, and optionally GIT_ACCESS_TOKEN for Azure DevOps Git HTTPS auth.", + "helpMarkDown": "Private task for YAML pipelines on Linux agents.", + "category": "Deploy", + "author": "private", + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "instanceNameFormat": "Configure Azure federated auth for $(serviceConnectionARM)", + "inputs": [ + { + "name": "serviceConnectionARM", + "type": "connectedService:AzureRM", + "label": "Azure Resource Manager service connection", + "defaultValue": "", + "required": true, + "helpMarkDown": "AzureRM service connection used to request the OIDC token." + }, + { + "name": "setGitAccessToken", + "type": "boolean", + "label": "Also set GIT_ACCESS_TOKEN for Azure DevOps Git HTTPS auth", + "defaultValue": "false", + "required": false, + "helpMarkDown": "When enabled, exchanges the OIDC token for an Entra access token scoped to Azure DevOps (499b84ac-1321-427f-aa17-267ca6975798/.default) and sets secret variable GIT_ACCESS_TOKEN." + } + ], + "execution": { + "Node20_1": { + "target": "dist/index.js" + } + }, + "minimumAgentVersion": "3.225.0" +} diff --git a/task/AzureFederatedAuth/tsconfig.json b/task/AzureFederatedAuth/tsconfig.json new file mode 100644 index 0000000..b82293d --- /dev/null +++ b/task/AzureFederatedAuth/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "Node", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/vss-extension.json b/vss-extension.json new file mode 100644 index 0000000..6071c5b --- /dev/null +++ b/vss-extension.json @@ -0,0 +1,41 @@ +{ + "manifestVersion": 1, + "id": "azuredevops-get-oidc-token-task", + "name": "Azure DevOps AzureFederatedAuth Task", + "version": "1.0.0", + "publisher": "private", + "targets": [ + { + "id": "Microsoft.VisualStudio.Services" + } + ], + "description": "Private Azure DevOps task to request an OIDC token for an AzureRM service connection and expose Terraform ARM variables.", + "categories": [ + "Azure Pipelines" + ], + "public": false, + "icons": { + "default": "images/icon.png" + }, + "files": [ + { + "path": "task/AzureFederatedAuth" + }, + { + "path": "images", + "addressable": true + } + ], + "contributions": [ + { + "id": "c5832ba7-adfc-4723-8bc6-ab6287c78b3d", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "task/AzureFederatedAuth" + } + } + ] +}