diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 27a83c10..d3a965c2 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 5c430f70..eb4ef84d 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-integration-tests.yml b/.github/workflows/azure-login-integration-tests.yml index 120b5a71..a746e74f 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 b54aaa1a..37c39fae 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: | @@ -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: | @@ -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: | @@ -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,37 +332,4 @@ jobs: uses: actions/github-script@v7 with: script: | - core.setFailed('Last action should fail but not. Please check it.') - - VMTest: - strategy: - matrix: - os: [self_linux, self_windows] - runs-on: ${{ matrix.os }} - environment: Automation test - - steps: - - name: 'Checking out repo code' - uses: actions/checkout@v4 - - - name: Set Node.js 20.x for GitHub Action - uses: actions/setup-node@v4 - with: - node-version: 20.x - - - name: 'Validate build' - run: | - npm install - npm run build - - - name: Login with system-assigned managed identity without auth-type - id: login_14 - continue-on-error: true - uses: ./ - - - 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.') + 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 a9287510..340d3b82 100644 --- a/.github/workflows/azure-login-positive.yml +++ b/.github/workflows/azure-login-positive.yml @@ -12,18 +12,18 @@ jobs: BasicTest: strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest, self_linux, self_windows] + os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} 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: 'Validate build' run: | @@ -47,13 +47,11 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - $checkResult = $checkResult -and ((Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG') - $checkResult = $checkResult -and ((Get-AzVM).Count -gt 0) if(-not $checkResult){ throw "Not all checks passed!" } @@ -71,7 +69,7 @@ jobs: az account show --output none - name: Run Azure PowerShell again - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -94,13 +92,11 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - $checkResult = $checkResult -and ((Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG') - $checkResult = $checkResult -and ((Get-AzVM).Count -gt 0) if(-not $checkResult){ throw "Not all checks passed!" } @@ -114,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: | @@ -164,13 +160,11 @@ jobs: az vm list --output none - name: Run Azure PowerShell - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - $checkResult = $checkResult -and ((Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG') - $checkResult = $checkResult -and ((Get-AzVM).Count -gt 0) if(-not $checkResult){ throw "Not all checks passed!" } @@ -189,7 +183,7 @@ jobs: az account show --output none - name: Run Azure PowerShell again - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -215,7 +209,7 @@ jobs: } - name: Run Azure PowerShell - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -236,7 +230,7 @@ jobs: az account show --output none - name: Run Azure PowerShell - uses: azure/powershell@v1 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -245,118 +239,78 @@ jobs: throw "Not all checks passed!" } - VMTest: - strategy: - matrix: - os: [self_linux, self_windows] - runs-on: ${{ matrix.os }} + InDockerTest: + runs-on: ubuntu-latest + container: ubuntu:24.04 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: | + 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 system-assigned managed identity, no subscription-id + - name: Login with individual parameters uses: ./ with: - auth-type: IDENTITY - allow-no-subscriptions: true + 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 + + - name: Run Azure Cli again run: | - az account show --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v1 + az group list --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 system-assigned managed identity, with subscription id - uses: ./ - with: - auth-type: IDENTITY - subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }} - 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@v1 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - $checkResult = $checkResult -and ((Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG') - $checkResult = $checkResult -and ((Get-AzVM).Count -gt 0) - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with tenant-level user-assigned managed identity with allow-no-subscriptions - uses: ./ - with: - client-id: ${{ secrets.UMI2_CLIENT_ID }} - allow-no-subscriptions: true - auth-type: IDENTITY - enable-AzPSSession: true - - - name: Run Azure Cli - run: | - az account show --output none - - - name: Run Azure PowerShell - uses: azure/powershell@v1 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - if(-not $checkResult){ - throw "Not all checks passed!" - } - - - name: Login with user-assigned managed identity, subscription-id - uses: ./ - with: - client-id: ${{ secrets.UMI1_CLIENT_ID }} - subscription-id: ${{ secrets.UMI1_SUBSCRIPTION_ID }} - auth-type: IDENTITY - 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@v1 - with: - azPSVersion: "latest" - inlineScript: | - $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' - $checkResult = $checkResult -and ((Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG') - $checkResult = $checkResult -and ((Get-AzVM).Count -gt 0) - if(-not $checkResult){ - throw "Not all checks passed!" - } - + $checkResult = Get-AzResourceGroup diff --git a/.github/workflows/azure-login-pr-check.yml b/.github/workflows/azure-login-pr-check.yml index 279cf3cd..b0cf457b 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 45da7c8a..f128428c 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 ca4c475e..0ccb3a2f 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 76c8789e..5c13f167 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/README.md b/README.md index beed96b7..a031ca76 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - [Login to Azure US Government cloud](#login-to-azure-us-government-cloud) - [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) - [Security hardening](#security-hardening) - [Azure CLI dependency](#azure-cli-dependency) - [Reference](#reference) @@ -56,7 +57,7 @@ Azure Login Action supports different ways of authentication with Azure. |tenant-id|false|UUID||the login tenant id| |creds|false|string||a json string for login with an Azure service principal| |enable-AzPSSession|false|boolean|false|if Azure PowerShell login is enabled| -|environment|false|string|azurecloud|the Azure Cloud environment| +|environment|false|string|azurecloud|the Azure Cloud environment. For cloud environments other than the public cloud, the `audience` will also need to be updated.| |allow-no-subscriptions|false|boolean|false|if login without subscription is allowed| |audience|false|string|api://AzureADTokenExchange|the audience to get the JWT ID token from GitHub OIDC provider| |auth-type|false|string|SERVICE_PRINCIPAL|the auth type| @@ -126,6 +127,8 @@ By default, Azure Login Action connects to the Azure Public Cloud (`AzureCloud`) To login to one of the Azure Government clouds or Azure Stack, set `environment` to one of the supported values `AzureUSGovernment` or `AzureChinaCloud` or `AzureGermanCloud` or `AzureStack`. +The default [`audience`](#audience) for each of these clouds is different and will also need to be set if using anything other than the public environment. + Refer to [Login to Azure US Government cloud](#login-to-azure-us-government-cloud) for its usage. ### `allow-no-subscriptions` @@ -153,7 +156,7 @@ Refer to [Login With System-assigned Managed Identity](#login-with-system-assign > - Ensure the CLI version is 2.30 or above to support login with OIDC. > - By default, Azure access tokens issued during OIDC based login could have limited validity. Azure access token issued by Service Principal is expected to have an expiration of 1 hour by default. And with Managed Identities, it would be 24 hours. This expiration time is further configurable in Azure. Refer to [access-token lifetime](https://learn.microsoft.com/azure/active-directory/develop/access-tokens#access-token-lifetime) for more details. -Before you use Azure Login Action with OIDC, you need to configure a federated identity credential on an service principal or a managed identity. +Before you use Azure Login Action with OIDC, you need to configure a federated identity credential on a service principal or a managed identity. - Prepare a service principal for Login with OIDC - [Create a service principal and assign a role to it](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal) @@ -190,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 }} @@ -220,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 }} @@ -235,7 +238,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -282,7 +285,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} @@ -309,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 @@ -322,7 +325,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -332,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 }}"}' ``` @@ -376,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 }} @@ -393,7 +396,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -442,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 }} @@ -460,7 +463,7 @@ jobs: az account show - name: Azure PowerShell script - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | @@ -482,7 +485,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} environment: 'AzureUSGovernment' @@ -504,7 +507,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: azure/login@v2 + - uses: azure/login@v3 with: creds: ${{ secrets.AZURE_CREDENTIALS }} environment: 'AzureStack' @@ -531,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 }} @@ -546,13 +549,123 @@ jobs: az account show - name: Run Azure PowerShell - uses: azure/powershell@v2 + uses: azure/powershell@v3 with: azPSVersion: "latest" inlineScript: | Get-AzContext ``` +### Enable/Disable the cleanup steps + +In Azure Login Action, "cleanup" means cleaning up the login context. For security reasons, we recommend users run cleanup every time. But in some scenarios, users need flexible control over cleanup. + +Referring to [`runs` for JavaScript actions](https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions), there are 3 steps in an action: `pre:`, `main:` and `post:`. Azure Login Action only implement 2 steps: `main:` and `post:`. + +There are 2 "cleanup" steps in Azure Login Action: + +- cleanup in `main:` + - It's **disabled** by default. + - Users can enable it by setting an env variable `AZURE_LOGIN_PRE_CLEANUP` to `true`. +- cleanup in `post:` + - It's **enabled** by default. + - Users can disable it by setting an env variable `AZURE_LOGIN_POST_CLEANUP` to `false`. + +Azure Login Action use env variables to enable or disable cleanup steps. In GitHub Actions, there are three valid scopes for env variables. + +- [env](https://docs.github.com/actions/writing-workflows/workflow-syntax-for-github-actions#env) + - valid for all jobs in this workflow. +- [jobs..env](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idenv) + - valid for all the steps in the job. +- [jobs..steps[*].env](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsenv) + - only valid for the step in a job. + +We set `jobs..steps[*].env` for example. Users can set `env` or `jobs..env` for a wider scope. + +```yaml +# File: .github/workflows/workflow.yml + +on: [push] + +name: Cleanup examples for Multiple Azure Login + +jobs: + + deploy: + runs-on: ubuntu-latest + steps: + + # enable cleanup for the 1st Azure Login + - name: Azure Login + uses: azure/login@v3 + env: + AZURE_LOGIN_PRE_CLEANUP: true + AZURE_LOGIN_POST_CLEANUP: true + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + # run some actions + + # disable cleanup for all other Azure Login + - name: Azure Login 2 + uses: azure/login@v3 + env: + AZURE_LOGIN_PRE_CLEANUP: false + AZURE_LOGIN_POST_CLEANUP: false + with: + client-id: ${{ secrets.AZURE_CLIENT_ID_2 }} + tenant-id: ${{ secrets.AZURE_TENANT_ID_2 }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_2 }} + enable-AzPSSession: true + + # run other actions + + # disable cleanup for all other Azure Login + - name: Azure Login 3 + uses: azure/login@v3 + env: + AZURE_LOGIN_PRE_CLEANUP: false + AZURE_LOGIN_POST_CLEANUP: false + with: + client-id: ${{ secrets.AZURE_CLIENT_ID_3 }} + tenant-id: ${{ secrets.AZURE_TENANT_ID_3 }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_3 }} + enable-AzPSSession: true + + # run other actions +``` + +```yaml +# File: .github/workflows/workflow.yml + +on: [push] + +name: Disable cleanup for GitHub Hosted Runners + +jobs: + + deploy: + runs-on: [ubuntu-latest, self-hosted] + steps: + + - name: Azure Login + uses: azure/login@v3 + env: + AZURE_LOGIN_PRE_CLEANUP: ${{ startsWith(runner.name, 'GitHub Actions') }} + AZURE_LOGIN_POST_CLEANUP: ${{ startsWith(runner.name, 'GitHub Actions') }} + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + # run some actions + +``` + ## Security hardening > [!WARNING] @@ -566,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 @@ -592,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. diff --git a/__tests__/LoginConfig.test.ts b/__tests__/LoginConfig.test.ts index a4933903..ee5d67b2 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/__tests__/PowerShell/AzPSScriptBuilder.test.ts b/__tests__/PowerShell/AzPSScriptBuilder.test.ts index f835dd89..862d7a0f 100644 --- a/__tests__/PowerShell/AzPSScriptBuilder.test.ts +++ b/__tests__/PowerShell/AzPSScriptBuilder.test.ts @@ -40,7 +40,7 @@ describe("Getting AzLogin PS script", () => { 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 | out-null;")).toBeTruthy(); + 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'); }); }); @@ -61,7 +61,7 @@ describe("Getting AzLogin PS script", () => { 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 | out-null;")).toBeTruthy(); + 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'); }); }); @@ -82,7 +82,7 @@ describe("Getting AzLogin PS script", () => { 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 | out-null;")).toBeTruthy(); + 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'); }); }); @@ -100,7 +100,7 @@ describe("Getting AzLogin PS script", () => { 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' | out-null;")).toBeTruthy(); + 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'); }); }); @@ -115,7 +115,7 @@ describe("Getting AzLogin PS script", () => { let loginConfig = new LoginConfig(); loginConfig.initialize(); return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' | out-null;")).toBeTruthy(); + expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' -InformationAction Ignore | out-null;")).toBeTruthy(); expect(loginMethod).toBe('system-assigned managed identity'); }); }); @@ -130,7 +130,7 @@ describe("Getting AzLogin PS script", () => { let loginConfig = new LoginConfig(); loginConfig.initialize(); return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' | out-null;")).toBeTruthy(); + expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -InformationAction Ignore | out-null;")).toBeTruthy(); expect(loginMethod).toBe('system-assigned managed identity'); }); }); @@ -145,7 +145,7 @@ describe("Getting AzLogin PS script", () => { let loginConfig = new LoginConfig(); loginConfig.initialize(); return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { - expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' | out-null;")).toBeTruthy(); + expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' -InformationAction Ignore | out-null;")).toBeTruthy(); expect(loginMethod).toBe('user-assigned managed identity'); }); }); diff --git a/action.yml b/action.yml index af6c2739..bf52a453 100644 --- a/action.yml +++ b/action.yml @@ -38,7 +38,7 @@ branding: icon: 'login.svg' color: 'blue' runs: - using: 'node20' - pre: 'lib/cleanup/index.js' + 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 0e67af0a..a0c62092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "login", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "login", - "version": "2.0.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", + "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/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.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", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { - "version": "7.23.9", + "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": { + "@babel/types": "^7.29.0" + }, "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", @@ -1278,11 +1173,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" @@ -1466,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": { @@ -1638,7 +1537,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": { @@ -1664,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, @@ -1746,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, @@ -1839,6 +1747,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": { @@ -2476,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": { @@ -2551,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": { @@ -2603,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": { @@ -2623,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" @@ -2799,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" }, @@ -3115,16 +3037,10 @@ "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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3229,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 aba816b1..c1bb0928 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "login", - "version": "2.0.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", diff --git a/src/Cli/AzureCliLogin.ts b/src/Cli/AzureCliLogin.ts index ec557fe6..aa3a95ec 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; @@ -28,10 +29,15 @@ export class AzureCliLogin { } }; - await this.executeAzCliCommand(["--version"], true, execOptions); + await this.executeAzCliCommand(["version"], true, execOptions); core.debug(`Azure CLI version used:\n${output}`); - - this.setAzurestackEnvIfNecessary(); + 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); core.info(`Done setting cloud: "${this.loginConfig.environment}"`); @@ -59,7 +65,7 @@ export class AzureCliLogin { } } - async setAzurestackEnvIfNecessary() { + async registerAzurestackEnvIfNecessary() { if (this.loginConfig.environment != "azurestack") { return; } @@ -85,7 +91,7 @@ export class AzureCliLogin { let suffixKeyvault = ".vault" + baseUri.substring(baseUri.indexOf('.')); // keyvault suffix starts with . let suffixStorage = baseUri.substring(baseUri.indexOf('.') + 1); // storage suffix starts without . let profileVersion = "2019-03-01-hybrid"; - await this.executeAzCliCommand(["cloud", "register", "-n", this.loginConfig.environment, "--endpoint-resource-manager", `"${this.loginConfig.resourceManagerEndpointUrl}"`, "--suffix-keyvault-dns", `"${suffixKeyvault}"`, "--suffix-storage-endpoint", `"${suffixStorage}"`, "--profile", `"${profileVersion}"`], false); + await this.executeAzCliCommand(["cloud", "register", "-n", this.loginConfig.environment, "--endpoint-resource-manager", this.loginConfig.resourceManagerEndpointUrl, "--suffix-keyvault-dns", suffixKeyvault, "--suffix-storage-endpoint", suffixStorage, "--profile", profileVersion], false); } catch (error) { core.error(`Error while trying to register cloud "${this.loginConfig.environment}"`); @@ -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'); } diff --git a/src/PowerShell/AzPSScriptBuilder.ts b/src/PowerShell/AzPSScriptBuilder.ts index 28e8a95f..5cd34580 100644 --- a/src/PowerShell/AzPSScriptBuilder.ts +++ b/src/PowerShell/AzPSScriptBuilder.ts @@ -104,7 +104,7 @@ export default class AzPSScriptBuilder { if(subscriptionId){ loginCmdlet += `-Subscription '${subscriptionId}' `; } - loginCmdlet += `${cmdletSuffix} | out-null;`; + loginCmdlet += `${cmdletSuffix} -InformationAction Ignore | out-null;`; return loginCmdlet; } } diff --git a/src/common/LoginConfig.ts b/src/common/LoginConfig.ts index d3a3e1c7..b9939c58 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() { @@ -99,7 +104,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'."); } } @@ -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]]; +} diff --git a/src/common/Utils.ts b/src/common/Utils.ts index b8a089d2..8e9186d3 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}@v1_${usrAgentRepo}`; - process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`; + 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}`; } export async function cleanupAzCLIAccounts(): Promise { diff --git a/src/main.ts b/src/main.ts index b3be983a..add837a9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ import * as core from '@actions/core'; -import { setUserAgent } from './common/Utils'; +import { cleanupAzCLIAccounts, cleanupAzPSAccounts, setUserAgent } from './common/Utils'; import { AzPSLogin } from './PowerShell/AzPSLogin'; import { LoginConfig } from './common/LoginConfig'; import { AzureCliLogin } from './Cli/AzureCliLogin'; @@ -7,6 +7,13 @@ import { AzureCliLogin } from './Cli/AzureCliLogin'; async function main() { try { setUserAgent(); + const preCleanup: string = process.env.AZURE_LOGIN_PRE_CLEANUP; + if ('true' == preCleanup) { + await cleanupAzCLIAccounts(); + if (core.getInput('enable-AzPSSession').toLowerCase() === "true") { + await cleanupAzPSAccounts(); + } + } // prepare the login configuration var loginConfig = new LoginConfig();