Compare commits

...

16 Commits

Author SHA1 Message Date
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
13 changed files with 262 additions and 175 deletions

View File

@@ -333,36 +333,3 @@ jobs:
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.')

View File

@@ -12,7 +12,7 @@ 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
@@ -47,13 +47,11 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1
uses: azure/powershell@v2
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@v2
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@v2
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!"
}
@@ -164,13 +160,11 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1
uses: azure/powershell@v2
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@v2
with:
azPSVersion: "latest"
inlineScript: |
@@ -209,13 +203,13 @@ jobs:
- name: Run Azure Cli
shell: pwsh
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){
throw "Not all checks passed!"
}
- name: Run Azure PowerShell
uses: azure/powershell@v1
uses: azure/powershell@v2
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@v2
with:
azPSVersion: "latest"
inlineScript: |
@@ -245,13 +239,10 @@ 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
@@ -260,103 +251,65 @@ jobs:
uses: actions/setup-node@v4
with:
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'
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@v2
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

119
README.md
View File

@@ -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)
@@ -553,6 +556,116 @@ jobs:
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.<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
# 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@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]
@@ -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

View File

@@ -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 () => {

View File

@@ -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');
});
});

View File

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

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "login",
"version": "2.0.0",
"version": "2.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "login",
"version": "2.0.0",
"version": "2.2.0",
"license": "MIT",
"dependencies": {
"@actions/core": "1.9.1",
@@ -1278,11 +1278,13 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -1638,7 +1640,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1839,6 +1843,8 @@
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3125,6 +3131,8 @@
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "login",
"version": "2.0.0",
"version": "2.2.0",
"description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure",
"main": "lib/main/index.js",
"scripts": {

View File

@@ -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');
}

View File

@@ -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;
}
}

View File

@@ -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]];
}

View File

@@ -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<void> {

View File

@@ -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();