From 6047d588623a8e635e1787c0a2e8b2788238196c Mon Sep 17 00:00:00 2001 From: Shiying Chen Date: Thu, 13 Feb 2025 11:20:07 +0800 Subject: [PATCH 01/26] replace the invalid link for github action doc (#510) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76ded9178..615c032f3 100644 --- a/README.md +++ b/README.md @@ -679,7 +679,7 @@ Internally in this action, we use azure CLI and execute `az login` with the cred ### GitHub Action -[GitHub Actions](https://help.github.com/articles/about-github-actions) gives you the flexibility to build an automated software development lifecycle workflow. +[GitHub Actions](https://docs.github.com/actions) gives you the flexibility to build an automated software development lifecycle workflow. ### GitHub Actions for deploying to Azure From 3fcf81c86c263390c805963b85d3887230a5fb1b Mon Sep 17 00:00:00 2001 From: Yan Xu Date: Thu, 13 Feb 2025 13:22:44 +0800 Subject: [PATCH 02/26] Bump braces from 3.0.2 to 3.0.3 (#511) * Bump braces from 3.0.2 to 3.0.3 Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] * remove libicu and update powershell version * apt install libicu72 * change installation url * fix typo --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MoChilia Co-authored-by: Shiying Chen --- .github/workflows/azure-login-positive.yml | 8 ++++---- package-lock.json | 14 +++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/azure-login-positive.yml b/.github/workflows/azure-login-positive.yml index 351f230c0..16d15019c 100644 --- a/.github/workflows/azure-login-positive.yml +++ b/.github/workflows/azure-login-positive.yml @@ -266,10 +266,10 @@ jobs: run: | apt-get update apt-get install -y wget - wget https://mirror.it.ubc.ca/ubuntu/pool/main/i/icu/libicu72_72.1-3ubuntu3_amd64.deb - dpkg -i libicu72_72.1-3ubuntu3_amd64.deb - wget https://github.com/PowerShell/PowerShell/releases/download/v7.4.3/powershell_7.4.3-1.deb_amd64.deb - dpkg -i powershell_7.4.3-1.deb_amd64.deb + wget https://ftp.debian.org/debian/pool/main/i/icu/libicu72_72.1-3_amd64.deb + dpkg -i libicu72_72.1-3_amd64.deb + wget https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/powershell_7.5.0-1.deb_amd64.deb + dpkg -i powershell_7.5.0-1.deb_amd64.deb - name: Check Powershell Version shell: pwsh diff --git a/package-lock.json b/package-lock.json index 47ff5fc3a..d0ef42c4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1278,11 +1278,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1638,7 +1640,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -1839,6 +1843,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -3125,6 +3131,8 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { From 888619bccc33951205242d1f0a8c9a987e175fe7 Mon Sep 17 00:00:00 2001 From: Shiying Chen Date: Thu, 13 Feb 2025 15:45:28 +0800 Subject: [PATCH 03/26] mention allow-no-subscriptions in missing subscriptionId error (#512) --- __tests__/LoginConfig.test.ts | 2 +- src/common/LoginConfig.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/LoginConfig.test.ts b/__tests__/LoginConfig.test.ts index a4933903a..ee5d67b2c 100644 --- a/__tests__/LoginConfig.test.ts +++ b/__tests__/LoginConfig.test.ts @@ -245,7 +245,7 @@ describe("LoginConfig Test", () => { let loginConfig = new LoginConfig(); await loginConfig.initialize(); - testValidateWithErrorMessage(loginConfig, "Ensure subscriptionId is supplied."); + testValidateWithErrorMessage(loginConfig, "Ensure 'subscription-id' is supplied or 'allow-no-subscriptions' is 'true'."); }); test('validate without subscriptionId and allowNoSubscriptionsLogin=true', async () => { diff --git a/src/common/LoginConfig.ts b/src/common/LoginConfig.ts index d3a3e1c74..0f69f0f89 100644 --- a/src/common/LoginConfig.ts +++ b/src/common/LoginConfig.ts @@ -99,7 +99,7 @@ export class LoginConfig { } } if (!this.subscriptionId && !this.allowNoSubscriptionsLogin) { - throw new Error("Ensure subscriptionId is supplied."); + throw new Error("Ensure 'subscription-id' is supplied or 'allow-no-subscriptions' is 'true'."); } } From 877e2b442c408d3806b91c18f37b80fefcca4aa6 Mon Sep 17 00:00:00 2001 From: Shiying Chen Date: Mon, 3 Mar 2025 15:30:34 +0800 Subject: [PATCH 04/26] Log more claims for OIDC login (#520) --- src/common/LoginConfig.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/common/LoginConfig.ts b/src/common/LoginConfig.ts index 0f69f0f89..b9939c588 100644 --- a/src/common/LoginConfig.ts +++ b/src/common/LoginConfig.ts @@ -79,11 +79,16 @@ export class LoginConfig { this.mask(this.federatedToken); } catch (error) { - core.error(`Please make sure to give write permissions to id-token in the workflow.`); + core.error("Failed to fetch federated token from GitHub. Please make sure to give write permissions to id-token in the workflow."); throw error; } - let [issuer, subjectClaim] = await jwtParser(this.federatedToken); - core.info("Federated token details:\n issuer - " + issuer + "\n subject claim - " + subjectClaim); + try { + let [issuer, subjectClaim, audience, jobWorkflowRef] = await jwtParser(this.federatedToken); + core.info("Federated token details:\n issuer - " + issuer + "\n subject claim - " + subjectClaim + "\n audience - " + audience + "\n job_workflow_ref - " + jobWorkflowRef); + } + catch (error) { + core.warning(`Failed to parse the federated token. Error: ${error}`); + } } validate() { @@ -114,5 +119,20 @@ async function jwtParser(federatedToken: string) { let tokenPayload = federatedToken.split('.')[1]; let bufferObj = Buffer.from(tokenPayload, "base64"); let decodedPayload = JSON.parse(bufferObj.toString("utf8")); - return [decodedPayload['iss'], decodedPayload['sub']]; -} + const JWT_CLAIM_ISSUER = 'iss'; + const JWT_CLAIM_SUBJECT = 'sub'; + const JWT_CLAIM_AUDIENCE = 'aud'; + const JWT_CLAIM_JOB_WORKFLOW_REF = 'job_workflow_ref'; + const requiredClaims = [ + JWT_CLAIM_ISSUER, + JWT_CLAIM_SUBJECT, + JWT_CLAIM_AUDIENCE, + JWT_CLAIM_JOB_WORKFLOW_REF + ]; + for (const claim of requiredClaims) { + if (!decodedPayload[claim]) { + throw new Error(`The claim '${claim}' is missing from the token payload`); + } + } + return [decodedPayload[JWT_CLAIM_ISSUER], decodedPayload[JWT_CLAIM_SUBJECT], decodedPayload[JWT_CLAIM_AUDIENCE], decodedPayload[JWT_CLAIM_JOB_WORKFLOW_REF]]; +} From bbcc074a232a35d7283353c18aabf0de1d557775 Mon Sep 17 00:00:00 2001 From: Shiying Chen Date: Tue, 4 Mar 2025 09:39:48 +0800 Subject: [PATCH 05/26] Use `--client-id` for user-assigned managed identity authentication in Azure CLI v2.69.0 or later. (#514) --- src/Cli/AzureCliLogin.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Cli/AzureCliLogin.ts b/src/Cli/AzureCliLogin.ts index 2c4c5f8f0..aa3a95ece 100644 --- a/src/Cli/AzureCliLogin.ts +++ b/src/Cli/AzureCliLogin.ts @@ -8,6 +8,7 @@ export class AzureCliLogin { loginConfig: LoginConfig; azPath: string; loginOptions: ExecOptions; + azVersion: string; constructor(loginConfig: LoginConfig) { this.loginConfig = loginConfig; @@ -30,7 +31,12 @@ export class AzureCliLogin { await this.executeAzCliCommand(["version"], true, execOptions); core.debug(`Azure CLI version used:\n${output}`); - + try { + this.azVersion = JSON.parse(output)["azure-cli"]; + } + catch (error) { + core.warning("Failed to parse Azure CLI version."); + } await this.registerAzurestackEnvIfNecessary(); await this.executeAzCliCommand(["cloud", "set", "-n", this.loginConfig.environment], false); @@ -108,7 +114,20 @@ export class AzureCliLogin { } async loginWithUserAssignedIdentity(args: string[]) { - args.push("--username", this.loginConfig.servicePrincipalId); + let azcliMinorVersion = 0; + try { + azcliMinorVersion = parseInt(this.azVersion.split('.')[1], 10); + } + catch (error) { + core.warning("Failed to parse the minor version of Azure CLI. Assuming the version is less than 2.69.0"); + } + //From Azure-cli v2.69.0, `--username` is replaced with `--client-id`, `--object-id` or `--resource-id`: https://github.com/Azure/azure-cli/pull/30525 + if (azcliMinorVersion < 69) { + args.push("--username", this.loginConfig.servicePrincipalId); + } + else { + args.push("--client-id", this.loginConfig.servicePrincipalId); + } await this.callCliLogin(args, 'user-assigned managed identity'); } From ce6a9ff965c6b99ee966eee159baa8c35e135635 Mon Sep 17 00:00:00 2001 From: Yan Xu Date: Tue, 17 Mar 2026 15:51:08 +0800 Subject: [PATCH 06/26] upgrade nodejs from 20 to 24 and update dependencies (#578) * upgrade nodejs from 20 to 24 and update dependencies * update installation step of ps * update az account count check * upgrade actions/checkout and actions/setup-node from 4 to 6 * remove empty lines --- .github/CODEOWNERS | 2 +- .github/workflows/azure-login-canary.yml | 3 +- .github/workflows/azure-login-negative.yml | 16 +- .github/workflows/azure-login-positive.yml | 37 +-- .github/workflows/azure-login-pr-check.yml | 10 +- .github/workflows/ci.yml | 8 +- .github/workflows/codeql.yml | 2 +- .github/workflows/markdownlint.yml | 6 +- action.yml | 2 +- package-lock.json | 254 +++++++-------------- package.json | 4 +- 11 files changed, 127 insertions(+), 217 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 27a83c107..d3a965c25 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@kaverma @kanika1894 @BALAGA-GAYATRI @pulkitaggarwl +@YanaXu diff --git a/.github/workflows/azure-login-canary.yml b/.github/workflows/azure-login-canary.yml index 5c430f703..eb4ef84de 100644 --- a/.github/workflows/azure-login-canary.yml +++ b/.github/workflows/azure-login-canary.yml @@ -30,7 +30,7 @@ jobs: az --version - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 'Az CLI login with subscription' uses: azure/login@v1 @@ -89,4 +89,3 @@ jobs: - name: Post to slack shell: bash run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}} - diff --git a/.github/workflows/azure-login-negative.yml b/.github/workflows/azure-login-negative.yml index 91dda5801..7998fa08c 100644 --- a/.github/workflows/azure-login-negative.yml +++ b/.github/workflows/azure-login-negative.yml @@ -19,12 +19,12 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: 'Validate build' run: | @@ -82,12 +82,12 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: 'Validate build' run: | diff --git a/.github/workflows/azure-login-positive.yml b/.github/workflows/azure-login-positive.yml index 16d15019c..ecb102f84 100644 --- a/.github/workflows/azure-login-positive.yml +++ b/.github/workflows/azure-login-positive.yml @@ -18,12 +18,12 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: 'Validate build' run: | @@ -110,12 +110,12 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: 'Validate build' run: | @@ -203,7 +203,7 @@ jobs: - name: Run Azure Cli shell: pwsh run: | - $checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 3 + $checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 2 if(-not $checkResult){ throw "Not all checks passed!" } @@ -245,12 +245,12 @@ jobs: environment: Automation test steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: Install Azure CLI run: | @@ -265,11 +265,12 @@ jobs: - name: Install Powershell run: | apt-get update - apt-get install -y wget - wget https://ftp.debian.org/debian/pool/main/i/icu/libicu72_72.1-3_amd64.deb - dpkg -i libicu72_72.1-3_amd64.deb - wget https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/powershell_7.5.0-1.deb_amd64.deb - dpkg -i powershell_7.5.0-1.deb_amd64.deb + apt-get install -y wget apt-transport-https software-properties-common + wget -q "https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb" + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update + apt-get install -y powershell - name: Check Powershell Version shell: pwsh diff --git a/.github/workflows/azure-login-pr-check.yml b/.github/workflows/azure-login-pr-check.yml index 279cf3cd7..b0cf457b9 100644 --- a/.github/workflows/azure-login-pr-check.yml +++ b/.github/workflows/azure-login-pr-check.yml @@ -10,16 +10,16 @@ jobs: runs-on: windows-latest steps: - name: Checkout from PR branch - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - # Using 20.x version as an example - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + # Using 24.x version as an example + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: installing node_modules run: npm install diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45da7c8a3..f128428c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,12 +18,12 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: 'Validate build' run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ca4c475ec..0ccb3a2ff 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 76c8789ec..5c13f1674 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -7,11 +7,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 20.x + node-version: 24.x - name: Run Markdownlint run: | npm i -g markdownlint-cli2 diff --git a/action.yml b/action.yml index 44c1f66a6..bf52a453b 100644 --- a/action.yml +++ b/action.yml @@ -38,7 +38,7 @@ branding: icon: 'login.svg' color: 'blue' runs: - using: 'node20' + using: 'node24' main: 'lib/main/index.js' post-if: (!env.AZURE_LOGIN_POST_CLEANUP || env.AZURE_LOGIN_POST_CLEANUP != 'false') post: 'lib/cleanup/index.js' diff --git a/package-lock.json b/package-lock.json index d0ef42c4a..a0c620929 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "login", - "version": "2.2.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "login", - "version": "2.2.0", + "version": "3.0.0", "license": "MIT", "dependencies": { "@actions/core": "1.9.1", @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/jest": "^29.2.4", - "@types/node": "^20.11.1", + "@types/node": "^24.0.0", "@vercel/ncc": "^0.38.1", "jest": "^29.3.1", "jest-circus": "^29.3.1", @@ -77,73 +77,20 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.20.5", "dev": true, @@ -321,7 +268,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -329,7 +278,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -345,91 +296,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.29.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "dev": true, - "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -601,13 +489,15 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -634,13 +524,14 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1077,11 +968,13 @@ } }, "node_modules/@types/node": { - "version": "20.11.19", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.16.0" } }, "node_modules/@types/prettier": { @@ -1270,7 +1163,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", + "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", @@ -1468,7 +1363,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1668,6 +1565,21 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "dev": true, @@ -1750,14 +1662,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, @@ -2482,11 +2386,15 @@ }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -2557,7 +2465,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.memoize": { @@ -2609,11 +2519,13 @@ "license": "MIT" }, "node_modules/micromatch": { - "version": "4.0.5", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2629,7 +2541,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2805,7 +2719,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -3121,14 +3037,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3237,7 +3145,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 1ebd3012a..c1bb09287 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "login", - "version": "2.2.0", + "version": "3.0.0", "description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure", "main": "lib/main/index.js", "scripts": { @@ -13,7 +13,7 @@ "license": "MIT", "devDependencies": { "@types/jest": "^29.2.4", - "@types/node": "^20.11.1", + "@types/node": "^24.0.0", "@vercel/ncc": "^0.38.1", "jest": "^29.3.1", "jest-circus": "^29.3.1", From 893aa84218880a3fafd9a6d332ff1aea7108f1fe Mon Sep 17 00:00:00 2001 From: Yan Xu Date: Tue, 17 Mar 2026 16:26:43 +0800 Subject: [PATCH 07/26] upgrade Azure Login Action version in README (#579) --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 615c032f3..0241d309f 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -223,7 +223,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -285,7 +285,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} @@ -312,7 +312,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} enable-AzPSSession: true @@ -335,7 +335,7 @@ jobs: If you want to pass subscription ID, tenant ID, client ID, and client secret as individual parameters instead of bundling them in a single JSON object to address the [security concerns](https://docs.github.com/actions/security-guides/encrypted-secrets), below snippet can help with the same. ```yaml - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' ``` @@ -379,7 +379,7 @@ jobs: runs-on: self-hosted steps: - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: auth-type: IDENTITY tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -445,7 +445,7 @@ jobs: runs-on: self-hosted steps: - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: auth-type: IDENTITY client-id: ${{ secrets.AZURE_CLIENT_ID }} @@ -485,7 +485,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} environment: 'AzureUSGovernment' @@ -507,7 +507,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} environment: 'AzureStack' @@ -534,7 +534,7 @@ jobs: steps: - name: Azure Login - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -597,7 +597,7 @@ jobs: # enable cleanup for the 1st Azure Login - name: Azure Login - uses: azure/login@v2 + uses: azure/login@v3 env: AZURE_LOGIN_PRE_CLEANUP: true AZURE_LOGIN_POST_CLEANUP: true @@ -611,7 +611,7 @@ jobs: # disable cleanup for all other Azure Login - name: Azure Login 2 - uses: azure/login@v2 + uses: azure/login@v3 env: AZURE_LOGIN_PRE_CLEANUP: false AZURE_LOGIN_POST_CLEANUP: false @@ -625,7 +625,7 @@ jobs: # disable cleanup for all other Azure Login - name: Azure Login 3 - uses: azure/login@v2 + uses: azure/login@v3 env: AZURE_LOGIN_PRE_CLEANUP: false AZURE_LOGIN_POST_CLEANUP: false @@ -652,7 +652,7 @@ jobs: steps: - name: Azure Login - uses: azure/login@v2 + uses: azure/login@v3 env: AZURE_LOGIN_PRE_CLEANUP: ${{ startsWith(runner.name, 'GitHub Actions') }} AZURE_LOGIN_POST_CLEANUP: ${{ startsWith(runner.name, 'GitHub Actions') }} From 9dfca5820a9055ed36abbd5b5710ace9f209ead5 Mon Sep 17 00:00:00 2001 From: Yan Xu Date: Tue, 24 Mar 2026 10:12:40 +0800 Subject: [PATCH 08/26] use the latest auzre/powershell@v3 (#581) --- .../azure-login-integration-tests.yml | 10 +++++----- .github/workflows/azure-login-negative.yml | 8 ++++---- .github/workflows/azure-login-positive.yml | 18 +++++++++--------- README.md | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/azure-login-integration-tests.yml b/.github/workflows/azure-login-integration-tests.yml index 120b5a71b..a746e74f0 100644 --- a/.github/workflows/azure-login-integration-tests.yml +++ b/.github/workflows/azure-login-integration-tests.yml @@ -37,7 +37,7 @@ jobs: creds: ${{ secrets.AZURE_CREDENTIALS }} enable-AzPSSession: true - - uses: azure/powershell@v1 + - uses: azure/powershell@v3 with: inlineScript: "(Get-AzContext).Environment.Name" azPSVersion: "latest" @@ -49,7 +49,7 @@ jobs: enable-AzPSSession: true allow-no-subscriptions: true - - uses: azure/powershell@v1 + - uses: azure/powershell@v3 with: inlineScript: "(Get-AzContext).Environment.Name" azPSVersion: "latest" @@ -87,7 +87,7 @@ jobs: subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }} enable-AzPSSession: true - - uses: azure/powershell@v1 + - uses: azure/powershell@v3 with: inlineScript: "(Get-AzContext).Environment.Name" azPSVersion: "latest" @@ -100,7 +100,7 @@ jobs: enable-AzPSSession: true allow-no-subscriptions: true - - uses: azure/powershell@v1 + - uses: azure/powershell@v3 with: inlineScript: "(Get-AzContext).Environment.Name" azPSVersion: "latest" @@ -126,4 +126,4 @@ jobs: - name: Post to slack shell: bash - run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}} + run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}} diff --git a/.github/workflows/azure-login-negative.yml b/.github/workflows/azure-login-negative.yml index 7998fa08c..37c39faed 100644 --- a/.github/workflows/azure-login-negative.yml +++ b/.github/workflows/azure-login-negative.yml @@ -58,7 +58,7 @@ jobs: - name: Run Azure PowerShell id: ps_3 continue-on-error: true - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -186,7 +186,7 @@ jobs: - name: Run Azure PowerShell id: ps_8 continue-on-error: true - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -216,7 +216,7 @@ jobs: - name: Run Azure PowerShell id: ps_9 continue-on-error: true - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -332,4 +332,4 @@ jobs: uses: actions/github-script@v7 with: script: | - core.setFailed('Last action should fail but not. Please check it.') + core.setFailed('Last action should fail but not. Please check it.') diff --git a/.github/workflows/azure-login-positive.yml b/.github/workflows/azure-login-positive.yml index ecb102f84..340d3b828 100644 --- a/.github/workflows/azure-login-positive.yml +++ b/.github/workflows/azure-login-positive.yml @@ -47,7 +47,7 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -69,7 +69,7 @@ jobs: az account show --output none - name: Run Azure PowerShell again - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -92,7 +92,7 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -160,7 +160,7 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -183,7 +183,7 @@ jobs: az account show --output none - name: Run Azure PowerShell again - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -209,7 +209,7 @@ jobs: } - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -230,7 +230,7 @@ jobs: az account show --output none - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -309,8 +309,8 @@ jobs: az group list --output none - name: Run Azure PowerShell again - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | - $checkResult = Get-AzResourceGroup + $checkResult = Get-AzResourceGroup diff --git a/README.md b/README.md index 0241d309f..a031ca763 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -325,7 +325,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -396,7 +396,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -463,7 +463,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -549,7 +549,7 @@ jobs: az account show - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -705,4 +705,4 @@ provided by the bot. You will only need to do this once across all repos using o This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From a8420072e8906b1dc28363b8731497e0fb1f81d9 Mon Sep 17 00:00:00 2001 From: Yeming Liu <11371776+isra-fel@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:40:28 +1000 Subject: [PATCH 09/26] Update CODEOWNERS (#598) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d3a965c25..f9bb67a67 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@YanaXu +@Azure/act-identity-squad From e82415a0b9080e85d20936cbd8e3dfdb4e581d73 Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Thu, 30 Jul 2026 11:02:53 +1000 Subject: [PATCH 10/26] Add IDE files to .gitignore (#597) --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 18e337ddd..fef07358f 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,12 @@ typings/ .DS_Store Thumbs.db +# IDE / editor +.vs/ +.vscode/ +.idea/ +*.swp + # Ignore built ts files __tests__/runner/* lib/**/* \ No newline at end of file From f4dcb8098222abd564648c10ad073a119a6e2dba Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 4 Aug 2026 11:21:18 +1000 Subject: [PATCH 11/26] Escape single quotes in PowerShell login script inputs (#599) Apply the same single-quote escape already used for the service principal secret to the remaining values interpolated into the generated PowerShell login script: tenant-id, subscription-id, client-id, federated token, and the AzureStack resourceManagerEndpointUrl. Factor the escape into a shared helper (escapePSSingleQuoted) so every interpolation site uses the same treatment, and add regression tests that assert each field is escaped across all supported auth paths. No behaviour change for valid inputs; legitimate values contain no single quotes. --- .../PowerShell/AzPSScriptBuilder.test.ts | 153 ++++++++++++++++++ src/PowerShell/AzPSScriptBuilder.ts | 28 +++- 2 files changed, 173 insertions(+), 8 deletions(-) diff --git a/__tests__/PowerShell/AzPSScriptBuilder.test.ts b/__tests__/PowerShell/AzPSScriptBuilder.test.ts index 862d7a0f5..76b32c244 100644 --- a/__tests__/PowerShell/AzPSScriptBuilder.test.ts +++ b/__tests__/PowerShell/AzPSScriptBuilder.test.ts @@ -150,4 +150,157 @@ describe("Getting AzLogin PS script", () => { }); }); + const INJECT_RAW = "abc' ; Start-Process calc ; $x='"; + const INJECT_ESCAPED = "abc'' ; Start-Process calc ; $x=''"; + + test('SECURITY: tenant-id single quote is escaped (SP+secret path)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + let creds = { + 'clientId': 'client-id', + 'clientSecret': 'client-secret', + 'tenantId': INJECT_RAW, + 'subscriptionId': 'subscription-id' + } + setEnv('creds', JSON.stringify(creds)); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-Tenant '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-Tenant '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: subscription-id single quote is escaped (SP+secret path)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + let creds = { + 'clientId': 'client-id', + 'clientSecret': 'client-secret', + 'tenantId': 'tenant-id', + 'subscriptionId': INJECT_RAW + } + setEnv('creds', JSON.stringify(creds)); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-Subscription '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-Subscription '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: client-id single quote is escaped (SP+secret path, PSCredential)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + let creds = { + 'clientId': INJECT_RAW, + 'clientSecret': 'client-secret', + 'tenantId': 'tenant-id', + 'subscriptionId': 'subscription-id' + } + setEnv('creds', JSON.stringify(creds)); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`New-Object System.Management.Automation.PSCredential('${INJECT_ESCAPED}',`); + expect(loginScript).not.toContain(`New-Object System.Management.Automation.PSCredential('${INJECT_RAW}',`); + }); + }); + + test('SECURITY: client-id single quote is escaped (OIDC path)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'false'); + setEnv('tenant-id', 'tenant-id'); + setEnv('subscription-id', 'subscription-id'); + setEnv('client-id', INJECT_RAW); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => { loginConfig.federatedToken = "fake-token"; }); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-ApplicationId '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-ApplicationId '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: client-id single quote is escaped (user-assigned MI path)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'IDENTITY'); + setEnv('client-id', INJECT_RAW); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-AccountId '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-AccountId '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: tenant-id and subscription-id single quotes are escaped (system-assigned MI path)', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'false'); + setEnv('tenant-id', INJECT_RAW); + setEnv('subscription-id', INJECT_RAW); + setEnv('auth-type', 'IDENTITY'); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-Tenant '${INJECT_ESCAPED}'`); + expect(loginScript).toContain(`-Subscription '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-Tenant '${INJECT_RAW}'`); + expect(loginScript).not.toContain(`-Subscription '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: resourceManagerEndpointUrl single quote is escaped (AzureStack path)', () => { + setEnv('environment', 'azurestack'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + let creds = { + 'clientId': 'client-id', + 'clientSecret': 'client-secret', + 'tenantId': 'tenant-id', + 'subscriptionId': 'subscription-id', + 'resourceManagerEndpointUrl': INJECT_RAW + } + setEnv('creds', JSON.stringify(creds)); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { + expect(loginScript).toContain(`-ARMEndpoint '${INJECT_ESCAPED}'`); + expect(loginScript).not.toContain(`-ARMEndpoint '${INJECT_RAW}'`); + }); + }); + + test('SECURITY: escapePSSingleQuoted handles null/undefined without throwing', () => { + setEnv('environment', 'azurecloud'); + setEnv('enable-AzPSSession', 'true'); + setEnv('allow-no-subscriptions', 'false'); + setEnv('auth-type', 'IDENTITY'); + + let loginConfig = new LoginConfig(); + loginConfig.initialize(); + return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { + expect(loginScript).toContain("Connect-AzAccount -Identity -Environment 'azurecloud'"); + expect(loginMethod).toBe('system-assigned managed identity'); + }); + }); + }); \ No newline at end of file diff --git a/src/PowerShell/AzPSScriptBuilder.ts b/src/PowerShell/AzPSScriptBuilder.ts index 5cd34580f..8b6b05824 100644 --- a/src/PowerShell/AzPSScriptBuilder.ts +++ b/src/PowerShell/AzPSScriptBuilder.ts @@ -21,12 +21,20 @@ export default class AzPSScriptBuilder { return script; } + // Doubles single quotes for safe interpolation into a PowerShell '...' literal. + private static escapePSSingleQuoted(value: string): string { + if (value === null || value === undefined) { + return ""; + } + return String(value).split("'").join("''"); + } + static async getAzPSLoginScript(loginConfig: LoginConfig) { let loginMethodName = ""; let commands = ""; if (loginConfig.environment.toLowerCase() == "azurestack") { - commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${loginConfig.resourceManagerEndpointUrl}' | out-null;`; + commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.resourceManagerEndpointUrl)}' | out-null;`; } if (loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) { if (loginConfig.servicePrincipalSecret) { @@ -64,10 +72,11 @@ export default class AzPSScriptBuilder { } private static loginWithSecret(loginConfig: LoginConfig): string { - let servicePrincipalSecret: string = loginConfig.servicePrincipalSecret.split("'").join("''"); + let servicePrincipalSecret: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalSecret); + let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); let loginCmdlet = `$psLoginSecrets = ConvertTo-SecureString '${servicePrincipalSecret}' -AsPlainText -Force; `; - loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${loginConfig.servicePrincipalId}', $psLoginSecrets); `; - + loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${servicePrincipalId}', $psLoginSecrets); `; + let cmdletSuffix = "-Credential $psLoginCredential"; loginCmdlet += AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); @@ -76,7 +85,9 @@ export default class AzPSScriptBuilder { private static async loginWithOIDC(loginConfig: LoginConfig) { await loginConfig.getFederatedToken(); - let cmdletSuffix = `-ApplicationId '${loginConfig.servicePrincipalId}' -FederatedToken '${loginConfig.federatedToken}'`; + let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); + let federatedToken: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.federatedToken); + let cmdletSuffix = `-ApplicationId '${servicePrincipalId}' -FederatedToken '${federatedToken}'`; return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); } @@ -86,7 +97,8 @@ export default class AzPSScriptBuilder { } static loginWithUserAssignedIdentity(loginConfig: LoginConfig): string { - let cmdletSuffix = `-AccountId '${loginConfig.servicePrincipalId}'`; + let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); + let cmdletSuffix = `-AccountId '${servicePrincipalId}'`; return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); } @@ -99,10 +111,10 @@ export default class AzPSScriptBuilder { } loginCmdlet += `-Environment '${environment}' `; if(tenantId){ - loginCmdlet += `-Tenant '${tenantId}' `; + loginCmdlet += `-Tenant '${AzPSScriptBuilder.escapePSSingleQuoted(tenantId)}' `; } if(subscriptionId){ - loginCmdlet += `-Subscription '${subscriptionId}' `; + loginCmdlet += `-Subscription '${AzPSScriptBuilder.escapePSSingleQuoted(subscriptionId)}' `; } loginCmdlet += `${cmdletSuffix} -InformationAction Ignore | out-null;`; return loginCmdlet; From e8cd11fea4cc0b12960fe5037c622ad3fba5701c Mon Sep 17 00:00:00 2001 From: Yeming Liu <11371776+isra-fel@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:02:33 +1000 Subject: [PATCH 12/26] Replacing hardcoded version v2 with v3 (#603) --- src/common/Utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 8e9186d32..8d36b3cd7 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -7,8 +7,8 @@ import { AzPSConstants, AzPSUtils } from '../PowerShell/AzPSUtils'; export function setUserAgent(): void { let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); let actionName = 'AzureLogin'; - process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@v2_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; - process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v2_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; + process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@v3_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; + process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v3_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; } export async function cleanupAzCLIAccounts(): Promise { From 833e82333ca8452a40084fee403f7f16e65cba70 Mon Sep 17 00:00:00 2001 From: Alex Wang Date: Tue, 4 Aug 2026 16:25:54 -0700 Subject: [PATCH 13/26] Add version support policy guidance (#604) --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a031ca763..5d3085cfb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # Azure Login Action - [Azure Login Action](#azure-login-action) + - [Supported Versions](#supported-versions) + - [Version Selection](#version-selection) + - [Major-version tag](#major-version-tag) + - [Exact version tag](#exact-version-tag) + - [Branch reference](#branch-reference) + - [Commit SHA](#commit-sha) + - [Security Updates](#security-updates) - [Input Parameters](#input-parameters) - [`client-id`](#client-id) - [`subscription-id`](#subscription-id) @@ -48,6 +55,70 @@ Azure Login Action supports different ways of authentication with Azure. > [!WARNING] > Avoid using managed identity login on self-hosted runners in public repositories. Managed identities enable secure authentication with Azure resources and obtain Microsoft Entra ID tokens without the need for explicit credential management. Any user can open pull requests against your repository and access your self-hosted runners without credentials. See more details in [self-hosted runner security](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security). +## Supported Versions + +Azure Login follows a major-version support model. + +| Version | Status | +| --- | --- | +| v3 | Supported | +| v2 | Maintenance mode (security fixes only) | +| v1 | End of Life (EOL) | + +New features are released only to supported versions. Security fixes are released to supported versions and versions in maintenance mode. + +Customers are strongly encouraged to use the latest v3 release. + +## Version Selection + +GitHub Actions users can reference Azure Login using several forms. + +### Major-version tag + +```yaml +uses: azure/login@v3 +``` + +Receives compatible updates, including security fixes, released to the referenced major version. + +### Exact version tag + +```yaml +uses: azure/login@v2.4.0 +``` + +Remains pinned to that specific release and does not automatically receive future fixes or updates. + +### Branch reference + +```yaml +uses: azure/login@master +``` + +Receives updates from the referenced branch. For stable workflows, use a supported major-version tag or pin to a full-length commit SHA. + +### Commit SHA + +```yaml +uses: azure/login@ +``` + +Remains pinned to that commit and does not automatically receive future fixes or updates. + +## Security Updates + +Security fixes are released to supported versions and versions in maintenance mode. Customers using exact version tags or commit SHA references must explicitly upgrade to a patched release to receive security fixes. + +```yaml +# Automatically receives future v3 security updates +uses: azure/login@v3 + +# Does not automatically receive future updates +uses: azure/login@v2.4.0 +``` + +Customers using v1 should migrate to v3. End-of-life releases no longer receive updates or security fixes. + ## Input Parameters |Parameter Name|Required?|Type|Default Value|Description| @@ -705,4 +776,4 @@ provided by the bot. You will only need to do this once across all repos using o This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 6f2890194523a5c36c11358f0b293c86d2fdc62b Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Mon, 17 Aug 2026 13:18:30 +1000 Subject: [PATCH 14/26] Workflows/ci hygiene (#609) * Update azure/login pin from v1 to v3 in canary and integration tests The canary and integration-test workflows still pinned azure/login@v1 while the action is on v3, so they were validating a two-major-old release instead of the current one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused Slack notification jobs from canary and integration tests The slack-post-result jobs posted to a Slack webhook that is no longer used, and referenced a secret with broken interpolation. Removing them; the test jobs themselves are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove redundant pr-check workflow pr-check.yml duplicated ci.yml's build+test (ci.yml already runs on master PRs across windows and ubuntu). Removing the redundant workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Run positive/negative live tests on pull requests instead of every push These suites perform real Azure logins. Triggering on every push to any branch ran the full matrix (and consumed live credentials) for incidental commits. Switch to pull_request targeting master, keeping workflow_dispatch for manual runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Self-provision RG for live login tests; drop VM assertions - positive/negative: add setup/teardown jobs that create and delete GitHubAction_CI_Group via azure/login@v3 (stable), gated on all jobs - drop az vm list / Get-AzVM assertions (no VM is provisioned) - negative permission tests keep targeting the never-created GitHubAction_CI_RG so they fail as expected - shared concurrency group so positive/negative don't collide on the RG * Merge positive/negative live tests into one self-provisioning workflow Replace azure-login-positive.yml and azure-login-negative.yml with a single azure-login-live-tests.yml. All 49 positive and 45 negative test steps are preserved unchanged; only structure and provisioning change. --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/azure-login-canary.yml | 29 +- .../azure-login-integration-tests.yml | 39 +- .github/workflows/azure-login-live-tests.yml | 690 ++++++++++++++++++ .github/workflows/azure-login-negative.yml | 335 --------- .github/workflows/azure-login-positive.yml | 316 -------- .github/workflows/azure-login-pr-check.yml | 31 - 6 files changed, 702 insertions(+), 738 deletions(-) create mode 100644 .github/workflows/azure-login-live-tests.yml delete mode 100644 .github/workflows/azure-login-negative.yml delete mode 100644 .github/workflows/azure-login-positive.yml delete mode 100644 .github/workflows/azure-login-pr-check.yml diff --git a/.github/workflows/azure-login-canary.yml b/.github/workflows/azure-login-canary.yml index eb4ef84de..665e04432 100644 --- a/.github/workflows/azure-login-canary.yml +++ b/.github/workflows/azure-login-canary.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v6 - name: 'Az CLI login with subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} @@ -41,7 +41,7 @@ jobs: az account show --output none - name: 'Az CLI login without subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} allow-no-subscriptions: true @@ -50,7 +50,7 @@ jobs: az account show --output none - name: 'Az CLI login with subscription OIDC' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -60,7 +60,7 @@ jobs: az account show --output none - name: 'Az CLI login without subscription OIDC' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -68,24 +68,3 @@ jobs: - run: | az account show --output none - - slack-post-result: - runs-on: ubuntu-latest -# continue-on-error: true - if: ${{ always() }} - needs: [az-login-test] - steps: - - name: Create slack post - id: slack_report - run: | - TITLE="Login action canary tests update - " - DATEVAR=`date "+%d/%m/%YT%H:%M:%S"` - TITLE="${TITLE}${DATEVAR}" - REPORT="${TITLE}\r\nLink to run - https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\r\n" - RUN_URL="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - REPORT="${REPORT}\r\n" - if [ ${{needs.az-login-test.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test>"; fi - echo "report=$REPORT" >> $GITHUB_OUTPUT - - name: Post to slack - shell: bash - run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}} diff --git a/.github/workflows/azure-login-integration-tests.yml b/.github/workflows/azure-login-integration-tests.yml index a746e74f0..31b91186e 100644 --- a/.github/workflows/azure-login-integration-tests.yml +++ b/.github/workflows/azure-login-integration-tests.yml @@ -14,7 +14,7 @@ jobs: # continue-on-error: true steps: - name: 'Az CLI login with subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} @@ -23,7 +23,7 @@ jobs: az vm list --output none - name: 'Az CLI login without subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} allow-no-subscriptions: true @@ -32,7 +32,7 @@ jobs: az account show --output none - name: 'Azure PowerShell login with subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} enable-AzPSSession: true @@ -43,7 +43,7 @@ jobs: azPSVersion: "latest" - name: 'Azure PowerShell login without subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: creds: ${{secrets.AZURE_CREDENTIALS}} enable-AzPSSession: true @@ -59,7 +59,7 @@ jobs: # continue-on-error: true steps: - name: 'Az CLI login with subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -70,7 +70,7 @@ jobs: az vm list --output none - name: 'Az CLI login without subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -80,7 +80,7 @@ jobs: az account show --output none - name: 'Azure PowerShell login with subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -93,7 +93,7 @@ jobs: azPSVersion: "latest" - name: 'Azure PowerShell login without subscription' - uses: azure/login@v1 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENTID }} tenant-id: ${{ secrets.AZURE_TENANTID }} @@ -104,26 +104,3 @@ jobs: with: inlineScript: "(Get-AzContext).Environment.Name" azPSVersion: "latest" - - slack-post-result: - runs-on: ubuntu-latest -# continue-on-error: true - if: ${{ always() }} - needs: [az-login-test-non-oidc, az-login-test-oidc] - steps: - - name: Create slack post - id: slack_report - run: | - TITLE="Login action OIDC flow tests update - " - DATEVAR=`date "+%d/%m/%YT%H:%M:%S"` - TITLE="${TITLE}${DATEVAR}" - REPORT="${TITLE}\r\nLink to run - https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\r\n" - RUN_URL="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - REPORT="${REPORT}\r\n" - if [ ${{needs.az-login-test-non-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-non-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-non-oidc>"; fi - if [ ${{needs.az-login-test-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-oidc>"; fi - echo "report=$REPORT" >> $GITHUB_OUTPUT - - - name: Post to slack - shell: bash - run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}} diff --git a/.github/workflows/azure-login-live-tests.yml b/.github/workflows/azure-login-live-tests.yml new file mode 100644 index 000000000..ec872ff77 --- /dev/null +++ b/.github/workflows/azure-login-live-tests.yml @@ -0,0 +1,690 @@ +name: Azure Login Action Live Tests +on: + workflow_dispatch: + pull_request: + branches: + - master + +permissions: + id-token: write + contents: read + +# One resource group is shared by every job in this run, so serialize runs to +# avoid one run tearing down another's group. +concurrency: + group: azure-login-live-tests + cancel-in-progress: false + +env: + # Created once in setup; the positive and embedded "can access" assertions + # target this group. + RG_POSITIVE: GitHubAction_CI_Group + LOCATION: eastus + # Intentionally NEVER created: the negative permission/not-found assertions + # target this name so they fail as expected. + RG_NEGATIVE: GitHubAction_CI_RG + +jobs: + # ---------------------------------------------------------------- setup + # Provision the resource group the "can access" assertions target. Uses a + # repo-level provisioning credential (PROVISION_CREDS) and does NOT declare an + # environment, so it is not subject to the Automation test approval gate - + # setup/teardown are provisioning, not part of the gated test surface. + setup: + runs-on: ubuntu-latest + steps: + - name: Azure login (provisioning) + uses: azure/login@v3 + with: + creds: ${{ secrets.PROVISION_CREDS }} + - name: Create resource group + run: az group create --name "$RG_POSITIVE" --location "$LOCATION" --output none + + # ================================================================ POSITIVE + Positive_BasicTest: + needs: setup + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + environment: Automation test + + steps: + - name: 'Checking out repo code' + uses: actions/checkout@v6 + + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: 'Validate build' + run: | + npm install + npm run build + + - name: 'Run L0 tests' + run: | + npm run test + + - name: Login with creds + uses: ./ + with: + creds: ${{secrets.SP1}} + enable-AzPSSession: true + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Run Azure PowerShell + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Login with individual parameters + uses: ./ + with: + client-id: ${{ secrets.SP1_CLIENT_ID }} + tenant-id: ${{ secrets.SP1_TENANT_ID }} + subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Run Azure Cli again + run: | + az account show --output none + + - name: Run Azure PowerShell again + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Login with explicit auth-type + uses: ./ + with: + creds: ${{secrets.SP1}} + auth-type: SERVICE_PRINCIPAL + enable-AzPSSession: true + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Run Azure PowerShell + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + Positive_ParameterTest: + needs: setup + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + environment: Automation test + + steps: + - name: 'Checking out repo code' + uses: actions/checkout@v6 + + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: 'Validate build' + run: | + npm install + npm run build + + - name: Login with creds, disable ps session + uses: ./ + with: + creds: ${{secrets.SP1}} + enable-AzPSSession: false + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Login with creds, wrong boolean value + uses: ./ + with: + creds: ${{secrets.SP1}} + enable-AzPSSession: notboolean + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Login with creds, allow no subscription + uses: ./ + with: + creds: ${{secrets.SP1}} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Run Azure PowerShell + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Login with individual parameters, allow no subscription + uses: ./ + with: + client-id: ${{ secrets.SP1_CLIENT_ID }} + tenant-id: ${{ secrets.SP1_TENANT_ID}} + subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Run Azure Cli again + run: | + az account show --output none + + - name: Run Azure PowerShell again + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Login with individual parameters, no subscription, allow no subscription + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Run Azure Cli + shell: pwsh + run: | + $checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 2 + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Run Azure PowerShell + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + - name: Login with creds, no subscription, allow no subscription + uses: ./ + with: + creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}' + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Run Azure Cli + run: | + az account show --output none + + - name: Run Azure PowerShell + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' + if(-not $checkResult){ + throw "Not all checks passed!" + } + + Positive_InDockerTest: + needs: setup + runs-on: ubuntu-latest + container: ubuntu:24.04 + environment: Automation test + steps: + - name: 'Checking out repo code' + uses: actions/checkout@v6 + + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: Install Azure CLI + run: | + apt-get update + apt-get install -y curl + curl -sL https://aka.ms/InstallAzureCLIDeb | bash + + - name: Check Azure CLI Version + run: | + az --version + + - name: Install Powershell + run: | + apt-get update + apt-get install -y wget apt-transport-https software-properties-common + wget -q "https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb" + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update + apt-get install -y powershell + + - name: Check Powershell Version + shell: pwsh + run: | + $PSVersionTable + + - name: Install Azure Powershell + shell: pwsh + run: | + Install-Module -Name Az -Repository PSGallery -Force + + - name: Check Azure Powershell Version + shell: pwsh + run: | + Get-Module -ListAvailable Az + + - name: 'Validate build' + run: | + npm install + npm run build + + - name: 'Run L0 tests' + run: | + npm run test + + - name: Login with individual parameters + uses: ./ + with: + client-id: ${{ secrets.SP1_CLIENT_ID }} + tenant-id: ${{ secrets.SP1_TENANT_ID }} + subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Run Azure Cli again + run: | + az group list --output none + + - name: Run Azure PowerShell again + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + $checkResult = Get-AzResourceGroup + + # ================================================================ NEGATIVE + Negative_PermissionTest: + needs: setup + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + environment: Automation test + + steps: + + - name: 'Checking out repo code' + uses: actions/checkout@v6 + + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: 'Validate build' + run: | + npm install + npm run build + + - name: Login with individual parameters + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + # subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Run Azure Cli + id: cli_3 + continue-on-error: true + run: | + az account show --output none + az group show --name GitHubAction_CI_RG --output none + + - name: Check Last step failed + if: steps.cli_3.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Run Azure PowerShell + id: ps_3 + continue-on-error: true + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + (Get-AzContext).Environment.Name -eq 'AzureCloud' + (Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG' + + - name: Check Last step failed + if: steps.ps_3.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + Negative_ParameterTest: + needs: setup + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + environment: Automation test + + steps: + - name: 'Checking out repo code' + uses: actions/checkout@v6 + + - name: Set Node.js 24.x for GitHub Action + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: 'Validate build' + run: | + npm install + npm run build + + - name: Login with creds, missing parameters in creds + id: login_4 + continue-on-error: true + uses: ./ + with: + creds: ${{secrets.SP3_NO_Secret}} + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_4.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with creds, wrong keys + id: login_5 + continue-on-error: true + uses: ./ + with: + creds: ${{secrets.SP4_Wrong_Key}} + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_5.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with creds, no creds or individual parameters + id: login_6 + continue-on-error: true + uses: ./ + with: + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_6.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with individual parameters, only client-id, no tenant-id, subscription-id + id: login_7 + continue-on-error: true + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_7.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with individual parameters, only tenant-id, subscription-id, no client-id + id: login_8 + continue-on-error: true + uses: ./ + with: + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_8.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with creds, disable ps session + uses: ./ + with: + creds: ${{secrets.SP1}} + enable-AzPSSession: false + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Run Azure PowerShell + id: ps_8 + continue-on-error: true + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + (Get-AzContext).Environment.Name -eq 'AzureCloud' + (Get-AzResourceGroup -Name GitHubAction_CI_Group).ResourceGroupName -eq 'GitHubAction_CI_Group' + + - name: Check Last step failed + if: steps.ps_8.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with creds, wrong boolean value + uses: ./ + with: + creds: ${{secrets.SP1}} + enable-AzPSSession: notboolean + + - name: Run Azure Cli + run: | + az account show --output none + az group show --name GitHubAction_CI_Group --output none + + - name: Run Azure PowerShell + id: ps_9 + continue-on-error: true + uses: azure/powershell@v3 + with: + azPSVersion: "latest" + inlineScript: | + (Get-AzContext).Environment.Name -eq 'AzureCloud' + (Get-AzResourceGroup -Name GitHubAction_CI_Group).ResourceGroupName -eq 'GitHubAction_CI_Group' + + - name: Check Last step failed + if: steps.ps_9.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with individual parameters, with a wrong audience + id: login_10 + continue-on-error: true + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} + audience: "https://github.com/actions" + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_10.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with tenant-level account, without allow-no-subscriptions + id: login_11 + continue-on-error: true + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_11.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + # SP1 is ignored and SP2 will be used for login, but it will fail since SP2 has no access to the given subscription + - name: Login with both creds and individual parameters + id: login_12 + continue-on-error: true + uses: ./ + with: + creds: ${{secrets.SP1}} + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_12.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login by OIDC with all info in creds + id: login_13 + continue-on-error: true + uses: ./ + with: + creds: ${{secrets.SP2}} + allow-no-subscriptions: true + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_13.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with individual parameters, no subscription-id, no allow-no-subscriptions + id: login_14 + continue-on-error: true + uses: ./ + with: + client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} + tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_14.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + - name: Login with creds, no subscription-id, no allow-no-subscriptions + id: login_15 + continue-on-error: true + uses: ./ + with: + creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}' + enable-AzPSSession: true + + - name: Check Last step failed + if: steps.login_15.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Last action should fail but not. Please check it.') + + # ---------------------------------------------------------------- teardown + # Always delete the resource group created in setup, even if gate jobs failed, + # so nothing is left running (cost) between the infrequent runs. Runs + # synchronously (no --no-wait) so the group is fully gone before the run ends + # and the concurrency lock releases, preventing a delete from a finished run + # racing the setup of the next one. + teardown: + needs: [setup, Positive_BasicTest, Positive_ParameterTest, Positive_InDockerTest, Negative_PermissionTest, Negative_ParameterTest] + if: always() + runs-on: ubuntu-latest + steps: + - name: Azure login (provisioning) + uses: azure/login@v3 + with: + creds: ${{ secrets.PROVISION_CREDS }} + - name: Delete resource group + run: az group delete --name "$RG_POSITIVE" --yes --output none || true diff --git a/.github/workflows/azure-login-negative.yml b/.github/workflows/azure-login-negative.yml deleted file mode 100644 index 37c39faed..000000000 --- a/.github/workflows/azure-login-negative.yml +++ /dev/null @@ -1,335 +0,0 @@ -name: Azure Login Action Negative Test -on: - workflow_dispatch: - push: - -permissions: - id-token: write - contents: read - -jobs: - - PermissionTest: - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - runs-on: ${{ matrix.os }} - environment: Automation test - - steps: - - - name: 'Checking out repo code' - uses: actions/checkout@v6 - - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: Login with individual parameters - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - # subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Run Azure Cli - id: cli_3 - continue-on-error: true - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Check Last step failed - if: steps.cli_3.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Run Azure PowerShell - id: ps_3 - continue-on-error: true - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - (Get-AzContext).Environment.Name -eq 'AzureCloud' - (Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG' - (Get-AzVM).Count -gt 0 - - - name: Check Last step failed - if: steps.ps_3.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - ParameterTest: - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - runs-on: ${{ matrix.os }} - environment: Automation test - - steps: - - name: 'Checking out repo code' - uses: actions/checkout@v6 - - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: Login with creds, missing parameters in creds - id: login_4 - continue-on-error: true - uses: ./ - with: - creds: ${{secrets.SP3_NO_Secret}} - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_4.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with creds, wrong keys - id: login_5 - continue-on-error: true - uses: ./ - with: - creds: ${{secrets.SP4_Wrong_Key}} - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_5.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with creds, no creds or individual parameters - id: login_6 - continue-on-error: true - uses: ./ - with: - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_6.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with individual parameters, only client-id, no tenant-id, subscription-id - id: login_7 - continue-on-error: true - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_7.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with individual parameters, only tenant-id, subscription-id, no client-id - id: login_8 - continue-on-error: true - uses: ./ - with: - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_8.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with creds, disable ps session - uses: ./ - with: - creds: ${{secrets.SP1}} - enable-AzPSSession: false - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Run Azure PowerShell - id: ps_8 - continue-on-error: true - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - (Get-AzContext).Environment.Name -eq 'AzureCloud' - (Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG' - (Get-AzVM).Count -gt 0 - - - name: Check Last step failed - if: steps.ps_8.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with creds, wrong boolean value - uses: ./ - with: - creds: ${{secrets.SP1}} - enable-AzPSSession: notboolean - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Run Azure PowerShell - id: ps_9 - continue-on-error: true - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - (Get-AzContext).Environment.Name -eq 'AzureCloud' - (Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG' - (Get-AzVM).Count -gt 0 - - - name: Check Last step failed - if: steps.ps_9.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with individual parameters, with a wrong audience - id: login_10 - continue-on-error: true - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} - audience: "https://github.com/actions" - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_10.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with tenant-level account, without allow-no-subscriptions - id: login_11 - continue-on-error: true - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_11.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - # SP1 is ignored and SP2 will be used for login, but it will fail since SP2 has no access to the given subscription - - name: Login with both creds and individual parameters - id: login_12 - continue-on-error: true - uses: ./ - with: - creds: ${{secrets.SP1}} - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_12.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login by OIDC with all info in creds - id: login_13 - continue-on-error: true - uses: ./ - with: - creds: ${{secrets.SP2}} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_13.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with individual parameters, no subscription-id, no allow-no-subscriptions - id: login_14 - continue-on-error: true - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_14.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') - - - name: Login with creds, no subscription-id, no allow-no-subscriptions - id: login_15 - continue-on-error: true - uses: ./ - with: - creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}' - enable-AzPSSession: true - - - name: Check Last step failed - if: steps.login_15.outcome == 'success' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Last action should fail but not. Please check it.') diff --git a/.github/workflows/azure-login-positive.yml b/.github/workflows/azure-login-positive.yml deleted file mode 100644 index 340d3b828..000000000 --- a/.github/workflows/azure-login-positive.yml +++ /dev/null @@ -1,316 +0,0 @@ -name: Azure Login Action Positive Test -on: - workflow_dispatch: - push: - -permissions: - id-token: write - contents: read - -jobs: - - BasicTest: - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - environment: Automation test - - steps: - - name: 'Checking out repo code' - uses: actions/checkout@v6 - - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: 'Run L0 tests' - run: | - npm run test - - - name: Login with creds - uses: ./ - with: - creds: ${{secrets.SP1}} - enable-AzPSSession: true - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with individual parameters - uses: ./ - with: - client-id: ${{ secrets.SP1_CLIENT_ID }} - tenant-id: ${{ secrets.SP1_TENANT_ID }} - subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} - enable-AzPSSession: true - - - name: Run Azure Cli again - run: | - az account show --output none - - - name: Run Azure PowerShell again - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with explicit auth-type - uses: ./ - with: - creds: ${{secrets.SP1}} - auth-type: SERVICE_PRINCIPAL - enable-AzPSSession: true - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - ParameterTest: - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - environment: Automation test - - steps: - - name: 'Checking out repo code' - uses: actions/checkout@v6 - - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: Login with creds, disable ps session - uses: ./ - with: - creds: ${{secrets.SP1}} - enable-AzPSSession: false - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Login with creds, wrong boolean value - uses: ./ - with: - creds: ${{secrets.SP1}} - enable-AzPSSession: notboolean - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Login with creds, allow no subscription - uses: ./ - with: - creds: ${{secrets.SP1}} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Run Azure Cli - run: | - az account show --output none - az group show --name GitHubAction_CI_RG --output none - az vm list --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with individual parameters, allow no subscription - uses: ./ - with: - client-id: ${{ secrets.SP1_CLIENT_ID }} - tenant-id: ${{ secrets.SP1_TENANT_ID}} - subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Run Azure Cli again - run: | - az account show --output none - - - name: Run Azure PowerShell again - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with individual parameters, no subscription, allow no subscription - uses: ./ - with: - client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }} - tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }} - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Run Azure Cli - shell: pwsh - run: | - $checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 2 - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Run Azure PowerShell - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with creds, no subscription, allow no subscription - uses: ./ - with: - creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}' - allow-no-subscriptions: true - enable-AzPSSession: true - - - name: Run Azure Cli - run: | - az account show --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - InDockerTest: - runs-on: ubuntu-latest - container: ubuntu:24.04 - environment: Automation test - steps: - - name: 'Checking out repo code' - uses: actions/checkout@v6 - - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: Install Azure CLI - run: | - apt-get update - apt-get install -y curl - curl -sL https://aka.ms/InstallAzureCLIDeb | bash - - - name: Check Azure CLI Version - run: | - az --version - - - name: Install Powershell - run: | - apt-get update - apt-get install -y wget apt-transport-https software-properties-common - wget -q "https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb" - dpkg -i packages-microsoft-prod.deb - rm packages-microsoft-prod.deb - apt-get update - apt-get install -y powershell - - - name: Check Powershell Version - shell: pwsh - run: | - $PSVersionTable - - - name: Install Azure Powershell - shell: pwsh - run: | - Install-Module -Name Az -Repository PSGallery -Force - - - name: Check Azure Powershell Version - shell: pwsh - run: | - Get-Module -ListAvailable Az - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: 'Run L0 tests' - run: | - npm run test - - - name: Login with individual parameters - uses: ./ - with: - client-id: ${{ secrets.SP1_CLIENT_ID }} - tenant-id: ${{ secrets.SP1_TENANT_ID }} - subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }} - enable-AzPSSession: true - - - name: Run Azure Cli again - run: | - az group list --output none - - - name: Run Azure PowerShell again - uses: azure/powershell@v3 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = Get-AzResourceGroup diff --git a/.github/workflows/azure-login-pr-check.yml b/.github/workflows/azure-login-pr-check.yml deleted file mode 100644 index b0cf457b9..000000000 --- a/.github/workflows/azure-login-pr-check.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: pr-check - -on: - pull_request: - branches: - - master - - 'releases/*' -jobs: - az-login-test: - runs-on: windows-latest - steps: - - name: Checkout from PR branch - uses: actions/checkout@v6 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - - # Using 24.x version as an example - - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 - with: - node-version: 24.x - - - name: installing node_modules - run: npm install - - - name: Build GitHub Action - run: npm run build - - - name: Run mock test - run: npm run test From 32b477179f51f55c6556bbc3c5f24205e8419e05 Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Mon, 17 Aug 2026 15:37:10 +1000 Subject: [PATCH 15/26] Cleanup/package json and dependabot (#608) * Remove accidental package-lock dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused version field from package.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Dependabot config (npm + github-actions) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 19 ++++++++++++++ package-lock.json | 56 +++++++++--------------------------------- package.json | 4 +-- 3 files changed, 32 insertions(+), 47 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b545d1322 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 5 + groups: + dev-dependencies: + dependency-type: "development" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: ["*"] diff --git a/package-lock.json b/package-lock.json index a0c620929..6e981114d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,13 +6,11 @@ "packages": { "": { "name": "login", - "version": "3.0.0", "license": "MIT", "dependencies": { "@actions/core": "1.9.1", "@actions/exec": "^1.0.1", - "@actions/io": "^1.0.1", - "package-lock": "^1.0.3" + "@actions/io": "^1.0.1" }, "devDependencies": { "@types/jest": "^29.2.4", @@ -1065,13 +1063,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/asyncc": { - "version": "2.0.6", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/babel-jest": { "version": "29.3.1", "dev": true, @@ -1160,12 +1151,14 @@ }, "node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "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==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1346,15 +1339,9 @@ "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "5.1.0", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/concat-map": { "version": "0.0.1", + "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { @@ -1563,6 +1550,7 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -1622,6 +1610,7 @@ }, "node_modules/glob": { "version": "7.2.3", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -1703,6 +1692,7 @@ }, "node_modules/inflight": { "version": "1.0.6", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -1711,6 +1701,7 @@ }, "node_modules/inherits": { "version": "2.0.4", + "dev": true, "license": "ISC" }, "node_modules/is-arrayish": { @@ -2464,12 +2455,6 @@ "node": ">=8" } }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "dev": true, @@ -2544,6 +2529,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2593,6 +2579,7 @@ }, "node_modules/once": { "version": "1.4.0", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -2659,20 +2646,6 @@ "node": ">=6" } }, - "node_modules/package-lock": { - "version": "1.0.3", - "license": "Unlicense", - "dependencies": { - "asyncc": "^2.0.4", - "commander": "^5.0.0", - "glob": "^7.1.6", - "lodash": "^4.17.15", - "traverse": "^0.6.6" - }, - "bin": { - "package-lock": "bin/package-lock.js" - } - }, "node_modules/parse-json": { "version": "5.2.0", "dev": true, @@ -2700,6 +2673,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3050,13 +3024,6 @@ "node": ">=8.0" } }, - "node_modules/traverse": { - "version": "0.6.7", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ts-jest": { "version": "29.0.3", "dev": true, @@ -3234,6 +3201,7 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/package.json b/package.json index c1bb09287..6364c76cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "login", - "version": "3.0.0", "description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure", "main": "lib/main/index.js", "scripts": { @@ -23,7 +22,6 @@ "dependencies": { "@actions/core": "1.9.1", "@actions/exec": "^1.0.1", - "@actions/io": "^1.0.1", - "package-lock": "^1.0.3" + "@actions/io": "^1.0.1" } } From 3b64cbda39d4a423951ec87e69bcb45ec37bb851 Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 18 Aug 2026 12:45:53 +1000 Subject: [PATCH 16/26] Add in dynamic version for telemetry using GITHUB_ACTION_REF (#614) * Add in dynamic version for telemetry using GITHUB_ACTION_REF * Isolate live-test resource group per run; scope concurrency to the branch The live-tests workflow used a fixed resource group name (GitHubAction_CI_Group) plus a repo-wide concurrency group. Because the test jobs run behind the Automation test approval gate, an unapproved run sits in the 'waiting' state holding the shared concurrency lock, which blocked live-tests on every other PR indefinitely. Give each run its own resource group by suffixing the name with github.run_id, and reference it via the RG_POSITIVE env var everywhere (previously nine jobs used the literal name). With per-run isolation the repo-wide lock is no longer needed to prevent collisions, so the concurrency group is now scoped to the branch (github.ref) and only prevents pile-ups on rapid pushes to the same ref - never across PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix resource group name expansion on Windows runners The per-run resource group name was referenced as the bash-style "$RG_POSITIVE" in az group show/create/delete run steps. Those steps use the runner default shell, which is PowerShell on windows-latest, so the name expanded to an empty string there and az failed with "argument --name/-n/--resource-group/-g: expected one argument". Reference the value through the GitHub Actions env expression instead, so it is substituted before the shell runs, making it shell-agnostic across ubuntu, windows, and macos. The azure/powershell inline scripts keep using the PowerShell env form, which is correct there. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/azure-login-live-tests.yml | 35 +++++++++++--------- src/common/Utils.ts | 5 +-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/azure-login-live-tests.yml b/.github/workflows/azure-login-live-tests.yml index ec872ff77..1d3657a95 100644 --- a/.github/workflows/azure-login-live-tests.yml +++ b/.github/workflows/azure-login-live-tests.yml @@ -9,16 +9,19 @@ permissions: id-token: write contents: read -# One resource group is shared by every job in this run, so serialize runs to -# avoid one run tearing down another's group. +# Each run provisions its own uniquely-named resource group (suffixed with the +# run id), so concurrent runs on different branches/PRs never collide or tear +# down each other's group. The concurrency group is branch-scoped only to avoid +# piling up runs on rapid pushes to the same ref - it does NOT serialize across +# the whole repo, so an unapproved run on one PR can never block another. concurrency: - group: azure-login-live-tests + group: azure-login-live-tests-${{ github.ref }} cancel-in-progress: false env: # Created once in setup; the positive and embedded "can access" assertions - # target this group. - RG_POSITIVE: GitHubAction_CI_Group + # target this group. Suffixed with the run id so every run is isolated. + RG_POSITIVE: GitHubAction_CI_Group_${{ github.run_id }} LOCATION: eastus # Intentionally NEVER created: the negative permission/not-found assertions # target this name so they fail as expected. @@ -38,7 +41,7 @@ jobs: with: creds: ${{ secrets.PROVISION_CREDS }} - name: Create resource group - run: az group create --name "$RG_POSITIVE" --location "$LOCATION" --output none + run: az group create --name "${{ env.RG_POSITIVE }}" --location "${{ env.LOCATION }}" --output none # ================================================================ POSITIVE Positive_BasicTest: @@ -77,7 +80,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Run Azure PowerShell uses: azure/powershell@v3 @@ -121,7 +124,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Run Azure PowerShell uses: azure/powershell@v3 @@ -165,7 +168,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Login with creds, wrong boolean value uses: ./ @@ -176,7 +179,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Login with creds, allow no subscription uses: ./ @@ -188,7 +191,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Run Azure PowerShell uses: azure/powershell@v3 @@ -521,7 +524,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Run Azure PowerShell id: ps_8 @@ -531,7 +534,7 @@ jobs: azPSVersion: "latest" inlineScript: | (Get-AzContext).Environment.Name -eq 'AzureCloud' - (Get-AzResourceGroup -Name GitHubAction_CI_Group).ResourceGroupName -eq 'GitHubAction_CI_Group' + (Get-AzResourceGroup -Name $env:RG_POSITIVE).ResourceGroupName -eq $env:RG_POSITIVE - name: Check Last step failed if: steps.ps_8.outcome == 'success' @@ -549,7 +552,7 @@ jobs: - name: Run Azure Cli run: | az account show --output none - az group show --name GitHubAction_CI_Group --output none + az group show --name "${{ env.RG_POSITIVE }}" --output none - name: Run Azure PowerShell id: ps_9 @@ -559,7 +562,7 @@ jobs: azPSVersion: "latest" inlineScript: | (Get-AzContext).Environment.Name -eq 'AzureCloud' - (Get-AzResourceGroup -Name GitHubAction_CI_Group).ResourceGroupName -eq 'GitHubAction_CI_Group' + (Get-AzResourceGroup -Name $env:RG_POSITIVE).ResourceGroupName -eq $env:RG_POSITIVE - name: Check Last step failed if: steps.ps_9.outcome == 'success' @@ -687,4 +690,4 @@ jobs: with: creds: ${{ secrets.PROVISION_CREDS }} - name: Delete resource group - run: az group delete --name "$RG_POSITIVE" --yes --output none || true + run: az group delete --name "${{ env.RG_POSITIVE }}" --yes --output none || true diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 8d36b3cd7..ecd45df94 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -7,8 +7,9 @@ import { AzPSConstants, AzPSUtils } from '../PowerShell/AzPSUtils'; export function setUserAgent(): void { let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); let actionName = 'AzureLogin'; - process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@v3_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; - process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v3_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; + let actionRef = process.env.GITHUB_ACTION_REF || 'unknown'; + process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@${actionRef}_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; + process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@${actionRef}_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`; } export async function cleanupAzCLIAccounts(): Promise { From d1f8233a2b2c1078380346f5266ce41a3eda5f2d Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Wed, 19 Aug 2026 17:15:22 +1000 Subject: [PATCH 17/26] Pin third-party GitHub Actions to commit SHAs (#615) Third-party actions were referenced by movable major tags (e.g. actions/checkout@v6). A tag can be repointed by the upstream owner or an attacker who compromises that action's repo, silently running new code in our CI - the class of supply-chain attack seen with tj-actions in 2025. Pin the externally-owned actions to the immutable commit SHA the tag resolves to, with the version kept in a trailing comment. Dependabot (github-actions ecosystem) updates both the SHA and the comment together, so they do not drift. Pinned: actions/checkout, actions/setup-node, actions/github-script, actions/stale, github/codeql-action (init/autobuild/analyze). Deliberately NOT pinned: azure/login and azure/powershell stay on @v3. These are our own actions used as test/provisioning tooling - the canary and integration suites exist to validate the current v3, so pinning them to a SHA would freeze what the tests exercise and cause them to drift from the released major. The supply-chain threat model (us attacking our own action's CI) does not apply. Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/azure-login-canary.yml | 2 +- .github/workflows/azure-login-live-tests.yml | 50 ++++++++++---------- .github/workflows/ci.yml | 4 +- .github/workflows/codeql.yml | 8 ++-- .github/workflows/defaultLabels.yml | 6 +-- .github/workflows/markdownlint.yml | 4 +- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/azure-login-canary.yml b/.github/workflows/azure-login-canary.yml index 665e04432..23bc1405b 100644 --- a/.github/workflows/azure-login-canary.yml +++ b/.github/workflows/azure-login-canary.yml @@ -30,7 +30,7 @@ jobs: az --version - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: 'Az CLI login with subscription' uses: azure/login@v3 diff --git a/.github/workflows/azure-login-live-tests.yml b/.github/workflows/azure-login-live-tests.yml index 1d3657a95..1e102279c 100644 --- a/.github/workflows/azure-login-live-tests.yml +++ b/.github/workflows/azure-login-live-tests.yml @@ -55,10 +55,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x @@ -147,10 +147,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x @@ -280,10 +280,10 @@ jobs: environment: Automation test steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x @@ -363,10 +363,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x @@ -393,7 +393,7 @@ jobs: - name: Check Last step failed if: steps.cli_3.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -410,7 +410,7 @@ jobs: - name: Check Last step failed if: steps.ps_3.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -426,10 +426,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x @@ -448,7 +448,7 @@ jobs: - name: Check Last step failed if: steps.login_4.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -463,7 +463,7 @@ jobs: - name: Check Last step failed if: steps.login_5.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -477,7 +477,7 @@ jobs: - name: Check Last step failed if: steps.login_6.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -493,7 +493,7 @@ jobs: - name: Check Last step failed if: steps.login_7.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -510,7 +510,7 @@ jobs: - name: Check Last step failed if: steps.login_8.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -538,7 +538,7 @@ jobs: - name: Check Last step failed if: steps.ps_8.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -566,7 +566,7 @@ jobs: - name: Check Last step failed if: steps.ps_9.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -585,7 +585,7 @@ jobs: - name: Check Last step failed if: steps.login_10.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -602,7 +602,7 @@ jobs: - name: Check Last step failed if: steps.login_11.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -622,7 +622,7 @@ jobs: - name: Check Last step failed if: steps.login_12.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -638,7 +638,7 @@ jobs: - name: Check Last step failed if: steps.login_13.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -654,7 +654,7 @@ jobs: - name: Check Last step failed if: steps.login_14.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -669,7 +669,7 @@ jobs: - name: Check Last step failed if: steps.login_15.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f128428c8..09aede1f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0ccb3a2ff..793db6d20 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,18 +19,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 with: languages: javascript # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -44,4 +44,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 diff --git a/.github/workflows/defaultLabels.yml b/.github/workflows/defaultLabels.yml index b9c165487..c190f060f 100644 --- a/.github/workflows/defaultLabels.yml +++ b/.github/workflows/defaultLabels.yml @@ -14,7 +14,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 name: Setting issue as idle with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -25,7 +25,7 @@ jobs: operations-per-run: 100 exempt-issue-labels: 'backlog' - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 name: Setting PR as idle with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -35,7 +35,7 @@ jobs: days-before-close: -1 operations-per-run: 100 - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 name: Close issue with no feedback for 20 days with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 5c13f1674..2f1fdf314 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -7,9 +7,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Use Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.x - name: Run Markdownlint From cf887bb6fa34c1dff1362834782556cdbd03188f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:39:23 +1000 Subject: [PATCH 18/26] Bump picomatch from 2.3.1 to 2.3.2 (#582) Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MaddyMicrosoft --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6e981114d..421674099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2700,7 +2700,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { From 43c6847a451de1d288960c290f11a9d2e840a9ca Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 12:53:21 +1000 Subject: [PATCH 19/26] Docs uplift (#620) * Add Troubleshooting section for OIDC federated credential subject mismatch Document the AADSTS700213 / AADSTS7002138 "no matching federated identity record" failures caused by case-sensitive subject matching and by portal- generated subjects that include GitHub owner/repository numeric IDs. Addresses #617. Also relevant to #573. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document intentional client-id masking in workflow logs Explain in the client-id input section that the action registers the client-id as a secret (core.setSecret), why it is masked, and that tenant-id and subscription-id are not masked. Addresses #566. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document Azure CLI 2.66.x requirement for Azure Stack Hub Note in the Azure Stack Hub section that Azure CLI newer than 2.66.x no longer supports Azure Stack Hub, so environment: 'AzureStack' requires pinning Azure CLI to 2.66.x (LTS). Prevents the failure reported when using the example on a current runner. Addresses #534. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 5d3085cfb..c4f105c67 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ - [Login to Azure Stack Hub](#login-to-azure-stack-hub) - [Login without subscription](#login-without-subscription) - [Enable/Disable the cleanup steps](#enabledisable-the-cleanup-steps) + - [Troubleshooting](#troubleshooting) + - [OIDC login fails with `AADSTS700213` / `AADSTS7002138` (no matching federated identity record)](#oidc-login-fails-with-aadsts700213--aadsts7002138-no-matching-federated-identity-record) - [Security hardening](#security-hardening) - [Azure CLI dependency](#azure-cli-dependency) - [Reference](#reference) @@ -143,6 +145,9 @@ It's better to create a GitHub Action secret for this parameter when using it. R Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage. +> [!NOTE] +> The action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. + ### `subscription-id` The input parameter `subscription-id` specifies the login subscription id. @@ -565,6 +570,9 @@ jobs: ### Login to Azure Stack Hub +> [!NOTE] +> Azure CLI versions newer than 2.66.x no longer support Azure Stack Hub. To use `environment: 'AzureStack'`, pin Azure CLI to 2.66.x (LTS), for example via the [Azure CLI action](https://github.com/Azure/cli) with `azcliversion: 2.66.0`. See the [Azure CLI notice for Azure Stack Hub customers](https://learn.microsoft.com/cli/azure/whats-new-overview?view=azure-cli-latest#important-notice-for-azure-stack-hub-customers). + ```yaml # File: .github/workflows/workflow.yml @@ -737,6 +745,29 @@ jobs: ``` +## Troubleshooting + +### OIDC login fails with `AADSTS700213` / `AADSTS7002138` (no matching federated identity record) + +When logging in with OIDC, the login may fail with an error similar to: + +```text +Error: AADSTS700213: No matching federated identity record found for presented assertion subject 'repo:/:environment:production'. +``` + +or: + +```text +Error: AADSTS7002138: No matching federated identity record found for presented assertion subject 'repo:/:ref:refs/heads/main'. The subject matches with case-insensitive comparison, but not with case-sensitive comparison. +``` + +This means Microsoft Entra ID could not find a federated identity credential whose **Subject** exactly matches the subject in the OIDC token that GitHub presented. The token's subject is shown in the run log under `Federated token details` as `subject claim`. Two common causes: + +- **Case mismatch.** Federated credential subjects are matched **case-sensitively**. If your organization, repository, branch, or environment name uses uppercase characters (for example `repo:My-Org/My-Repo`), the federated credential Subject must use the exact same casing as the `subject claim` in the run log. +- **Subject includes GitHub numeric IDs.** When you create the federated credential in the Azure portal and fill in the optional GitHub owner/repository ID fields, the portal generates a Subject of the form `repo:@/@::`. The OIDC token GitHub sends does **not** include those numeric IDs (its subject is `repo:/::`), so it will never match. Create or edit the federated credential **without** the owner/repository IDs so the Subject matches the token exactly. + +In both cases, set the federated credential Subject to exactly match the `subject claim` shown in your run's `Federated token details`. See [Configure a federated identity credential](https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) for details. + ## Security hardening > [!WARNING] From ae37a0d4d51dedecdd92c4baa8b76009601d719e Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 12:54:23 +1000 Subject: [PATCH 20/26] Harden Dependabot config for the CommonJS ncc build (#622) - Ignore typescript >=7: the native compiler rewrite. ts-jest declares a peer of "typescript >=4.3 <7", so TS 7 fails npm install with an ERESOLVE conflict. - Ignore @actions/io >=3: 3.x is ESM-only, which the CommonJS ncc bundle cannot require(). Stay on 2.x (CommonJS, Node 24 support) until the action moves to ESM. - Limit the dev-dependencies group to minor and patch updates so a breaking major no longer poisons the whole grouped PR; majors now arrive as isolated, reviewable PRs. - Label npm dependency PRs with "dependencies". Production dependencies are intentionally left ungrouped so each runtime bump is reviewed individually and one breaking major cannot block the others. Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b545d1322..28b238980 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,9 +5,20 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 5 + labels: ["dependencies"] groups: dev-dependencies: dependency-type: "development" + update-types: ["minor", "patch"] + ignore: + # ts-jest currently supports "typescript >=4.3 <7", so cap TypeScript + # below 7 (the native compiler rewrite) until ts-jest supports it. + - dependency-name: "typescript" + versions: [">=7"] + # @actions/io 3.x is ESM-only, which breaks the CommonJS ncc build. + # Stay on 2.x (CommonJS, Node 24 support) until the action moves to ESM. + - dependency-name: "@actions/io" + versions: [">=3"] - package-ecosystem: "github-actions" directory: "/" From 1029b1a00f777bb6cd4523c980189a6f62b110b4 Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 12:56:49 +1000 Subject: [PATCH 21/26] Bump the github-actions group across 1 directory with 7 updates (#621) Bumps the github-actions group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `6.1.0` | `7.0.1` | | [actions/setup-node](https://github.com/actions/setup-node) | `6.5.0` | `7.0.0` | | [actions/github-script](https://github.com/actions/github-script) | `7.1.0` | `9.0.0` | | [github/codeql-action/init](https://github.com/github/codeql-action) | `3.37.7` | `4.37.7` | | [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `3.37.7` | `4.37.7` | | [github/codeql-action/analyze](https://github.com/github/codeql-action) | `3.37.7` | `4.37.7` | | [actions/stale](https://github.com/actions/stale) | `8.0.0` | `11.0.0` | Updates `actions/checkout` from 6.1.0 to 7.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/d23441a48e516b6c34aea4fa41551a30e30af803...3d3c42e5aac5ba805825da76410c181273ba90b1) Updates `actions/setup-node` from 6.5.0 to 7.0.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/249970729cb0ef3589644e2896645e5dc5ba9c38...820762786026740c76f36085b0efc47a31fe5020) Updates `actions/github-script` from 7.1.0 to 9.0.0 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/f28e40c7f34bde8b3046d885e986cb6290c5673b...3a2844b7e9c422d3c10d287c895573f7108da1b3) Updates `github/codeql-action/init` from 3.37.7 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f3712979fa5f215279b101dd0a2e3bdfb4353324...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/autobuild` from 3.37.7 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f3712979fa5f215279b101dd0a2e3bdfb4353324...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/analyze` from 3.37.7 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f3712979fa5f215279b101dd0a2e3bdfb4353324...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `actions/stale` from 8.0.0 to 11.0.0 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/1160a2240286f5da8ec72b1c0816ce2481aabf84...4391f3da665fdf50b6810c1a66712fb9ba21aa93) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: github/codeql-action/init dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/stale dependency-version: 11.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/azure-login-canary.yml | 2 +- .github/workflows/azure-login-live-tests.yml | 50 ++++++++++---------- .github/workflows/ci.yml | 4 +- .github/workflows/codeql.yml | 8 ++-- .github/workflows/defaultLabels.yml | 6 +-- .github/workflows/markdownlint.yml | 4 +- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/azure-login-canary.yml b/.github/workflows/azure-login-canary.yml index 23bc1405b..8978ff008 100644 --- a/.github/workflows/azure-login-canary.yml +++ b/.github/workflows/azure-login-canary.yml @@ -30,7 +30,7 @@ jobs: az --version - name: Check out repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 'Az CLI login with subscription' uses: azure/login@v3 diff --git a/.github/workflows/azure-login-live-tests.yml b/.github/workflows/azure-login-live-tests.yml index 1e102279c..0e2aeffad 100644 --- a/.github/workflows/azure-login-live-tests.yml +++ b/.github/workflows/azure-login-live-tests.yml @@ -55,10 +55,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x @@ -147,10 +147,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x @@ -280,10 +280,10 @@ jobs: environment: Automation test steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x @@ -363,10 +363,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x @@ -393,7 +393,7 @@ jobs: - name: Check Last step failed if: steps.cli_3.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -410,7 +410,7 @@ jobs: - name: Check Last step failed if: steps.ps_3.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -426,10 +426,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x @@ -448,7 +448,7 @@ jobs: - name: Check Last step failed if: steps.login_4.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -463,7 +463,7 @@ jobs: - name: Check Last step failed if: steps.login_5.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -477,7 +477,7 @@ jobs: - name: Check Last step failed if: steps.login_6.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -493,7 +493,7 @@ jobs: - name: Check Last step failed if: steps.login_7.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -510,7 +510,7 @@ jobs: - name: Check Last step failed if: steps.login_8.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -538,7 +538,7 @@ jobs: - name: Check Last step failed if: steps.ps_8.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -566,7 +566,7 @@ jobs: - name: Check Last step failed if: steps.ps_9.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -585,7 +585,7 @@ jobs: - name: Check Last step failed if: steps.login_10.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -602,7 +602,7 @@ jobs: - name: Check Last step failed if: steps.login_11.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -622,7 +622,7 @@ jobs: - name: Check Last step failed if: steps.login_12.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -638,7 +638,7 @@ jobs: - name: Check Last step failed if: steps.login_13.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -654,7 +654,7 @@ jobs: - name: Check Last step failed if: steps.login_14.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') @@ -669,7 +669,7 @@ jobs: - name: Check Last step failed if: steps.login_15.outcome == 'success' - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | core.setFailed('Last action should fail but not. Please check it.') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09aede1f1..2ca62c4cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: steps: - name: 'Checking out repo code' - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set Node.js 24.x for GitHub Action - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 793db6d20..ee7a94e6b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,18 +19,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: javascript # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -44,4 +44,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f3712979fa5f215279b101dd0a2e3bdfb4353324 # v3.37.7 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 diff --git a/.github/workflows/defaultLabels.yml b/.github/workflows/defaultLabels.yml index c190f060f..27382ba84 100644 --- a/.github/workflows/defaultLabels.yml +++ b/.github/workflows/defaultLabels.yml @@ -14,7 +14,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 name: Setting issue as idle with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -25,7 +25,7 @@ jobs: operations-per-run: 100 exempt-issue-labels: 'backlog' - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 name: Setting PR as idle with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -35,7 +35,7 @@ jobs: days-before-close: -1 operations-per-run: 100 - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 name: Close issue with no feedback for 20 days with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 2f1fdf314..91a56caae 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -7,9 +7,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Use Node.js - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24.x - name: Run Markdownlint From 905554eb7ba5516b9febd304678709a5e96c4bbc Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 13:03:41 +1000 Subject: [PATCH 22/26] Add admin-gated Release workflow (#610) * Add admin-gated Release workflow Reproduces the Azure Login Action release runbook as a manually dispatched workflow: checkout master, build, test, commit the built lib/ to a releases/vX.Y.Z branch, tag that commit, force-move the major tag (e.g. v3), and publish a GitHub Release. Admin-only is enforced two ways: - environment: release, which pauses the run until a required reviewer (admin) approves. This is the primary gate and must be configured with required reviewers in repo settings before the first real release. - an 'Ensure triggered by an admin' step that fails fast for non-admins. Actions are pinned to @v6 to match the rest of the repo. * Add admin-gated Rollback workflow Undo a bad release by re-pointing the major tag (e.g. v3) back to a known-good, already-published version tag. This is the fast consumer-facing fix: anyone on azure/login@v3 recovers immediately. - Admin-gated the same two ways as release.yml: environment: release approval plus an actor-admin check. - Shares the 'release' concurrency group so a rollback can never race an in-flight release (both push the same major tag). - Verifies the target tag exists and carries both built entry points (lib/main/index.js and lib/cleanup/index.js) before moving the major tag, so it can never point consumers at unbuilt code. - Optionally resets the 'Latest' release badge; tolerates a missing Release object so a cosmetic badge failure never fails a successful tag rollback. - Does NOT delete the bad tag/branch/release (someone may have pinned the exact version); left as a deliberate manual step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Build releases with the node version from action.yml * Restrict release ref allowlist to master and hotfix/* branches The allowlist previously accepted releases/v2 for v2 maintenance, but releases/v2 is a stale early-v2 snapshot that does not contain the later v2 releases (v2.3.0, v2.3.1, ...). Releasing from it would ship old code. The latest v2 source lives in the newest v2 release tag. To cut a back-major release, branch a hotfix off that tag (git checkout -b hotfix/v2.3.2 v2.3.1), apply the fix, and release from the hotfix/* branch. Drop releases/v2 from the allowlist so it can no longer be selected by mistake. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop explicit 'latest' handling; rely on GitHub auto-latest gh release create defaults to marking the highest-version release as "Latest", so a back-major release (e.g. a v2 hotfix) never steals the badge from the current major on its own. The explicit latest controls were therefore redundant, and rollback's badge edit conflicted with immutable releases. - release.yml: remove the latest input, the back-major latest guard, and the --latest flag on gh release create. - rollback.yml: remove the set_latest input and the gh release edit --latest badge step. Rollback now does only the major-tag move (matching the original runbook) plus the admin gate and built-artifact checks. The "Latest" badge is display-only and never affects what azure/login@vN resolves to, so this is behaviourally safe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 145 +++++++++++++++++++++++++++++++++ .github/workflows/rollback.yml | 109 +++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/rollback.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..510efe23d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,145 @@ +name: Release + +# Manually triggered by a maintainer. Reproduces the Azure Login Action +# release runbook: build -> commit built lib to a release branch -> tag -> +# publish GitHub Release -> move the major tag (e.g. v3). +# +# ADMIN-ONLY: GitHub cannot restrict workflow_dispatch to admins at the trigger +# level (any user with write access can dispatch). Admin-only is enforced two ways: +# 1. environment: release -> requires an admin reviewer to approve the run +# (configure required reviewers under Settings > Environments > release). +# This is the real gate: the destructive steps cannot run without approval. +# 2. The "Ensure triggered by an admin" step below fails fast for non-admins. +on: + workflow_dispatch: + inputs: + version: + description: "Release version, e.g. v3.1.0" + required: true + ref: + description: "Branch to release from. Use master for the current major (v3). For a back-major release (e.g. v2), branch a hotfix off the latest release tag (git checkout -b hotfix/v2.3.2 v2.3.1) and pass that hotfix/* branch." + required: false + default: master + +permissions: + contents: write # push branch/tag, force-move major tag, create the Release + +# Serialize releases: never let two release runs push tags/branches at once. +concurrency: + group: release + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + # Admin gate: this environment must have required reviewers (admins) configured + # in repo settings. The run pauses here until an admin approves. + environment: release + steps: + - name: Ensure triggered by an admin + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + PERM=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.permission') + echo "Actor '$ACTOR' has repository permission: $PERM" + if [[ "$PERM" != "admin" ]]; then + echo "::error::Release must be triggered by a repository admin (actor has '$PERM')." + exit 1 + fi + + - name: Validate version input + env: + V: ${{ inputs.version }} + run: | + if [[ ! "$V" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::version must look like v3.1.0 (got '$V')" + exit 1 + fi + + - name: Validate release ref + env: + REF: ${{ inputs.ref }} + run: | + # Only allow releasing from vetted lines: master (current major) and + # the v2 maintenance branch. Prevents shipping arbitrary unreviewed + # branches straight to consumers via the moved major tag. + case "$REF" in + master|hotfix/*) echo "ref '$REF' allowed" ;; + *) echo "::error::ref '$REF' not permitted for release (allowed: master, or a hotfix/* branch based on the latest release tag for a back-major release)"; exit 1 ;; + esac + + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref }} # master for the current major; a hotfix/* branch for a back-major release + fetch-depth: 0 + fetch-tags: true + + - name: Ensure version is new + env: + V: ${{ inputs.version }} + run: | + if git ls-remote --exit-code --tags origin "refs/tags/$V" >/dev/null 2>&1; then + echo "::error::tag $V already exists"; exit 1 + fi + if git ls-remote --exit-code --heads origin "refs/heads/releases/$V" >/dev/null 2>&1; then + echo "::error::branch releases/$V already exists"; exit 1 + fi + + - name: Determine runtime node from action.yml + id: node + shell: bash + run: | + ver=$(grep "using:" action.yml | grep -oE "node[0-9]+" | grep -oE "[0-9]+" | head -1) + echo "version=$ver" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-node@v6 + with: + node-version: ${{ steps.node.outputs.version }} # matches action.yml runs.using + cache: npm + + - name: Install, build, test + run: | + npm ci + npm run build + npm test + + - name: Create release branch with built lib + env: + V: ${{ inputs.version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "releases/$V" + git add -f lib + git commit -m "prepare release $V" + git push --set-upstream origin "releases/$V" + + - name: Create and push tag + env: + V: ${{ inputs.version }} + run: | + git tag -a -m "$V" "$V" + git push origin "$V" + + - name: Move major tag (e.g. v3) + env: + V: ${{ inputs.version }} + run: | + MAJOR="${V%%.*}" # v3.1.0 -> v3 + git tag -d "$MAJOR" || true + git tag -a "$MAJOR" -m "Update $MAJOR to $V" + git push origin "$MAJOR" -f + + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ github.token }} + V: ${{ inputs.version }} + run: | + # No --latest flag: GitHub automatically marks the highest-version + # release as "Latest", so a back-major release (e.g. a v2 hotfix) does + # not steal the badge from the current major. + gh release create "$V" \ + --title "Azure Login Action $V" \ + --generate-notes diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml new file mode 100644 index 000000000..74f127e99 --- /dev/null +++ b/.github/workflows/rollback.yml @@ -0,0 +1,109 @@ +name: Rollback + +# Manually triggered by a maintainer to undo a bad release. A release moves the +# major tag (e.g. v3) forward to the new version; consumers pinned to +# `azure/login@v3` immediately get it. If that release is broken, rollback +# re-points the major tag back to a known-good, already-published version tag +# (e.g. v3.0.1), which contains the built lib/. This is the fast consumer-facing +# fix. It does NOT delete the bad tag/branch/release by default - deletion is +# destructive (someone may have pinned the exact version) and is left to a +# deliberate manual step if ever needed. +# +# ADMIN-ONLY: enforced the same two ways as release.yml: +# 1. environment: release -> requires an admin reviewer to approve the run. +# 2. The "Ensure triggered by an admin" step fails fast for non-admins. +on: + workflow_dispatch: + inputs: + target_version: + description: "Known-good version to roll the major tag back to, e.g. v3.0.1 (must be an existing tag)" + required: true + +permissions: + contents: write # force-move the major tag, update the Release + +# Share the 'release' concurrency group so a rollback can never race an +# in-flight release (both push the same major tag). +concurrency: + group: release + cancel-in-progress: false + +jobs: + rollback: + runs-on: ubuntu-latest + # Admin gate: this environment must have required reviewers (admins) + # configured in repo settings. The run pauses here until an admin approves. + environment: release + steps: + - name: Ensure triggered by an admin + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + PERM=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.permission') + echo "Actor '$ACTOR' has repository permission: $PERM" + if [[ "$PERM" != "admin" ]]; then + echo "::error::Rollback must be triggered by a repository admin (actor has '$PERM')." + exit 1 + fi + + - name: Validate target_version input + env: + V: ${{ inputs.target_version }} + run: | + if [[ ! "$V" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::target_version must look like v3.0.1 (got '$V')" + exit 1 + fi + + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Verify target tag exists and contains built lib + env: + V: ${{ inputs.target_version }} + run: | + # The target must be an existing published version tag... + if ! git rev-parse -q --verify "refs/tags/$V" >/dev/null; then + echo "::error::tag $V does not exist - can only roll back to an already-published version" + exit 1 + fi + # ...and it must carry the built entry points (main + post), or + # consumers would break worse than before (the major tag must always + # point at runnable code). action.yml uses lib/main/index.js and + # lib/cleanup/index.js. + git checkout --quiet "$V" + missing="" + [[ -f lib/main/index.js ]] || missing="$missing lib/main/index.js" + [[ -f lib/cleanup/index.js ]] || missing="$missing lib/cleanup/index.js" + if [[ -n "$missing" ]]; then + echo "::error::tag $V is missing built entry point(s):$missing - refusing to point the major tag at unbuilt code" + exit 1 + fi + + - name: Re-point major tag to target + env: + V: ${{ inputs.target_version }} + run: | + MAJOR="${V%%.*}" # v3.0.1 -> v3 + CURRENT=$(git rev-parse "refs/tags/$MAJOR^{commit}" 2>/dev/null || echo "none") + TARGET=$(git rev-parse "$V^{commit}") + echo "Rolling major $MAJOR: $CURRENT -> $TARGET ($V)" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -d "$MAJOR" || true + git tag -a "$MAJOR" -m "Rollback $MAJOR to $V" "$V" + git push origin "$MAJOR" -f + + - name: Summary + env: + V: ${{ inputs.target_version }} + run: | + MAJOR="${V%%.*}" + echo "### Rollback complete" >> "$GITHUB_STEP_SUMMARY" + echo "- Major tag \`$MAJOR\` now points at \`$V\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Consumers using \`azure/login@$MAJOR\` now get \`$V\`" >> "$GITHUB_STEP_SUMMARY" + echo "- The bad tag/branch/release were NOT deleted (delete manually if required)." >> "$GITHUB_STEP_SUMMARY" From ca06f43012dcfa14c59d4d0509084aace81dbb5d Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 13:26:15 +1000 Subject: [PATCH 23/26] Refactor PS login to use a static param()-bound script (#607) * Refactor PS login to use a static param()-bound script * Update readme * Add injection-safety test for federated token argv handling --------- Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> --- README.md | 10 + .../PowerShell/AzPSScriptBuilder.test.ts | 306 ++++++++---------- package.json | 2 +- scripts/copy-ps-assets.js | 13 + src/PowerShell/AzPSLogin.ps1 | 74 +++++ src/PowerShell/AzPSLogin.ts | 8 +- src/PowerShell/AzPSScriptBuilder.ts | 126 +++----- src/PowerShell/AzPSUtils.ts | 13 +- 8 files changed, 287 insertions(+), 265 deletions(-) create mode 100644 scripts/copy-ps-assets.js create mode 100644 src/PowerShell/AzPSLogin.ps1 diff --git a/README.md b/README.md index c4f105c67..486be60c0 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,16 @@ Azure Login Action supports different ways of authentication with Azure. > [!WARNING] > Avoid using managed identity login on self-hosted runners in public repositories. Managed identities enable secure authentication with Azure resources and obtain Microsoft Entra ID tokens without the need for explicit credential management. Any user can open pull requests against your repository and access your self-hosted runners without credentials. See more details in [self-hosted runner security](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security). +** ** + +> [!WARNING] +> Only pass values from `${{ secrets.* }}` into `client-id`, `tenant-id`, `subscription-id`, and `creds`. Do not pipe values from `${{ github.event.* }}` (pull request titles, issue comments, `workflow_dispatch` inputs, branch names, etc.) into these inputs. Untrusted values in these fields can allow attackers to influence the Azure identity the action logs in as. + +** ** + +> [!WARNING] +> Only set `enable-AzPSSession: true` if your workflow runs Azure PowerShell (`Az.*`) cmdlets. If your workflow only uses the Azure CLI (`az ...`), leave `enable-AzPSSession` unset (the default is `false`). Enabling it launches an additional PowerShell login step that is unnecessary for CLI-only workflows. + ## Supported Versions Azure Login follows a major-version support model. diff --git a/__tests__/PowerShell/AzPSScriptBuilder.test.ts b/__tests__/PowerShell/AzPSScriptBuilder.test.ts index 76b32c244..a1dba885c 100644 --- a/__tests__/PowerShell/AzPSScriptBuilder.test.ts +++ b/__tests__/PowerShell/AzPSScriptBuilder.test.ts @@ -1,7 +1,7 @@ -import AzPSSCriptBuilder from "../../src/PowerShell/AzPSScriptBuilder"; +import AzPSScriptBuilder from "../../src/PowerShell/AzPSScriptBuilder"; import { LoginConfig } from "../../src/common/LoginConfig"; -describe("Getting AzLogin PS script", () => { +describe("Building the Az PS login invocation", () => { function setEnv(name: string, value: string) { process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value; @@ -10,7 +10,7 @@ describe("Getting AzLogin PS script", () => { function cleanEnv() { for (const envKey in process.env) { if (envKey.startsWith('INPUT_')) { - delete process.env[envKey] + delete process.env[envKey]; } } } @@ -19,75 +19,47 @@ describe("Getting AzLogin PS script", () => { cleanEnv(); }); - test('getImportLatestModuleScript', () => { - expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path"); - expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("Import-Module -Name $latestModulePath"); + test('getImportLatestModuleScript still emits the interpolated module-import script', () => { + expect(AzPSScriptBuilder.getImportLatestModuleScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path"); + expect(AzPSScriptBuilder.getImportLatestModuleScript("TestModule")).toContain("Import-Module -Name $latestModulePath"); }); - test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true', () => { - setEnv('environment', 'azurecloud'); - setEnv('enable-AzPSSession', 'true'); - setEnv('allow-no-subscriptions', 'true'); - setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { - 'clientId': 'client-id', - 'clientSecret': "client-secret", - 'tenantId': 'tenant-id', - 'subscriptionId': 'subscription-id' - } - setEnv('creds', JSON.stringify(creds)); - - let loginConfig = new LoginConfig(); - loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('service principal with secret'); - }); + test('getScriptPath resolves to AzPSLogin.ps1 next to the compiled module', () => { + expect(AzPSScriptBuilder.getScriptPath()).toMatch(/AzPSLogin\.ps1$/); }); - test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true, secret with single-quote', () => { + test('SP + secret: values ride as pwsh params; secret rides via env var', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { - 'clientId': 'client-id', - 'clientSecret': "client-se'cret", - 'tenantId': 'tenant-id', - 'subscriptionId': 'subscription-id' - } - setEnv('creds', JSON.stringify(creds)); - - let loginConfig = new LoginConfig(); - loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-se''cret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('service principal with secret'); - }); - }); - - test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=false', () => { - setEnv('environment', 'azurecloud'); - setEnv('enable-AzPSSession', 'true'); - setEnv('allow-no-subscriptions', 'false'); // same as true - setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { + const creds = { 'clientId': 'client-id', 'clientSecret': 'client-secret', 'tenantId': 'tenant-id', 'subscriptionId': 'subscription-id' - } + }; setEnv('creds', JSON.stringify(creds)); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('service principal with secret'); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ methodName, args, env }) => { + expect(methodName).toBe('service principal with secret'); + expect(args[0]).toBe('-File'); + expect(args[1]).toMatch(/AzPSLogin\.ps1$/); + expect(args).toEqual(expect.arrayContaining([ + '-Environment', 'azurecloud', + '-AuthType', 'SERVICE_PRINCIPAL', + '-Tenant', 'tenant-id', + '-Subscription', 'subscription-id', + '-ApplicationId', 'client-id', + ])); + expect(env[AzPSScriptBuilder.ENV_SP_SECRET]).toBe('client-secret'); + expect(env[AzPSScriptBuilder.ENV_FEDERATED_TOKEN]).toBeUndefined(); }); }); - test('getAzPSLoginScript for OIDC', () => { + test('SP + OIDC: federated token rides via env var; no client secret', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'false'); @@ -96,211 +68,197 @@ describe("Getting AzLogin PS script", () => { setEnv('client-id', 'client-id'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";}); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -ApplicationId 'client-id' -FederatedToken 'fake-token' -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('OIDC'); + jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => { loginConfig.federatedToken = "fake-token"; }); + + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ methodName, args, env }) => { + expect(methodName).toBe('OIDC'); + expect(args).toEqual(expect.arrayContaining([ + '-Tenant', 'tenant-id', + '-Subscription', 'subscription-id', + '-ApplicationId', 'client-id', + ])); + expect(env[AzPSScriptBuilder.ENV_FEDERATED_TOKEN]).toBe('fake-token'); + expect(env[AzPSScriptBuilder.ENV_SP_SECRET]).toBeUndefined(); }); }); - test('getAzPSLoginScript for System MI', () => { + test('system-assigned MI: no ApplicationId param, no env vars', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'false'); setEnv('subscription-id', 'subscription-id'); setEnv('auth-type', 'IDENTITY'); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('system-assigned managed identity'); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ methodName, args, env }) => { + expect(methodName).toBe('system-assigned managed identity'); + expect(args).toEqual(expect.arrayContaining([ + '-AuthType', 'IDENTITY', + '-Subscription', 'subscription-id', + ])); + expect(args).not.toContain('-ApplicationId'); + expect(Object.keys(env)).toHaveLength(0); }); }); - test('getAzPSLoginScript for System MI without subscription id', () => { + test('system-assigned MI without subscription id: subscription param omitted', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'false'); - // setEnv('subscription-id', 'subscription-id'); setEnv('auth-type', 'IDENTITY'); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('system-assigned managed identity'); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ methodName, args }) => { + expect(methodName).toBe('system-assigned managed identity'); + expect(args).not.toContain('-Subscription'); }); }); - test('getAzPSLoginScript for user-assigned MI', () => { + test('user-assigned MI: ApplicationId param present, no env vars', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'IDENTITY'); setEnv('client-id', 'client-id'); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' -InformationAction Ignore | out-null;")).toBeTruthy(); - expect(loginMethod).toBe('user-assigned managed identity'); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ methodName, args, env }) => { + expect(methodName).toBe('user-assigned managed identity'); + expect(args).toEqual(expect.arrayContaining([ + '-AuthType', 'IDENTITY', + '-ApplicationId', 'client-id', + ])); + expect(Object.keys(env)).toHaveLength(0); }); }); - const INJECT_RAW = "abc' ; Start-Process calc ; $x='"; - const INJECT_ESCAPED = "abc'' ; Start-Process calc ; $x=''"; - - test('SECURITY: tenant-id single quote is escaped (SP+secret path)', () => { - setEnv('environment', 'azurecloud'); + test('AzureStack: ArmEndpoint passed as param', () => { + setEnv('environment', 'azurestack'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { + const creds = { 'clientId': 'client-id', 'clientSecret': 'client-secret', - 'tenantId': INJECT_RAW, - 'subscriptionId': 'subscription-id' - } + 'tenantId': 'tenant-id', + 'subscriptionId': 'subscription-id', + 'resourceManagerEndpointUrl': 'https://management.azurestack.local/' + }; setEnv('creds', JSON.stringify(creds)); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-Tenant '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-Tenant '${INJECT_RAW}'`); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ args }) => { + expect(args).toEqual(expect.arrayContaining([ + '-ArmEndpoint', 'https://management.azurestack.local/', + ])); }); }); - test('SECURITY: subscription-id single quote is escaped (SP+secret path)', () => { - setEnv('environment', 'azurecloud'); + test('SECURITY: adversarial ArmEndpoint travels as a discrete argv element', () => { + setEnv('environment', 'azurestack'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { + const nasty = "https://mgmt.local/' ; Start-Process calc ; $x='"; + const creds = { 'clientId': 'client-id', 'clientSecret': 'client-secret', 'tenantId': 'tenant-id', - 'subscriptionId': INJECT_RAW - } + 'subscriptionId': 'subscription-id', + 'resourceManagerEndpointUrl': nasty + }; setEnv('creds', JSON.stringify(creds)); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-Subscription '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-Subscription '${INJECT_RAW}'`); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ args }) => { + const armIndex = args.indexOf('-ArmEndpoint'); + expect(armIndex).toBeGreaterThan(-1); + expect(args[armIndex + 1]).toBe(nasty); + const otherArgs = args.filter((_, i) => i !== armIndex + 1); + expect(otherArgs.some(a => a.includes(nasty))).toBe(false); }); }); - test('SECURITY: client-id single quote is escaped (SP+secret path, PSCredential)', () => { + // Structural safety: no matter how nasty a value is, it can never be re-parsed + // as PowerShell code because it's a distinct argv element / env var, not a + // substring inside a script literal. + const NASTY_VALUES = [ + "abc' ; Start-Process calc ; $x='", + 'abc"; whoami ; #', + "abc\nStart-Process calc", + ]; + + test.each(NASTY_VALUES)('SECURITY: adversarial tenant value %j travels as a discrete argv element', (nasty) => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { - 'clientId': INJECT_RAW, + const creds = { + 'clientId': 'client-id', 'clientSecret': 'client-secret', - 'tenantId': 'tenant-id', + 'tenantId': nasty, 'subscriptionId': 'subscription-id' - } + }; setEnv('creds', JSON.stringify(creds)); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`New-Object System.Management.Automation.PSCredential('${INJECT_ESCAPED}',`); - expect(loginScript).not.toContain(`New-Object System.Management.Automation.PSCredential('${INJECT_RAW}',`); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ args }) => { + const tenantIndex = args.indexOf('-Tenant'); + expect(tenantIndex).toBeGreaterThan(-1); + expect(args[tenantIndex + 1]).toBe(nasty); + const otherArgs = args.filter((_, i) => i !== tenantIndex + 1); + expect(otherArgs.some(a => a.includes(nasty))).toBe(false); }); }); - test('SECURITY: client-id single quote is escaped (OIDC path)', () => { - setEnv('environment', 'azurecloud'); - setEnv('enable-AzPSSession', 'true'); - setEnv('allow-no-subscriptions', 'false'); - setEnv('tenant-id', 'tenant-id'); - setEnv('subscription-id', 'subscription-id'); - setEnv('client-id', INJECT_RAW); - setEnv('auth-type', 'SERVICE_PRINCIPAL'); - - let loginConfig = new LoginConfig(); - loginConfig.initialize(); - jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => { loginConfig.federatedToken = "fake-token"; }); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-ApplicationId '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-ApplicationId '${INJECT_RAW}'`); - }); - }); - - test('SECURITY: client-id single quote is escaped (user-assigned MI path)', () => { + test('SECURITY: adversarial client-secret rides in env var only, never in argv', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'true'); - setEnv('auth-type', 'IDENTITY'); - setEnv('client-id', INJECT_RAW); - - let loginConfig = new LoginConfig(); - loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-AccountId '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-AccountId '${INJECT_RAW}'`); - }); - }); - - test('SECURITY: tenant-id and subscription-id single quotes are escaped (system-assigned MI path)', () => { - setEnv('environment', 'azurecloud'); - setEnv('enable-AzPSSession', 'true'); - setEnv('allow-no-subscriptions', 'false'); - setEnv('tenant-id', INJECT_RAW); - setEnv('subscription-id', INJECT_RAW); - setEnv('auth-type', 'IDENTITY'); - - let loginConfig = new LoginConfig(); - loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-Tenant '${INJECT_ESCAPED}'`); - expect(loginScript).toContain(`-Subscription '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-Tenant '${INJECT_RAW}'`); - expect(loginScript).not.toContain(`-Subscription '${INJECT_RAW}'`); - }); - }); - - test('SECURITY: resourceManagerEndpointUrl single quote is escaped (AzureStack path)', () => { - setEnv('environment', 'azurestack'); - setEnv('enable-AzPSSession', 'true'); - setEnv('allow-no-subscriptions', 'true'); setEnv('auth-type', 'SERVICE_PRINCIPAL'); - let creds = { + const nasty = "abc' ; Start-Process calc ; $x='"; + const creds = { 'clientId': 'client-id', - 'clientSecret': 'client-secret', + 'clientSecret': nasty, 'tenantId': 'tenant-id', - 'subscriptionId': 'subscription-id', - 'resourceManagerEndpointUrl': INJECT_RAW - } + 'subscriptionId': 'subscription-id' + }; setEnv('creds', JSON.stringify(creds)); - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([_, loginScript]) => { - expect(loginScript).toContain(`-ARMEndpoint '${INJECT_ESCAPED}'`); - expect(loginScript).not.toContain(`-ARMEndpoint '${INJECT_RAW}'`); + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ args, env }) => { + expect(env[AzPSScriptBuilder.ENV_SP_SECRET]).toBe(nasty); + expect(args.some(a => a.includes(nasty))).toBe(false); }); }); - test('SECURITY: escapePSSingleQuoted handles null/undefined without throwing', () => { + test('SECURITY: federated token rides in env var only, never in argv', () => { setEnv('environment', 'azurecloud'); setEnv('enable-AzPSSession', 'true'); setEnv('allow-no-subscriptions', 'false'); - setEnv('auth-type', 'IDENTITY'); + setEnv('tenant-id', 'tenant-id'); + setEnv('subscription-id', 'subscription-id'); + setEnv('client-id', 'client-id'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + const nasty = "abc' ; Start-Process calc ; $x='"; - let loginConfig = new LoginConfig(); + const loginConfig = new LoginConfig(); loginConfig.initialize(); - return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript).toContain("Connect-AzAccount -Identity -Environment 'azurecloud'"); - expect(loginMethod).toBe('system-assigned managed identity'); + jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => { loginConfig.federatedToken = nasty; }); + + return AzPSScriptBuilder.getAzPSLoginInvocation(loginConfig).then(({ args, env }) => { + expect(env[AzPSScriptBuilder.ENV_FEDERATED_TOKEN]).toBe(nasty); + expect(args.some(a => a.includes(nasty))).toBe(false); }); }); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 6364c76cd..d087154de 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure", "main": "lib/main/index.js", "scripts": { - "build:main": "ncc build src/main.ts -o lib/main", + "build:main": "ncc build src/main.ts -o lib/main && node scripts/copy-ps-assets.js", "build:cleanup": "ncc build src/cleanup.ts -o lib/cleanup", "build": "npm run build:main && npm run build:cleanup", "test": "jest" diff --git a/scripts/copy-ps-assets.js b/scripts/copy-ps-assets.js new file mode 100644 index 000000000..801255167 --- /dev/null +++ b/scripts/copy-ps-assets.js @@ -0,0 +1,13 @@ +// Copies the static PowerShell login script into the compiled action bundle. +// Run as part of `npm run build:main` so that lib/main/index.js can locate +// AzPSLogin.ps1 via `path.join(__dirname, 'AzPSLogin.ps1')` at runtime. + +const fs = require('fs'); +const path = require('path'); + +const src = path.join(__dirname, '..', 'src', 'PowerShell', 'AzPSLogin.ps1'); +const dest = path.join(__dirname, '..', 'lib', 'main', 'AzPSLogin.ps1'); + +fs.mkdirSync(path.dirname(dest), { recursive: true }); +fs.copyFileSync(src, dest); +console.log(`Copied ${path.relative(process.cwd(), src)} -> ${path.relative(process.cwd(), dest)}`); diff --git a/src/PowerShell/AzPSLogin.ps1 b/src/PowerShell/AzPSLogin.ps1 new file mode 100644 index 000000000..c084a62e8 --- /dev/null +++ b/src/PowerShell/AzPSLogin.ps1 @@ -0,0 +1,74 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidateSet('azurecloud', 'azurechinacloud', 'azureusgovernment', 'azuregermancloud', 'azurestack')] + [string]$Environment, + + [Parameter(Mandatory)] + [ValidateSet('SERVICE_PRINCIPAL', 'IDENTITY')] + [string]$AuthType, + + [string]$Tenant, + + [string]$Subscription, + + [string]$ApplicationId, + + [string]$ArmEndpoint +) + +$ErrorActionPreference = 'Stop' +$WarningPreference = 'SilentlyContinue' + +try { + if ($Environment -eq 'azurestack') { + if ([string]::IsNullOrEmpty($ArmEndpoint)) { + throw "ArmEndpoint is required when Environment is 'azurestack'." + } + Add-AzEnvironment -Name $Environment -ARMEndpoint $ArmEndpoint | Out-Null + } + + $connectArgs = @{ + Environment = $Environment + InformationAction = 'Ignore' + } + if ($Tenant) { $connectArgs['Tenant'] = $Tenant } + if ($Subscription) { $connectArgs['Subscription'] = $Subscription } + + if ($AuthType -eq 'SERVICE_PRINCIPAL') { + $connectArgs['ServicePrincipal'] = $true + + if ($env:AZURE_LOGIN_ACTION__SP_SECRET) { + $secure = ConvertTo-SecureString $env:AZURE_LOGIN_ACTION__SP_SECRET -AsPlainText -Force + $connectArgs['Credential'] = New-Object System.Management.Automation.PSCredential($ApplicationId, $secure) + Remove-Item Env:AZURE_LOGIN_ACTION__SP_SECRET -ErrorAction SilentlyContinue + } + elseif ($env:AZURE_LOGIN_ACTION__FEDERATED_TOKEN) { + $connectArgs['ApplicationId'] = $ApplicationId + $connectArgs['FederatedToken'] = $env:AZURE_LOGIN_ACTION__FEDERATED_TOKEN + Remove-Item Env:AZURE_LOGIN_ACTION__FEDERATED_TOKEN -ErrorAction SilentlyContinue + } + else { + throw "SERVICE_PRINCIPAL auth requires either AZURE_LOGIN_ACTION__SP_SECRET or AZURE_LOGIN_ACTION__FEDERATED_TOKEN in the environment." + } + } + else { + $connectArgs['Identity'] = $true + if ($ApplicationId) { + $connectArgs['AccountId'] = $ApplicationId + } + } + + Connect-AzAccount @connectArgs | Out-Null + + $output = @{ Success = $true; Result = '' } +} +catch { + $output = @{ Success = $false; Error = $_.Exception.Message } +} +finally { + Remove-Item Env:AZURE_LOGIN_ACTION__SP_SECRET -ErrorAction SilentlyContinue + Remove-Item Env:AZURE_LOGIN_ACTION__FEDERATED_TOKEN -ErrorAction SilentlyContinue +} + +ConvertTo-Json $output diff --git a/src/PowerShell/AzPSLogin.ts b/src/PowerShell/AzPSLogin.ts index 6e0c7bd6a..24103c239 100644 --- a/src/PowerShell/AzPSLogin.ts +++ b/src/PowerShell/AzPSLogin.ts @@ -15,10 +15,10 @@ export class AzPSLogin { core.info(`Running Azure PowerShell Login.`); AzPSUtils.setPSModulePathForGitHubRunner(); await AzPSUtils.importLatestAzAccounts(); - const [loginMethod, loginScript] = await AzPSScriptBuilder.getAzPSLoginScript(this.loginConfig); - core.info(`Attempting Azure PowerShell login by using ${loginMethod}...`); - core.debug(`Azure PowerShell Login Script: ${loginScript}`); - await AzPSUtils.runPSScript(loginScript); + const { methodName, args, env } = await AzPSScriptBuilder.getAzPSLoginInvocation(this.loginConfig); + core.info(`Attempting Azure PowerShell login by using ${methodName}...`); + core.debug(`Azure PowerShell login invocation: pwsh ${JSON.stringify(args)}`); + await AzPSUtils.runPSFile(args, env); console.log(`Running Azure PowerShell Login successfully.`); } } diff --git a/src/PowerShell/AzPSScriptBuilder.ts b/src/PowerShell/AzPSScriptBuilder.ts index 8b6b05824..d5470ea30 100644 --- a/src/PowerShell/AzPSScriptBuilder.ts +++ b/src/PowerShell/AzPSScriptBuilder.ts @@ -1,7 +1,21 @@ +import * as path from 'path'; import { LoginConfig } from '../common/LoginConfig'; +export interface AzPSLoginInvocation { + methodName: string; + args: string[]; + env: Record; +} + export default class AzPSScriptBuilder { + static readonly ENV_SP_SECRET = 'AZURE_LOGIN_ACTION__SP_SECRET'; + static readonly ENV_FEDERATED_TOKEN = 'AZURE_LOGIN_ACTION__FEDERATED_TOKEN'; + + static getScriptPath(): string { + return path.join(__dirname, 'AzPSLogin.ps1'); + } + static getImportLatestModuleScript(moduleName: string): string { let script = `try { $ErrorActionPreference = "Stop" @@ -21,103 +35,45 @@ export default class AzPSScriptBuilder { return script; } - // Doubles single quotes for safe interpolation into a PowerShell '...' literal. - private static escapePSSingleQuoted(value: string): string { - if (value === null || value === undefined) { - return ""; + static async getAzPSLoginInvocation(loginConfig: LoginConfig): Promise { + const args: string[] = [ + '-File', AzPSScriptBuilder.getScriptPath(), + '-Environment', loginConfig.environment, + '-AuthType', loginConfig.authType, + ]; + const env: Record = {}; + let methodName: string; + + if (loginConfig.tenantId) { + args.push('-Tenant', loginConfig.tenantId); } - return String(value).split("'").join("''"); - } - - static async getAzPSLoginScript(loginConfig: LoginConfig) { - let loginMethodName = ""; - let commands = ""; - - if (loginConfig.environment.toLowerCase() == "azurestack") { - commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.resourceManagerEndpointUrl)}' | out-null;`; + if (loginConfig.subscriptionId) { + args.push('-Subscription', loginConfig.subscriptionId); } + if (loginConfig.environment.toLowerCase() === 'azurestack') { + args.push('-ArmEndpoint', loginConfig.resourceManagerEndpointUrl); + } + if (loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) { + args.push('-ApplicationId', loginConfig.servicePrincipalId); if (loginConfig.servicePrincipalSecret) { - commands += AzPSScriptBuilder.loginWithSecret(loginConfig); - loginMethodName = 'service principal with secret'; + env[AzPSScriptBuilder.ENV_SP_SECRET] = loginConfig.servicePrincipalSecret; + methodName = 'service principal with secret'; } else { - commands += await AzPSScriptBuilder.loginWithOIDC(loginConfig); - loginMethodName = "OIDC"; + await loginConfig.getFederatedToken(); + env[AzPSScriptBuilder.ENV_FEDERATED_TOKEN] = loginConfig.federatedToken; + methodName = 'OIDC'; } } else { if (loginConfig.servicePrincipalId) { - commands += AzPSScriptBuilder.loginWithUserAssignedIdentity(loginConfig); - loginMethodName = 'user-assigned managed identity'; + args.push('-ApplicationId', loginConfig.servicePrincipalId); + methodName = 'user-assigned managed identity'; } else { - commands += AzPSScriptBuilder.loginWithSystemAssignedIdentity(loginConfig); - loginMethodName = 'system-assigned managed identity'; + methodName = 'system-assigned managed identity'; } } - let script = `try { - $ErrorActionPreference = "Stop" - $WarningPreference = "SilentlyContinue" - $output = @{} - ${commands} - $output['Success'] = $true - $output['Result'] = "" - } - catch { - $output['Success'] = $false - $output['Error'] = $_.exception.Message - } - return ConvertTo-Json $output`; - - return [loginMethodName, script]; - } - - private static loginWithSecret(loginConfig: LoginConfig): string { - let servicePrincipalSecret: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalSecret); - let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); - let loginCmdlet = `$psLoginSecrets = ConvertTo-SecureString '${servicePrincipalSecret}' -AsPlainText -Force; `; - loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${servicePrincipalId}', $psLoginSecrets); `; - - let cmdletSuffix = "-Credential $psLoginCredential"; - loginCmdlet += AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); - - return loginCmdlet; - } - - private static async loginWithOIDC(loginConfig: LoginConfig) { - await loginConfig.getFederatedToken(); - let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); - let federatedToken: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.federatedToken); - let cmdletSuffix = `-ApplicationId '${servicePrincipalId}' -FederatedToken '${federatedToken}'`; - return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); - } - - private static loginWithSystemAssignedIdentity(loginConfig: LoginConfig): string { - let cmdletSuffix = ""; - return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); - } - - static loginWithUserAssignedIdentity(loginConfig: LoginConfig): string { - let servicePrincipalId: string = AzPSScriptBuilder.escapePSSingleQuoted(loginConfig.servicePrincipalId); - let cmdletSuffix = `-AccountId '${servicePrincipalId}'`; - return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix); - } - - private static psLoginCmdlet(authType:string, environment:string, tenantId:string, subscriptionId:string, cmdletSuffix:string){ - let loginCmdlet = `Connect-AzAccount `; - if(authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){ - loginCmdlet += "-ServicePrincipal "; - }else{ - loginCmdlet += "-Identity "; - } - loginCmdlet += `-Environment '${environment}' `; - if(tenantId){ - loginCmdlet += `-Tenant '${AzPSScriptBuilder.escapePSSingleQuoted(tenantId)}' `; - } - if(subscriptionId){ - loginCmdlet += `-Subscription '${AzPSScriptBuilder.escapePSSingleQuoted(subscriptionId)}' `; - } - loginCmdlet += `${cmdletSuffix} -InformationAction Ignore | out-null;`; - return loginCmdlet; + return { methodName, args, env }; } } diff --git a/src/PowerShell/AzPSUtils.ts b/src/PowerShell/AzPSUtils.ts index 4b287cfd7..bc5298b6f 100644 --- a/src/PowerShell/AzPSUtils.ts +++ b/src/PowerShell/AzPSUtils.ts @@ -52,6 +52,14 @@ export class AzPSUtils { } static async runPSScript(psScript: string): Promise { + return AzPSUtils.runPwsh(['-Command', psScript]); + } + + static async runPSFile(args: string[], extraEnv: Record = {}): Promise { + return AzPSUtils.runPwsh(args, extraEnv); + } + + private static async runPwsh(args: string[], extraEnv: Record = {}): Promise { let outputString: string = ""; let commandStdErr = false; const options: any = { @@ -69,9 +77,12 @@ export class AzPSUtils { } } }; + if (Object.keys(extraEnv).length > 0) { + options.env = { ...process.env, ...extraEnv }; + } let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true); - await exec.exec(`"${psPath}"`, ["-Command", psScript], options) + await exec.exec(`"${psPath}"`, args, options) if (commandStdErr) { throw new Error('Azure PowerShell login failed with errors.'); } From a26a33192ede2175e913e9a5d390c6172a1b90d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:22:42 +1000 Subject: [PATCH 24/26] Bump brace-expansion from 1.1.12 to 1.1.18 (#616) Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.18. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.18) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.18 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MaddyMicrosoft --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 421674099..799e18969 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1155,9 +1155,9 @@ "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==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { From ff48d59225932d4cb5f3e865588532d7e979ba88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:24:48 +1000 Subject: [PATCH 25/26] Bump uuid and @actions/core (#589) Removes [uuid](https://github.com/uuidjs/uuid). It's no longer used after updating ancestor dependency [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core). These dependencies need to be updated together. Removes `uuid` Updates `@actions/core` from 1.9.1 to 1.11.1 - [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md) - [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core) --- updated-dependencies: - dependency-name: uuid dependency-version: dependency-type: indirect - dependency-name: "@actions/core" dependency-version: 1.11.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MaddyMicrosoft --- package-lock.json | 17 ++++++----------- package.json | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 799e18969..1d1af4a8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "login", "license": "MIT", "dependencies": { - "@actions/core": "1.9.1", + "@actions/core": "1.11.1", "@actions/exec": "^1.0.1", "@actions/io": "^1.0.1" }, @@ -23,18 +23,13 @@ } }, "node_modules/@actions/core": { - "version": "1.9.1", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", "license": "MIT", "dependencies": { - "@actions/http-client": "^2.0.1", - "uuid": "^8.3.2" - } - }, - "node_modules/@actions/core/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" } }, "node_modules/@actions/exec": { diff --git a/package.json b/package.json index d087154de..a678091fe 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@actions/core": "1.9.1", + "@actions/core": "1.11.1", "@actions/exec": "^1.0.1", "@actions/io": "^1.0.1" } From d90bae5347f733e8026acdee0e5e0f3b5ae6fca6 Mon Sep 17 00:00:00 2001 From: MaddyMicrosoft Date: Tue, 25 Aug 2026 16:20:46 +1000 Subject: [PATCH 26/26] Cap @actions/exec and @actions/core below the ESM-only 3.x majors (#628) @actions/exec 3.0.0 and @actions/core 3.0.1 ship as ESM-only (package.json "type": "module"), which the CommonJS ncc bundle cannot require() (see the failing bumps in #623 and #624, same failure mode as the already-capped @actions/io). Extend the existing ESM cap to these two packages so Dependabot stays on their 2.x (CommonJS) line until the action migrates to ESM. @actions/http-client 3.x remains CommonJS and is intentionally not capped. Co-authored-by: Maddison Das <272712104+MaddyMicrosoft@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 28b238980..f0028db08 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,10 +15,16 @@ updates: # below 7 (the native compiler rewrite) until ts-jest supports it. - dependency-name: "typescript" versions: [">=7"] - # @actions/io 3.x is ESM-only, which breaks the CommonJS ncc build. - # Stay on 2.x (CommonJS, Node 24 support) until the action moves to ESM. + # @actions/io, @actions/exec and @actions/core 3.x are ESM-only + # (package.json "type": "module"), which the CommonJS ncc bundle cannot + # require(). @actions/http-client 3.x is still CommonJS, so it is not + # capped. Stay on 2.x for the ESM packages until the action migrates to ESM. - dependency-name: "@actions/io" versions: [">=3"] + - dependency-name: "@actions/exec" + versions: [">=3"] + - dependency-name: "@actions/core" + versions: [">=3"] - package-ecosystem: "github-actions" directory: "/"