Compare commits

..

26 Commits

Author SHA1 Message Date
MoChilia
a457da9ea1 prepare release v2.3.0 2025-04-01 10:44:04 +08:00
Shiying Chen
bbcc074a23 Use --client-id for user-assigned managed identity authentication in Azure CLI v2.69.0 or later. (#514) 2025-03-04 09:39:48 +08:00
Shiying Chen
877e2b442c Log more claims for OIDC login (#520) 2025-03-03 15:30:34 +08:00
Shiying Chen
888619bccc mention allow-no-subscriptions in missing subscriptionId error (#512) 2025-02-13 15:45:28 +08:00
Yan Xu
3fcf81c86c 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] <support@github.com>

* remove libicu and update powershell version

* apt install libicu72

* change installation url

* fix typo

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: MoChilia <chenshiyingcn@163.com>
Co-authored-by: Shiying Chen <shiyingchen@microsoft.com>
2025-02-13 13:22:44 +08:00
Shiying Chen
6047d58862 replace the invalid link for github action doc (#510) 2025-02-13 11:20:07 +08:00
Yan Xu
a514566bcd update version to 2.2.0 (#487) 2024-09-18 14:29:08 +08:00
Yan Xu
aa77932d98 Update Readme to support enable/disable cleanup steps (#485)
* update README.md to add cleanup examples

* update README.md

* update README.md

* update README.md

* fix markdown lint errors

---------

Co-authored-by: Shiying Chen <shiyingchen@microsoft.com>
2024-09-18 14:19:52 +08:00
Yan Xu
e319965b63 Add test for docker container (#486)
* add docker test

* update docker test
2024-09-18 14:04:44 +08:00
Yan Xu
73ceb51d05 move pre cleanup to main and add pre-if and post-if (#484) 2024-09-14 13:28:11 +08:00
Dennis Hoelgaard Bal
ffd504ae12 Fix typo (#483) 2024-09-10 10:48:05 +08:00
Shiying Chen
3cf32af09d enhance telemetry (#478) 2024-08-01 11:08:06 +08:00
Shiying Chen
e1a0ade0c1 Fix #459: Errors when registering cloud profile for AzureStack (#466) 2024-06-20 14:41:16 +08:00
Shiying Chen
8fb68f4ade Remove tests for self-hosted runner (#465)
* disable vm

* update

* update
2024-06-20 13:42:11 +08:00
Jesse Antoszyk
151fd0098c Update documentation for setting audience when environment is set (#455)
* Add details about setting "audience" parameter when "environment" is not public cloud

* Remove extra added space
2024-06-04 10:47:24 +08:00
Shiying Chen
5b1f5e2d92 Replace az --version with az version (#450) 2024-05-27 14:55:09 +08:00
Yan Xu
cb503d892a Disable information output in Connect-AzAccount (#448)
* disable information output in Connect-AzAccount

* fix test
2024-05-24 11:08:40 +08:00
Yeming Liu
59ce201ac2 Update CODE_OF_CONDUCT.md (#446) 2024-05-21 11:03:42 +08:00
Jiashuo Li
cf8f85dbab Update azure/CLI@v1 to azure/cli@v2 and azure/powershell@v1 to azure/powershell@v2 (#438)
* patch

* fix indentation
2024-04-26 08:58:16 +08:00
Shiying Chen
19d77c4f9b Change the trigger for pr check workflow (#435)
* change trigger

* remove env
2024-04-17 09:55:46 +08:00
Shiying Chen
e9468bad0a use ncc to compile (#428) 2024-03-27 17:42:11 +08:00
Shiying Chen
81e1d9f360 Update README.md for azure/login@v2 (#423)
* update readme

* fix lint error

* remove 'en' from link
2024-03-04 14:01:45 +08:00
Shiying Chen
c847559275 Bump dependencies versions (#419)
* bump dependencies version

* abandon jsonpath

* remove jspath
2024-02-22 16:10:10 +08:00
Justin Chao
332d569187 Update Action to use Node.js v20 (#411)
* Update Action to use Node.js v20

Node.js 16 actions are deprecated.
Updating action to use Node.js 20.

Link: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/

* Updating all Github workflows to use Node 20.x

Updating all Github workflows used in CI checks to use Node 20.x

---------

Co-authored-by: Justin Chao <justin.chao@optum.com>
Co-authored-by: Shiying Chen <shiyingchen@microsoft.com>
2024-02-20 13:44:57 +08:00
Shiying Chen
dcaef1266d Temp change ci test for a bug in azps (#416) 2024-02-20 13:33:37 +08:00
Shiying Chen
3d449ed579 Fix CodeQL error: Resource not accessible by integration & Update CodeQL version (#417) 2024-02-20 13:27:37 +08:00
19 changed files with 10415 additions and 4066 deletions

View File

@@ -333,36 +333,3 @@ jobs:
with: with:
script: | script: |
core.setFailed('Last action should fail but not. Please check it.') 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.')

View File

@@ -12,7 +12,7 @@ jobs:
BasicTest: BasicTest:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-latest, self_linux, self_windows] os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
environment: Automation test environment: Automation test
@@ -47,13 +47,11 @@ jobs:
az vm list --output none az vm list --output none
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' $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){ if(-not $checkResult){
throw "Not all checks passed!" throw "Not all checks passed!"
} }
@@ -71,7 +69,7 @@ jobs:
az account show --output none az account show --output none
- name: Run Azure PowerShell again - name: Run Azure PowerShell again
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -94,13 +92,11 @@ jobs:
az vm list --output none az vm list --output none
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' $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){ if(-not $checkResult){
throw "Not all checks passed!" throw "Not all checks passed!"
} }
@@ -164,13 +160,11 @@ jobs:
az vm list --output none az vm list --output none
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' $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){ if(-not $checkResult){
throw "Not all checks passed!" throw "Not all checks passed!"
} }
@@ -189,7 +183,7 @@ jobs:
az account show --output none az account show --output none
- name: Run Azure PowerShell again - name: Run Azure PowerShell again
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -209,17 +203,17 @@ jobs:
- name: Run Azure Cli - name: Run Azure Cli
shell: pwsh shell: pwsh
run: | run: |
$checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 2 $checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 3
if(-not $checkResult){ if(-not $checkResult){
throw "Not all checks passed!" throw "Not all checks passed!"
} }
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
$checkResult = (Get-AzContext -ListAvailable).Count -eq 2 $checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
if(-not $checkResult){ if(-not $checkResult){
throw "Not all checks passed!" throw "Not all checks passed!"
} }
@@ -236,7 +230,7 @@ jobs:
az account show --output none az account show --output none
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -245,13 +239,10 @@ jobs:
throw "Not all checks passed!" throw "Not all checks passed!"
} }
VMTest: InDockerTest:
strategy: runs-on: ubuntu-latest
matrix: container: ubuntu:24.04
os: [self_linux, self_windows]
runs-on: ${{ matrix.os }}
environment: Automation test environment: Automation test
steps: steps:
- name: 'Checking out repo code' - name: 'Checking out repo code'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -261,102 +252,64 @@ jobs:
with: with:
node-version: 20.x node-version: 20.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
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
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' - name: 'Validate build'
run: | run: |
npm install npm install
npm run build npm run build
- name: Login with system-assigned managed identity, no subscription-id - name: 'Run L0 tests'
run: |
npm run test
- name: Login with individual parameters
uses: ./ uses: ./
with: with:
auth-type: IDENTITY client-id: ${{ secrets.SP1_CLIENT_ID }}
allow-no-subscriptions: true tenant-id: ${{ secrets.SP1_TENANT_ID }}
subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }}
enable-AzPSSession: true enable-AzPSSession: true
- name: Run Azure Cli - name: Run Azure Cli again
run: | run: |
az account show --output none az group list --output none
- name: Run Azure PowerShell - name: Run Azure PowerShell again
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud' $checkResult = Get-AzResourceGroup
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!"
}

View File

@@ -1,13 +1,12 @@
name: pr-check name: pr-check
on: on:
pull_request_target: pull_request:
branches: branches:
- master - master
- 'releases/*' - 'releases/*'
jobs: jobs:
az-login-test: az-login-test:
environment: Automation test
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout from PR branch - name: Checkout from PR branch
@@ -28,44 +27,5 @@ jobs:
- name: Build GitHub Action - name: Build GitHub Action
run: npm run build run: npm run build
- name: 'Az CLI login with subscription' - name: Run mock test
uses: ./ run: npm run test
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- run: |
az account show --output none
az vm list --output none
- name: 'Az CLI login without subscription'
uses: ./
with:
creds: ${{ secrets.AZURE_CREDENTIALS_NO_SUB }}
allow-no-subscriptions: true
- run: |
az account show --output none
# az vm list --output none
- name: 'Azure PowerShell login with subscription'
uses: ./
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true
- uses: azure/powershell@v1
with:
inlineScript: "(Get-AzContext).Environment.Name"
azPSVersion: "latest"
# - name: 'Azure PowerShell login without subscription'
# uses: ./
# with:
# creds: ${{secrets.AZURE_CREDENTIALS_NO_SUB}}
# enable-AzPSSession: true
# allow-no-subscriptions: true
# - uses: azure/powershell@v1
# with:
# inlineScript: "Get-AzContext"
# azPSVersion: "latest"

View File

@@ -6,6 +6,11 @@ on:
schedule: schedule:
- cron: '0 19 * * 0' - cron: '0 19 * * 0'
permissions:
actions: read
contents: read
security-events: write
jobs: jobs:
CodeQL-Build: CodeQL-Build:
@@ -18,14 +23,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: javascript languages: javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -39,4 +44,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3

View File

@@ -7,3 +7,4 @@ Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support)

192
README.md
View File

@@ -19,7 +19,8 @@
- [Login to Azure US Government cloud](#login-to-azure-us-government-cloud) - [Login to Azure US Government cloud](#login-to-azure-us-government-cloud)
- [Login to Azure Stack Hub](#login-to-azure-stack-hub) - [Login to Azure Stack Hub](#login-to-azure-stack-hub)
- [Login without subscription](#login-without-subscription) - [Login without subscription](#login-without-subscription)
- [Az logout and security hardening](#az-logout-and-security-hardening) - [Enable/Disable the cleanup steps](#enabledisable-the-cleanup-steps)
- [Security hardening](#security-hardening)
- [Azure CLI dependency](#azure-cli-dependency) - [Azure CLI dependency](#azure-cli-dependency)
- [Reference](#reference) - [Reference](#reference)
- [GitHub Action](#github-action) - [GitHub Action](#github-action)
@@ -56,7 +57,7 @@ Azure Login Action supports different ways of authentication with Azure.
|tenant-id|false|UUID||the login tenant id| |tenant-id|false|UUID||the login tenant id|
|creds|false|string||a json string for login with an Azure service principal| |creds|false|string||a json string for login with an Azure service principal|
|enable-AzPSSession|false|boolean|false|if Azure PowerShell login is enabled| |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| |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| |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| |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`. 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. Refer to [Login to Azure US Government cloud](#login-to-azure-us-government-cloud) for its usage.
### `allow-no-subscriptions` ### `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. > - 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. > - 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 - 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) - [Create a service principal and assign a role to it](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal)
@@ -183,21 +186,21 @@ name: Run Azure Login with OIDC
on: [push] on: [push]
permissions: permissions:
id-token: write id-token: write
contents: read contents: read
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Azure login - name: Azure login
uses: azure/login@v1 uses: azure/login@v2
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
@@ -213,14 +216,14 @@ name: Run Azure Login with OIDC
on: [push] on: [push]
permissions: permissions:
id-token: write id-token: write
contents: read contents: read
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Azure login - name: Azure login
uses: azure/login@v1 uses: azure/login@v2
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -228,14 +231,14 @@ jobs:
enable-AzPSSession: true enable-AzPSSession: true
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
- name: Azure PowerShell script - name: Azure PowerShell script
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -282,17 +285,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: azure/login@v1 - uses: azure/login@v2
with: with:
creds: ${{ secrets.AZURE_CREDENTIALS }} creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
``` ```
- **The workflow sample to run both Azure CLI and Azure PowerShell** - **The workflow sample to run both Azure CLI and Azure PowerShell**
@@ -310,20 +312,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: azure/login@v1 - uses: azure/login@v2
with: with:
creds: ${{ secrets.AZURE_CREDENTIALS }} creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true enable-AzPSSession: true
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
- name: Azure PowerShell script - name: Azure PowerShell script
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -333,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. 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 ```yaml
- uses: Azure/login@v1 - uses: azure/login@v2
with: with:
creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}'
``` ```
@@ -377,7 +379,7 @@ jobs:
runs-on: self-hosted runs-on: self-hosted
steps: steps:
- name: Azure login - name: Azure login
uses: azure/login@v1 uses: azure/login@v2
with: with:
auth-type: IDENTITY auth-type: IDENTITY
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -387,14 +389,14 @@ jobs:
# Azure CLI Action only supports linux self-hosted runners for now. # Azure CLI Action only supports linux self-hosted runners for now.
# If you want to execute the Azure CLI script on a windows self-hosted runner, you can execute it directly in `run`. # If you want to execute the Azure CLI script on a windows self-hosted runner, you can execute it directly in `run`.
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
- name: Azure PowerShell script - name: Azure PowerShell script
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -443,7 +445,7 @@ jobs:
runs-on: self-hosted runs-on: self-hosted
steps: steps:
- name: Azure login - name: Azure login
uses: azure/login@v1 uses: azure/login@v2
with: with:
auth-type: IDENTITY auth-type: IDENTITY
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
@@ -454,14 +456,14 @@ jobs:
# Azure CLI Action only supports linux self-hosted runners for now. # Azure CLI Action only supports linux self-hosted runners for now.
# If you want to execute the Azure CLI script on a windows self-hosted runner, you can execute it directly in `run`. # If you want to execute the Azure CLI script on a windows self-hosted runner, you can execute it directly in `run`.
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
- name: Azure PowerShell script - name: Azure PowerShell script
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
@@ -483,12 +485,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: azure/login@v1 - uses: azure/login@v2
with: with:
creds: ${{ secrets.AZURE_CREDENTIALS }} creds: ${{ secrets.AZURE_CREDENTIALS }}
environment: 'AzureUSGovernment' environment: 'AzureUSGovernment'
enable-AzPSSession: true enable-AzPSSession: true
``` ```
### Login to Azure Stack Hub ### Login to Azure Stack Hub
@@ -506,12 +507,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: azure/login@v1 - uses: azure/login@v2
with: with:
creds: ${{ secrets.AZURE_CREDENTIALS }} creds: ${{ secrets.AZURE_CREDENTIALS }}
environment: 'AzureStack' environment: 'AzureStack'
enable-AzPSSession: true enable-AzPSSession: true
``` ```
Refer to the [Azure Stack Hub Login Action Tutorial](https://learn.microsoft.com/azure-stack/user/ci-cd-github-action-login-cli) for more detailed instructions. Refer to the [Azure Stack Hub Login Action Tutorial](https://learn.microsoft.com/azure-stack/user/ci-cd-github-action-login-cli) for more detailed instructions.
@@ -534,7 +534,7 @@ jobs:
steps: steps:
- name: Azure Login - name: Azure Login
uses: azure/login@v1 uses: azure/login@v2
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -542,45 +542,135 @@ jobs:
enable-AzPSSession: true enable-AzPSSession: true
- name: Azure CLI script - name: Azure CLI script
uses: azure/CLI@v1 uses: azure/cli@v2
with: with:
azcliversion: latest azcliversion: latest
inlineScript: | inlineScript: |
az account show az account show
- name: Run Azure PowerShell - name: Run Azure PowerShell
uses: azure/powershell@v1 uses: azure/powershell@v2
with: with:
azPSVersion: "latest" azPSVersion: "latest"
inlineScript: | inlineScript: |
Get-AzContext Get-AzContext
``` ```
## Az logout and security hardening ### Enable/Disable the cleanup steps
This action doesn't implement ```az logout``` by default at the end of execution. However, there is no way to tamper with the credentials or account information because the GitHub-hosted runner is on a VM that will get re-imaged for every customer run, which deletes everything. But if the runner is self-hosted (not provided by GitHub), it is recommended to manually log out at the end of the workflow, as shown below. More details on security of the runners can be found [here](https://docs.github.com/actions/learn-github-actions/security-hardening-for-github-actions#hardening-for-self-hosted-runners). 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.
> [!WARNING] 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:`.
> When using self hosted runners it is possible to have multiple runners on a single VM. Currently if your runners share a single user on the VM each runner will share the same credentials. That means in detail that each runner is able to change the permissions of another run. As a workaround we propose to use one single VM user per runner. If you start the runner as a service, do not forget to add the [optional user argument](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service#installing-the-service)
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.<job_id>.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.<job_id>.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.<job_id>.steps[*].env` for example. Users can set `env` or `jobs.<job_id>.env` for a wider scope.
```yaml ```yaml
- name: Azure CLI script # File: .github/workflows/workflow.yml
uses: azure/CLI@v1
with:
inlineScript: |
az logout
az cache purge
az account clear
- name: Azure PowerShell script on: [push]
uses: azure/powershell@v1
with: name: Cleanup examples for Multiple Azure Login
azPSVersion: "latest"
inlineScript: | jobs:
Clear-AzContext -Scope Process
Clear-AzContext -Scope CurrentUser deploy:
runs-on: ubuntu-latest
steps:
# enable cleanup for the 1st Azure Login
- name: Azure Login
uses: azure/login@v2
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@v2
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@v2
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@v2
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]
> When using self hosted runners it is possible to have multiple runners on a single VM. Currently if your runners share a single user on the VM each runner will share the same credentials. That means in detail that each runner is able to change the permissions of another run. As a workaround we propose to use one single VM user per runner. If you start the runner as a service, do not forget to add the [optional user argument](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service#installing-the-service)
## Azure CLI dependency ## Azure CLI dependency
Internally in this action, we use azure CLI and execute `az login` with the credentials provided through secrets. In order to validate the new azure CLI releases for this action, [canary test workflow](.github/workflows/azure-login-canary.yml) is written which will execute the action on [azure CLI's edge build](https://github.com/Azure/azure-cli#edge-builds) which will fail incase of any breaking change is being introduced in the new upcoming release. The test results can be posted on a slack or teams channel using the corresponding integrations. Incase of a failure, the concern will be raised to [azure-cli](https://github.com/Azure/azure-cli) for taking a necessary action and also the latest CLI installation will be postponed in [Runner VMs](https://github.com/actions/virtual-environments) as well for hosted runner to prevent the workflows failing due to the new CLI changes. Internally in this action, we use azure CLI and execute `az login` with the credentials provided through secrets. In order to validate the new azure CLI releases for this action, [canary test workflow](.github/workflows/azure-login-canary.yml) is written which will execute the action on [azure CLI's edge build](https://github.com/Azure/azure-cli#edge-builds) which will fail incase of any breaking change is being introduced in the new upcoming release. The test results can be posted on a slack or teams channel using the corresponding integrations. Incase of a failure, the concern will be raised to [azure-cli](https://github.com/Azure/azure-cli) for taking a necessary action and also the latest CLI installation will be postponed in [Runner VMs](https://github.com/actions/virtual-environments) as well for hosted runner to prevent the workflows failing due to the new CLI changes.
@@ -589,7 +679,7 @@ Internally in this action, we use azure CLI and execute `az login` with the cred
### GitHub Action ### 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 ### GitHub Actions for deploying to Azure

View File

@@ -96,7 +96,7 @@ describe("LoginConfig Test", () => {
expect(loginConfig.servicePrincipalId).toBe("client-id"); expect(loginConfig.servicePrincipalId).toBe("client-id");
expect(loginConfig.servicePrincipalSecret).toBe("client-secret"); expect(loginConfig.servicePrincipalSecret).toBe("client-secret");
expect(loginConfig.tenantId).toBe("tenant-id"); expect(loginConfig.tenantId).toBe("tenant-id");
expect(loginConfig.subscriptionId).toBe(""); expect(loginConfig.subscriptionId).toBe(undefined);
}); });
test('initialize with creds', async () => { test('initialize with creds', async () => {
@@ -245,7 +245,7 @@ describe("LoginConfig Test", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
await loginConfig.initialize(); 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 () => { test('validate without subscriptionId and allowNoSubscriptionsLogin=true', async () => {

View File

@@ -40,7 +40,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('service principal with secret');
}); });
}); });
@@ -61,7 +61,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('service principal with secret');
}); });
}); });
@@ -82,7 +82,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('service principal with secret');
}); });
}); });
@@ -100,7 +100,7 @@ describe("Getting AzLogin PS script", () => {
loginConfig.initialize(); loginConfig.initialize();
jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";}); jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";});
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('OIDC');
}); });
}); });
@@ -115,7 +115,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('system-assigned managed identity');
}); });
}); });
@@ -130,7 +130,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('system-assigned managed identity');
}); });
}); });
@@ -145,7 +145,7 @@ describe("Getting AzLogin PS script", () => {
let loginConfig = new LoginConfig(); let loginConfig = new LoginConfig();
loginConfig.initialize(); loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => { 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'); expect(loginMethod).toBe('user-assigned managed identity');
}); });
}); });

View File

@@ -39,6 +39,6 @@ branding:
color: 'blue' color: 'blue'
runs: runs:
using: 'node20' using: 'node20'
pre: 'lib/cleanup.js' main: 'lib/main/index.js'
main: 'lib/main.js' post-if: (!env.AZURE_LOGIN_POST_CLEANUP || env.AZURE_LOGIN_POST_CLEANUP != 'false')
post: 'lib/cleanup.js' post: 'lib/cleanup/index.js'

4652
lib/cleanup/index.js Normal file

File diff suppressed because it is too large Load Diff

4957
lib/main/index.js Normal file

File diff suppressed because it is too large Load Diff

4238
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,20 @@
{ {
"name": "login", "name": "login",
"version": "1.0.0", "version": "2.2.0",
"description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure", "description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure",
"main": "lib/main.js", "main": "lib/main/index.js",
"scripts": { "scripts": {
"build": "tsc", "build:main": "ncc build src/main.ts -o lib/main",
"build:cleanup": "ncc build src/cleanup.ts -o lib/cleanup",
"build": "npm run build:main && npm run build:cleanup",
"test": "jest" "test": "jest"
}, },
"author": "Sumiran Aggarwal", "author": "Microsoft",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/jest": "^29.2.4", "@types/jest": "^29.2.4",
"@types/node": "^12.7.11", "@types/node": "^20.11.1",
"@vercel/ncc": "^0.38.1",
"jest": "^29.3.1", "jest": "^29.3.1",
"jest-circus": "^29.3.1", "jest-circus": "^29.3.1",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
@@ -21,7 +24,6 @@
"@actions/core": "1.9.1", "@actions/core": "1.9.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/io": "^1.0.1", "@actions/io": "^1.0.1",
"actions-secret-parser": "^1.0.4",
"package-lock": "^1.0.3" "package-lock": "^1.0.3"
} }
} }

View File

@@ -8,6 +8,7 @@ export class AzureCliLogin {
loginConfig: LoginConfig; loginConfig: LoginConfig;
azPath: string; azPath: string;
loginOptions: ExecOptions; loginOptions: ExecOptions;
azVersion: string;
constructor(loginConfig: LoginConfig) { constructor(loginConfig: LoginConfig) {
this.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}`); core.debug(`Azure CLI version used:\n${output}`);
try {
this.setAzurestackEnvIfNecessary(); 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); await this.executeAzCliCommand(["cloud", "set", "-n", this.loginConfig.environment], false);
core.info(`Done setting cloud: "${this.loginConfig.environment}"`); core.info(`Done setting cloud: "${this.loginConfig.environment}"`);
@@ -59,7 +65,7 @@ export class AzureCliLogin {
} }
} }
async setAzurestackEnvIfNecessary() { async registerAzurestackEnvIfNecessary() {
if (this.loginConfig.environment != "azurestack") { if (this.loginConfig.environment != "azurestack") {
return; return;
} }
@@ -85,7 +91,7 @@ export class AzureCliLogin {
let suffixKeyvault = ".vault" + baseUri.substring(baseUri.indexOf('.')); // keyvault suffix starts with . let suffixKeyvault = ".vault" + baseUri.substring(baseUri.indexOf('.')); // keyvault suffix starts with .
let suffixStorage = baseUri.substring(baseUri.indexOf('.') + 1); // storage suffix starts without . let suffixStorage = baseUri.substring(baseUri.indexOf('.') + 1); // storage suffix starts without .
let profileVersion = "2019-03-01-hybrid"; 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) { catch (error) {
core.error(`Error while trying to register cloud "${this.loginConfig.environment}"`); core.error(`Error while trying to register cloud "${this.loginConfig.environment}"`);
@@ -108,7 +114,20 @@ export class AzureCliLogin {
} }
async loginWithUserAssignedIdentity(args: string[]) { 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'); await this.callCliLogin(args, 'user-assigned managed identity');
} }

View File

@@ -104,7 +104,7 @@ export default class AzPSScriptBuilder {
if(subscriptionId){ if(subscriptionId){
loginCmdlet += `-Subscription '${subscriptionId}' `; loginCmdlet += `-Subscription '${subscriptionId}' `;
} }
loginCmdlet += `${cmdletSuffix} | out-null;`; loginCmdlet += `${cmdletSuffix} -InformationAction Ignore | out-null;`;
return loginCmdlet; return loginCmdlet;
} }
} }

View File

@@ -1,5 +1,4 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import { FormatType, SecretParser } from 'actions-secret-parser';
export class LoginConfig { export class LoginConfig {
static readonly AUTH_TYPE_SERVICE_PRINCIPAL = "SERVICE_PRINCIPAL"; static readonly AUTH_TYPE_SERVICE_PRINCIPAL = "SERVICE_PRINCIPAL";
@@ -49,10 +48,10 @@ export class LoginConfig {
private readParametersFromCreds() { private readParametersFromCreds() {
let creds = core.getInput('creds', { required: false }); let creds = core.getInput('creds', { required: false });
let secrets = creds ? new SecretParser(creds, FormatType.JSON) : null; if (!creds) {
if (!secrets) {
return; return;
} }
let secrets = JSON.parse(creds);
if(this.authType != LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){ if(this.authType != LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){
return; return;
@@ -64,11 +63,11 @@ export class LoginConfig {
} }
core.debug('Reading creds in JSON...'); core.debug('Reading creds in JSON...');
this.servicePrincipalId = this.servicePrincipalId ? this.servicePrincipalId : secrets.getSecret("$.clientId", false); this.servicePrincipalId = this.servicePrincipalId ? this.servicePrincipalId : secrets.clientId;
this.servicePrincipalSecret = secrets.getSecret("$.clientSecret", false); this.servicePrincipalSecret = secrets.clientSecret;
this.tenantId = this.tenantId ? this.tenantId : secrets.getSecret("$.tenantId", false); this.tenantId = this.tenantId ? this.tenantId : secrets.tenantId;
this.subscriptionId = this.subscriptionId ? this.subscriptionId : secrets.getSecret("$.subscriptionId", false); this.subscriptionId = this.subscriptionId ? this.subscriptionId : secrets.subscriptionId;
this.resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false); this.resourceManagerEndpointUrl = secrets.resourceManagerEndpointUrl;
if (!this.servicePrincipalId || !this.servicePrincipalSecret || !this.tenantId) { if (!this.servicePrincipalId || !this.servicePrincipalSecret || !this.tenantId) {
throw new Error("Not all parameters are provided in 'creds'. Double-check if all keys are defined in 'creds': 'clientId', 'clientSecret', 'tenantId'."); throw new Error("Not all parameters are provided in 'creds'. Double-check if all keys are defined in 'creds': 'clientId', 'clientSecret', 'tenantId'.");
} }
@@ -80,11 +79,16 @@ export class LoginConfig {
this.mask(this.federatedToken); this.mask(this.federatedToken);
} }
catch (error) { 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; throw error;
} }
let [issuer, subjectClaim] = await jwtParser(this.federatedToken); try {
core.info("Federated token details:\n issuer - " + issuer + "\n subject claim - " + subjectClaim); 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() { validate() {
@@ -100,7 +104,7 @@ export class LoginConfig {
} }
} }
if (!this.subscriptionId && !this.allowNoSubscriptionsLogin) { 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'.");
} }
} }
@@ -115,6 +119,20 @@ async function jwtParser(federatedToken: string) {
let tokenPayload = federatedToken.split('.')[1]; let tokenPayload = federatedToken.split('.')[1];
let bufferObj = Buffer.from(tokenPayload, "base64"); let bufferObj = Buffer.from(tokenPayload, "base64");
let decodedPayload = JSON.parse(bufferObj.toString("utf8")); 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]];
} }

View File

@@ -7,8 +7,8 @@ import { AzPSConstants, AzPSUtils } from '../PowerShell/AzPSUtils';
export function setUserAgent(): void { export function setUserAgent(): void {
let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex');
let actionName = 'AzureLogin'; 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.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}@v1_${usrAgentRepo}`; 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<void> { export async function cleanupAzCLIAccounts(): Promise<void> {

View File

@@ -1,5 +1,5 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import { setUserAgent } from './common/Utils'; import { cleanupAzCLIAccounts, cleanupAzPSAccounts, setUserAgent } from './common/Utils';
import { AzPSLogin } from './PowerShell/AzPSLogin'; import { AzPSLogin } from './PowerShell/AzPSLogin';
import { LoginConfig } from './common/LoginConfig'; import { LoginConfig } from './common/LoginConfig';
import { AzureCliLogin } from './Cli/AzureCliLogin'; import { AzureCliLogin } from './Cli/AzureCliLogin';
@@ -7,6 +7,13 @@ import { AzureCliLogin } from './Cli/AzureCliLogin';
async function main() { async function main() {
try { try {
setUserAgent(); 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 // prepare the login configuration
var loginConfig = new LoginConfig(); var loginConfig = new LoginConfig();