mirror of
https://github.com/azure/login.git
synced 2026-03-15 09:20:56 -04:00
Compare commits
5 Commits
dependabot
...
jiasli/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97334770a | ||
|
|
da2e80cd21 | ||
|
|
b54e6e2f76 | ||
|
|
2da0d5b800 | ||
|
|
b15e3257a9 |
14
.github/workflows/azure-login-canary.yml
vendored
14
.github/workflows/azure-login-canary.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
az --version
|
||||
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 'Az CLI login with subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: 'Az CLI login without subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
allow-no-subscriptions: true
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: 'Az CLI login with subscription OIDC'
|
||||
uses: azure/login@v1
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }}
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: 'Az CLI login without subscription OIDC'
|
||||
uses: azure/login@v1
|
||||
@@ -67,7 +67,9 @@ jobs:
|
||||
allow-no-subscriptions: true
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
|
||||
|
||||
slack-post-result:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -85,7 +87,7 @@ jobs:
|
||||
RUN_URL="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
|
||||
REPORT="${REPORT}\r\n"
|
||||
if [ ${{needs.az-login-test.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test>"; fi
|
||||
echo "report=$REPORT" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=report::$REPORT"
|
||||
- name: Post to slack
|
||||
shell: bash
|
||||
run: curl -X POST -H 'Content-type:application/json' --data '{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"${{steps.slack_report.outputs.report}}"}}]}' https://hooks.slack.com/services/${{SECRETS.SLACK_CHANNEL_SECRET}}
|
||||
|
||||
@@ -19,8 +19,8 @@ jobs:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
# az webapp list
|
||||
|
||||
- name: 'Az CLI login without subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
allow-no-subscriptions: true
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: 'Azure PowerShell login with subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: "(Get-AzContext).Environment.Name"
|
||||
inlineScript: "Get-AzContext"
|
||||
azPSVersion: "latest"
|
||||
|
||||
- name: 'Azure PowerShell login without subscription'
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
|
||||
- uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: "(Get-AzContext).Environment.Name"
|
||||
inlineScript: "Get-AzContext"
|
||||
azPSVersion: "latest"
|
||||
|
||||
az-login-test-oidc:
|
||||
@@ -66,8 +66,8 @@ jobs:
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }}
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
# az webapp list
|
||||
|
||||
- name: 'Az CLI login without subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
allow-no-subscriptions: true
|
||||
|
||||
- run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: 'Azure PowerShell login with subscription'
|
||||
uses: azure/login@v1
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
|
||||
- uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: "(Get-AzContext).Environment.Name"
|
||||
inlineScript: "Get-AzContext"
|
||||
azPSVersion: "latest"
|
||||
|
||||
- name: 'Azure PowerShell login without subscription'
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
- uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: "(Get-AzContext).Environment.Name"
|
||||
inlineScript: "Get-AzContext"
|
||||
azPSVersion: "latest"
|
||||
|
||||
slack-post-result:
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
REPORT="${REPORT}\r\n"
|
||||
if [ ${{needs.az-login-test-non-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-non-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-non-oidc>"; fi
|
||||
if [ ${{needs.az-login-test-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-oidc>"; fi
|
||||
echo "report=$REPORT" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=report::$REPORT"
|
||||
|
||||
- name: Post to slack
|
||||
shell: bash
|
||||
|
||||
278
.github/workflows/azure-login-negative.yml
vendored
278
.github/workflows/azure-login-negative.yml
vendored
@@ -9,22 +9,130 @@ permissions:
|
||||
|
||||
jobs:
|
||||
|
||||
OSTest:
|
||||
runs-on: macos-latest
|
||||
environment: autotest
|
||||
|
||||
steps:
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v3.5.2
|
||||
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: 'Run L0 tests'
|
||||
id: run_test
|
||||
continue-on-error: true
|
||||
run: |
|
||||
npm run test
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.run_test.outcome == 'success'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login with creds
|
||||
id: login_1
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP1}}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_1.outcome == 'success'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
id: ps_1
|
||||
continue-on-error: true
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.ps_1.outcome == 'success'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login with individual parameters
|
||||
id: login_2
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_2.outcome == 'success'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Run Azure Cli again
|
||||
run: |
|
||||
az account show
|
||||
|
||||
- name: Run Azure PowerShell again
|
||||
id: ps_2
|
||||
continue-on-error: true
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext | Format-List
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.ps_2.outcome == 'success'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
PermissionTest:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: Automation test
|
||||
environment: autotest
|
||||
|
||||
steps:
|
||||
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3.5.2
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
@@ -36,7 +144,7 @@ jobs:
|
||||
with:
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
# subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
@@ -44,13 +152,13 @@ jobs:
|
||||
id: cli_3
|
||||
continue-on-error: true
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.cli_3.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -58,17 +166,17 @@ jobs:
|
||||
- name: Run Azure PowerShell
|
||||
id: ps_3
|
||||
continue-on-error: true
|
||||
uses: azure/powershell@v1
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
(Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
(Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG'
|
||||
(Get-AzVM).Count -gt 0
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.ps_3.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -78,16 +186,16 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: Automation test
|
||||
environment: autotest
|
||||
|
||||
steps:
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3.5.2
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
@@ -104,7 +212,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_4.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -119,7 +227,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_5.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -133,7 +241,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_6.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -149,7 +257,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_7.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -166,7 +274,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_8.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -179,24 +287,24 @@ jobs:
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
id: ps_8
|
||||
continue-on-error: true
|
||||
uses: azure/powershell@v1
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
(Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
(Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG'
|
||||
(Get-AzVM).Count -gt 0
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.ps_8.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -209,24 +317,24 @@ jobs:
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
id: ps_9
|
||||
continue-on-error: true
|
||||
uses: azure/powershell@v1
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
(Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
(Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG'
|
||||
(Get-AzVM).Count -gt 0
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.ps_9.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
@@ -245,91 +353,7 @@ jobs:
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_10.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login with tenant-level account, without allow-no-subscriptions
|
||||
id: login_11
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_11.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
# SP1 is ignored and SP2 will be used for login, but it will fail since SP2 has no access to the given subscription
|
||||
- name: Login with both creds and individual parameters
|
||||
id: login_12
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP1}}
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_12.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login by OIDC with all info in creds
|
||||
id: login_13
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP2}}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_13.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login with individual parameters, no subscription-id, no allow-no-subscriptions
|
||||
id: login_14
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_14.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
|
||||
- name: Login with creds, no subscription-id, no allow-no-subscriptions
|
||||
id: login_15
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}'
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Check Last step failed
|
||||
if: steps.login_15.outcome == 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
core.setFailed('Last action should fail but not. Please check it.')
|
||||
279
.github/workflows/azure-login-positive.yml
vendored
279
.github/workflows/azure-login-positive.yml
vendored
@@ -12,24 +12,31 @@ jobs:
|
||||
BasicTest:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, self_linux, self_windows]
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: Automation test
|
||||
environment: autotest
|
||||
|
||||
steps:
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
- name: TestGetToken
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
script: |
|
||||
const tempToken = await core.getIDToken('api://AzureADTokenExchange')
|
||||
console.log(tempToken.split('').join(' '))
|
||||
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v3.5.2
|
||||
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
|
||||
- name: 'Run L0 tests'
|
||||
run: |
|
||||
npm run test
|
||||
@@ -42,97 +49,95 @@ jobs:
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Login with individual parameters
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.SP1_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.SP1_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }}
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli again
|
||||
run: |
|
||||
az account show --output none
|
||||
az account show
|
||||
|
||||
- name: Run Azure PowerShell again
|
||||
uses: azure/powershell@v2
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
|
||||
- name: Login with explicit auth-type
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP1}}
|
||||
auth-type: SERVICE_PRINCIPAL
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
Get-AzContext | Format-List
|
||||
|
||||
ParameterTest:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: Automation test
|
||||
environment: autotest
|
||||
|
||||
steps:
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3.5.2
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Login with both creds and individual parameters
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP1}}
|
||||
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Login with creds, disable ps session
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{secrets.SP1}}
|
||||
enable-AzPSSession: false
|
||||
|
||||
- name: Run Azure Cli
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Login with creds, wrong boolean value
|
||||
uses: ./
|
||||
@@ -140,11 +145,11 @@ jobs:
|
||||
creds: ${{secrets.SP1}}
|
||||
enable-AzPSSession: notboolean
|
||||
|
||||
- name: Run Azure Cli
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Login with creds, allow no subscription
|
||||
uses: ./
|
||||
@@ -155,42 +160,18 @@ jobs:
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
az group show --name GitHubAction_CI_RG --output none
|
||||
az vm list --output none
|
||||
|
||||
az account show
|
||||
az group show --name GitHubActionGroup
|
||||
az vm list
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
|
||||
- name: Login with individual parameters, allow no subscription
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.SP1_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.SP1_TENANT_ID}}
|
||||
subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli again
|
||||
run: |
|
||||
az account show --output none
|
||||
|
||||
- name: Run Azure PowerShell again
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
Get-AzContext | Format-List
|
||||
Get-AzResourceGroup -Name GitHubActionGroup
|
||||
Get-AzVM
|
||||
|
||||
- name: Login with individual parameters, no subscription, allow no subscription
|
||||
uses: ./
|
||||
@@ -201,115 +182,13 @@ jobs:
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli
|
||||
shell: pwsh
|
||||
run: |
|
||||
$checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 3
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
az account show
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
uses: azure/powershell@v1.2.0
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
Get-AzContext | Format-List
|
||||
|
||||
- name: Login with creds, no subscription, allow no subscription
|
||||
uses: ./
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.OIDC_SP2_CLIENT_ID }}","clientSecret":"${{ secrets.SP2_CLIENT_SECRET }}","tenantId":"${{ secrets.OIDC_SP2_TENANT_ID }}"}'
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli
|
||||
run: |
|
||||
az account show --output none
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
|
||||
if(-not $checkResult){
|
||||
throw "Not all checks passed!"
|
||||
}
|
||||
|
||||
InDockerTest:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
environment: Automation test
|
||||
steps:
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
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 individual parameters
|
||||
uses: ./
|
||||
with:
|
||||
client-id: ${{ secrets.SP1_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.SP1_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.SP1_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Run Azure Cli again
|
||||
run: |
|
||||
az group list --output none
|
||||
|
||||
- name: Run Azure PowerShell again
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
$checkResult = Get-AzResourceGroup
|
||||
|
||||
55
.github/workflows/azure-login-pr-check.yml
vendored
55
.github/workflows/azure-login-pr-check.yml
vendored
@@ -1,25 +1,26 @@
|
||||
name: pr-check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
- 'releases/*'
|
||||
jobs:
|
||||
az-login-test:
|
||||
environment: Automation test
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout from PR branch
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
# Using 20.x version as an example
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
# Using 16.x version as an example
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: installing node_modules
|
||||
run: npm install
|
||||
@@ -27,5 +28,43 @@ jobs:
|
||||
- name: Build GitHub Action
|
||||
run: npm run build
|
||||
|
||||
- name: Run mock test
|
||||
run: npm run test
|
||||
- name: 'Az CLI login with subscription'
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- run: |
|
||||
az account show
|
||||
# az webapp list
|
||||
|
||||
- name: 'Az CLI login without subscription'
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS_NO_SUB }}
|
||||
allow-no-subscriptions: true
|
||||
|
||||
- run: |
|
||||
az account show
|
||||
|
||||
- name: 'Azure PowerShell login with subscription'
|
||||
uses: ./
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: "Get-AzContext"
|
||||
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"
|
||||
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -1,5 +1,3 @@
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -18,18 +16,18 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: 'Checking out repo code'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set Node.js 20.x for GitHub Action
|
||||
uses: actions/setup-node@v4
|
||||
- name: Set Node.js 16.x for GitHub Action
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: 'Validate build'
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
|
||||
- name: 'Run L0 tests'
|
||||
run: |
|
||||
npm run test
|
||||
|
||||
27
.github/workflows/codeql.yml
vendored
27
.github/workflows/codeql.yml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
@@ -19,18 +14,28 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, 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)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -44,4 +49,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
4
.github/workflows/defaultLabels.yml
vendored
4
.github/workflows/defaultLabels.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v3
|
||||
name: Setting issue as idle
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
operations-per-run: 100
|
||||
exempt-issue-labels: 'backlog'
|
||||
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v3
|
||||
name: Setting PR as idle
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
6
.github/workflows/markdownlint.yml
vendored
6
.github/workflows/markdownlint.yml
vendored
@@ -7,11 +7,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
- name: Run Markdownlint
|
||||
run: |
|
||||
npm i -g markdownlint-cli2
|
||||
|
||||
@@ -7,4 +7,3 @@ Resources:
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- 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)
|
||||
|
||||
788
README.md
788
README.md
@@ -1,522 +1,252 @@
|
||||
# Azure Login Action
|
||||
# GitHub Actions for deploying to Azure
|
||||
|
||||
- [Azure Login Action](#azure-login-action)
|
||||
- [Input Parameters](#input-parameters)
|
||||
- [`client-id`](#client-id)
|
||||
- [`subscription-id`](#subscription-id)
|
||||
- [`tenant-id`](#tenant-id)
|
||||
- [`creds`](#creds)
|
||||
- [`enable-AzPSSession`](#enable-azpssession)
|
||||
- [`environment`](#environment)
|
||||
- [`allow-no-subscriptions`](#allow-no-subscriptions)
|
||||
- [`audience`](#audience)
|
||||
- [`auth-type`](#auth-type)
|
||||
- [Workflow Examples](#workflow-examples)
|
||||
- [Login With OpenID Connect (OIDC) \[Recommended\]](#login-with-openid-connect-oidc-recommended)
|
||||
- [Login With a Service Principal Secret](#login-with-a-service-principal-secret)
|
||||
- [Login With System-assigned Managed Identity](#login-with-system-assigned-managed-identity)
|
||||
- [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity)
|
||||
- [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)
|
||||
- [GitHub Action](#github-action)
|
||||
- [GitHub Actions for deploying to Azure](#github-actions-for-deploying-to-azure)
|
||||
- [Azure CLI Action](#azure-cli-action)
|
||||
- [Azure PowerShell Action](#azure-powershell-action)
|
||||
- [Contributing](#contributing)
|
||||
## Automate your GitHub workflows using Azure Actions
|
||||
|
||||
With the [Azure Login Action](https://github.com/Azure/login), you can login to Azure and run [Azure CLI](https://learn.microsoft.com/cli/azure/) and [Azure PowerShell](https://learn.microsoft.com/powershell/azure) scripts.
|
||||
[GitHub Actions](https://help.github.com/articles/about-github-actions) gives you the flexibility to build an automated software development lifecycle workflow.
|
||||
|
||||
Azure Login Action supports different ways of authentication with Azure.
|
||||
With [GitHub Actions for Azure](https://github.com/Azure/actions/), you can create workflows that you can set up in your repository to build, test, package, release and **deploy** to Azure.
|
||||
|
||||
- Login with OpenID Connect (OIDC)
|
||||
- Login with a Service Principal Secret
|
||||
- Login with System-assigned Managed Identity
|
||||
- Login with User-assigned Managed Identity
|
||||
## GitHub Action for Azure Login
|
||||
|
||||
**We recommend using OIDC based authentication for increased security.**
|
||||
With the [Azure Login](https://github.com/Azure/login/blob/master/action.yml) Action, you can do an Azure login using [Azure Managed Identities and Azure service principal](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview#managed-identity-types) to run Az CLI and Azure PowerShell scripts.
|
||||
|
||||
> [!WARNING]
|
||||
> By default, the output of Azure CLI commands is printed to the stdout stream. Without redirecting the stdout stream, contents in it will be stored in the build log of the action. Configure Azure CLI to _not_ show output in the console screen or print in the log by setting the environment variable `AZURE_CORE_OUTPUT` to `none`. If you need the output of a specific command, override the default setting using the argument `--output` with your format of choice. For more information on output options with the Azure CLI, see [Format output](https://learn.microsoft.com/cli/azure/format-output-azure-cli).
|
||||
- By default, the action only logs in with the Azure CLI (using the `az login` command). To log in with the Az PowerShell module, set `enable-AzPSSession` to true. To login to Azure tenants without any subscriptions, set the optional parameter `allow-no-subscriptions` to true.
|
||||
|
||||
** **
|
||||
- To login into one of the Azure Government clouds or Azure Stack, set the optional parameter `environment` with one of the supported values `AzureUSGovernment` or `AzureChinaCloud` or `AzureStack`. If this parameter is not specified, it takes the default value `AzureCloud` and connects to the Azure Public Cloud. Additionally, the parameter `creds` takes the Azure service principal created in the particular cloud to connect (Refer to the [Configure a service principal with a secret](#configure-a-service-principal-with-a-secret) section below for details).
|
||||
- The Action supports two different ways of authentication with Azure. One using the Azure Service Principal with secrets. The other is OpenID connect (OIDC) method of authentication using Azure [Workload Identity Federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation). **We recommend using OIDC based authentication for increased security.**
|
||||
- To login using Azure Service Principal with a secret, follow [this](#configure-a-service-principal-with-a-secret) guidance.
|
||||
- To login using **OpenID Connect (OIDC) based Federated Identity Credentials**, you need to first configure trust between GitHub workflow and an Azure Managed Identity or an Azure AD App (Service Principal)
|
||||
1. Follow [this](#configure-a-federated-credential-to-use-oidc-based-authentication) guidance to create a Federated Credential associated with your Azure Managed Identity or AD App (Service Principal). This is needed to establish OIDC trust between GitHub deployment workflows and the specific Azure resources scoped by the Managed Identity/service principal.
|
||||
2. In your GitHub workflow, Set `permissions:` with `id-token: write` at workflow level or job level based on whether the OIDC token needs to be auto-generated for all Jobs or a specific Job.
|
||||
3. Within the Job deploying to Azure, add Azure/login action and pass the `client-id` and `tenant-id` of the Azure Managed Identity/service principal associated with an OIDC Federated Identity Credential created in step (i). You also need to pass `subscription-id` or set `allow-no-subscriptions` to true.
|
||||
|
||||
> [!WARNING]
|
||||
> Avoid using managed identity login on self-hosted runners in public repositories. Managed identities enable secure authentication with Azure resources and obtain Microsoft Entra ID tokens without the need for explicit credential management. Any user can open pull requests against your repository and access your self-hosted runners without credentials. See more details in [self-hosted runner security](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security).
|
||||
Note:
|
||||
|
||||
## Input Parameters
|
||||
- Ensure the CLI version is 2.30 or above to use OIDC support.
|
||||
- By default, Azure access tokens issued during OIDC based login could have limited validity. Azure access token issued by AD App (Service Principal) is expected to have an expiration of 1 hour by default. And with Managed Identities, it would be 24 hrs. This expiration time is further configurable in Azure. Refger to [access-token lifetime](https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens#access-token-lifetime) for more details.
|
||||
|
||||
|Parameter Name|Required?|Type|Default Value|Description|
|
||||
|---|---|---|---|---|
|
||||
|client-id|false|UUID||the client id of a service principal or a user-assigned managed identity|
|
||||
|subscription-id|false|UUID||the login subscription id|
|
||||
|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. 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|
|
||||
|
||||
### `client-id`
|
||||
|
||||
The input parameter `client-id` specifies the login client id. It could be the client id of a service principal or a user-assigned managed identity.
|
||||
|
||||
It's used in login with OpenID Connect (OIDC) and user-assigned managed identity.
|
||||
|
||||
It's better to create a GitHub Action secret for this parameter when using it. Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).
|
||||
|
||||
Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage.
|
||||
|
||||
### `subscription-id`
|
||||
|
||||
The input parameter `subscription-id` specifies the login subscription id.
|
||||
|
||||
It's used in login with OpenID Connect (OIDC) and managed identity.
|
||||
|
||||
It's better to create a GitHub Action secret for this parameter when using it. Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).
|
||||
|
||||
Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended), [Login With System-assigned Managed Identity](#login-with-system-assigned-managed-identity) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage.
|
||||
|
||||
### `tenant-id`
|
||||
|
||||
The input parameter `tenant-id` specifies the login tenant id.
|
||||
|
||||
It's used in login with OpenID Connect (OIDC) and managed identity.
|
||||
|
||||
It's better to create a GitHub Action secret for this parameter when using it. Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).
|
||||
|
||||
Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended), [Login With System-assigned Managed Identity](#login-with-system-assigned-managed-identity) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage.
|
||||
|
||||
### `creds`
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> If one of `client-id` and `subscription-id` and `tenant-id` is set, `creds` will be ignored.
|
||||
|
||||
The value of input parameter `creds` is a string in json format, including the following values:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientSecret": "******",
|
||||
"subscriptionId": "******",
|
||||
"tenantId": "******",
|
||||
"clientId": "******"
|
||||
}
|
||||
```
|
||||
|
||||
It's used in login with an Azure service principal.
|
||||
|
||||
It's better to create a GitHub Action secret for this parameter when using it. Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).
|
||||
|
||||
Refer to [Login With a Service Principal Secret](#login-with-a-service-principal-secret) for its usage.
|
||||
|
||||
### `enable-AzPSSession`
|
||||
|
||||
By default, Azure Login Action only logs in with the Azure CLI. To log in with the Azure PowerShell module, set `enable-AzPSSession` to true.
|
||||
|
||||
Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) for its usage.
|
||||
|
||||
### `environment`
|
||||
|
||||
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`
|
||||
|
||||
By default, Azure Login Action requires a `subscription-id`. To login to Azure tenants without any subscriptions, set `allow-no-subscriptions` to true.
|
||||
|
||||
Refer to [Login without subscription](#login-without-subscription) for its usage.
|
||||
|
||||
### `audience`
|
||||
|
||||
Azure Login Action gets the JWT ID token from GitHub OIDC provider when login with OIDC. The default `audience` is `api://AzureADTokenExchange`. Users can specify a custom `audience`.
|
||||
|
||||
### `auth-type`
|
||||
|
||||
The input parameter `auth-type` specifies the type of authentication. The default value is `SERVICE_PRINCIPAL`. Users can specify it as `IDENTITY` for login with Managed Identity.
|
||||
|
||||
Refer to [Login With System-assigned Managed Identity](#login-with-system-assigned-managed-identity) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage.
|
||||
|
||||
## Workflow Examples
|
||||
|
||||
### Login With OpenID Connect (OIDC) [Recommended]
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> - 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 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)
|
||||
- [Configure a federated identity credential on an service principal](https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions)
|
||||
- Prepare a user-assigned managed identity for Login with OIDC
|
||||
- [Create a user-assigned managed identity and assign a role to it](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity)
|
||||
- [Configure a federated identity credential on a user-assigned managed identity](https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity?pivots=identity-wif-mi-methods-azp#github-actions-deploying-azure-resources)
|
||||
|
||||
After it, create GitHub Action secrets for following values: (Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).)
|
||||
|
||||
- AZURE_CLIENT_ID: the service principal client ID or user-assigned managed identity client ID
|
||||
- AZURE_SUBSCRIPTION_ID: the subscription ID
|
||||
- AZURE_TENANT_ID: the tenant ID
|
||||
|
||||
Now you can try the workflow to login with OIDC.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> In GitHub workflow, you should set `permissions:` with `id-token: write` at workflow level or job level based on whether the OIDC token is allowed be generated for all Jobs or a specific Job.
|
||||
|
||||
- **The workflow sample to only run Azure CLI**
|
||||
## Sample workflow that uses Azure login action to run az cli
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: AzureLoginSample
|
||||
|
||||
jobs:
|
||||
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- run: |
|
||||
az webapp list --query "[?state=='Running']"
|
||||
|
||||
```
|
||||
|
||||
## Sample workflow that uses Azure login action to run Azure PowerShell
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: AzurePowerShellLoginSample
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Login via Az module
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{secrets.AZURE_CREDENTIALS}}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- run: |
|
||||
Get-AzVM -ResourceGroupName "ResourceGroup11"
|
||||
|
||||
```
|
||||
|
||||
## Sample workflow that uses Azure login action using OIDC to run az cli (Linux)
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/OIDC_workflow.yml
|
||||
|
||||
name: Run Azure Login with OIDC
|
||||
on: [push]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
- name: Az CLI login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
uses: azure/CLI@v1
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
az group list
|
||||
```
|
||||
|
||||
- **The workflow sample to run both Azure CLI and Azure PowerShell**
|
||||
Users can also specify `audience` field for access-token in the input parameters of the action. If not specified, it is defaulted to `api://AzureADTokenExchange`. This action supports login az powershell as well for both Windows and Linux runners by setting an input parameter `enable-AzPSSession: true`. Below is the sample workflow for the same using the Windows runner. Please note that powershell login is not supported in macOS runners.
|
||||
|
||||
## Sample workflow that uses Azure login action using OIDC to run az PowerShell (Windows)
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
# File: .github/workflows/OIDC_workflow.yml
|
||||
|
||||
name: Run Azure Login with OIDC
|
||||
on: [push]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
Windows-latest:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: OIDC Login to Azure Public Cloud with AzPowershell (enableAzPSSession true)
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
- name: 'Get RG with powershell action'
|
||||
uses: azure/powershell@v1
|
||||
with:
|
||||
inlineScript: |
|
||||
Get-AzResourceGroup
|
||||
azPSVersion: "latest"
|
||||
|
||||
- name: Azure PowerShell script
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext
|
||||
```
|
||||
|
||||
### Login With a Service Principal Secret
|
||||
Refer to the [Azure PowerShell](https://github.com/azure/powershell) GitHub Action to run your Azure PowerShell scripts.
|
||||
|
||||
Before you login a service principal secret, you need to prepare a service principal with a secret.
|
||||
|
||||
- [Create a service principal and assign a role to it](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal)
|
||||
- [Create a new service principal client secret](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-3-create-a-new-client-secret)
|
||||
|
||||
After it, create a GitHub Action secret `AZURE_CREDENTIALS` with the value like below: (Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).)
|
||||
|
||||
```json
|
||||
{
|
||||
"clientSecret": "******",
|
||||
"subscriptionId": "******",
|
||||
"tenantId": "******",
|
||||
"clientId": "******"
|
||||
}
|
||||
```
|
||||
|
||||
- clientSecret: the service principal client secret
|
||||
- subscriptionId: the subscription ID
|
||||
- tenantId: the tenant ID
|
||||
- clientId: the service principal client ID
|
||||
|
||||
Now you can try the workflow to login with a service principal secret.
|
||||
|
||||
- **The workflow sample to only run Azure CLI**
|
||||
## Sample to connect to Azure US Government cloud
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: Run Azure Login With a Service Principal Secret
|
||||
|
||||
jobs:
|
||||
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: azure/login@v2
|
||||
- name: Login to Azure US Gov Cloud with CLI
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
|
||||
environment: 'AzureUSGovernment'
|
||||
enable-AzPSSession: false
|
||||
- name: Login to Azure US Gov Cloud with Az Powershell
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
```
|
||||
|
||||
- **The workflow sample to run both Azure CLI and Azure PowerShell**
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: Run Azure Login With a Service Principal Secret
|
||||
|
||||
jobs:
|
||||
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: azure/login@v2
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
|
||||
- name: Azure PowerShell script
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzWebApp
|
||||
```
|
||||
|
||||
If you want to pass subscription ID, tenant ID, client ID, and client secret as individual parameters instead of bundling them in a single JSON object to address the [security concerns](https://docs.github.com/actions/security-guides/encrypted-secrets), below snippet can help with the same.
|
||||
|
||||
```yaml
|
||||
- uses: azure/login@v2
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}'
|
||||
```
|
||||
|
||||
### Login With System-assigned Managed Identity
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> "Login With System-assigned Managed Identity" is only supported on GitHub self-hosted runners and the self-hosted runners need to be hosted by Azure virtual machines.
|
||||
|
||||
Before you login with system-assigned managed identity, you need to create an Azure virtual machine to host the GitHub self-hosted runner.
|
||||
|
||||
- Create an Azure virtual machine
|
||||
- [Create a Windows virtual machine](https://learn.microsoft.com/azure/virtual-machines/windows/quick-create-portal)
|
||||
- [Create a Linux virtual machine](https://learn.microsoft.com/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu)
|
||||
- [Configure system-assigned managed identity on the Azure virtual machine](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm#system-assigned-managed-identity)
|
||||
- Install required softwares on the Azure virtual machine
|
||||
- [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell)
|
||||
- [Install Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
|
||||
- If you want to run Azure CLI Action, [Install Docker](https://docs.docker.com/engine/install/).
|
||||
- [Install Azure PowerShell](https://learn.microsoft.com/powershell/azure/install-azure-powershell)
|
||||
- [Configure the Azure virtual machine as a GitHub self-hosted runner](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners)
|
||||
|
||||
After it, create GitHub Action secrets for following values: (Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).)
|
||||
|
||||
- AZURE_SUBSCRIPTION_ID: the Subscription ID
|
||||
- AZURE_TENANT_ID: the Tenant ID
|
||||
|
||||
Now you can try the workflow to login with system-assigned managed identity.
|
||||
|
||||
- **The workflow sample to run both Azure CLI and Azure PowerShell**
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
name: Run Azure Login with System-assigned Managed Identity
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
auth-type: IDENTITY
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
# 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`.
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
|
||||
- name: Azure PowerShell script
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext
|
||||
Get-AzResourceGroup
|
||||
```
|
||||
|
||||
### Login With User-assigned Managed Identity
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> "Login With User-assigned Managed Identity" is only supported on GitHub self-hosted runners and the self-hosted runners need to be hosted by Azure virtual machines.
|
||||
|
||||
Before you login with User-assigned managed identity, you need to create an Azure virtual machine to host the GitHub self-hosted runner.
|
||||
|
||||
- Create an Azure virtual machine
|
||||
- [Create a Windows virtual machine](https://learn.microsoft.com/azure/virtual-machines/windows/quick-create-portal)
|
||||
- [Create a Linux virtual machine](https://learn.microsoft.com/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu)
|
||||
- [Create a user-assigned managed identity and assign a role to it](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity)
|
||||
- [Configure user-assigned managed identity on the Azure virtual machine](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm#user-assigned-managed-identity)
|
||||
- Install required softwares on the Azure virtual machine
|
||||
- [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell)
|
||||
- [Install Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
|
||||
- If you want to run Azure CLI Action, [Install Docker](https://docs.docker.com/engine/install/).
|
||||
- [Install Azure PowerShell](https://learn.microsoft.com/powershell/azure/install-azure-powershell)
|
||||
- [Configure the Azure virtual machine as a GitHub self-hosted runner](https://docs.github.com/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners)
|
||||
|
||||
After it, create GitHub Action secrets for following values: (Refer to [Using secrets in GitHub Actions](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions).)
|
||||
|
||||
- AZURE_CLIENT_ID: the user-assigned managed identity client ID
|
||||
- AZURE_SUBSCRIPTION_ID: the subscription ID
|
||||
- AZURE_TENANT_ID: the tenant ID
|
||||
|
||||
Now you can try the workflow to login with user-assigned managed identity.
|
||||
|
||||
- **The workflow sample to run both Azure CLI and Azure PowerShell**
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
name: Run Azure Login with User-assigned Managed Identity
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Azure login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
auth-type: IDENTITY
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
# 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`.
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
|
||||
- name: Azure PowerShell script
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext
|
||||
```
|
||||
|
||||
### Login to Azure US Government cloud
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: Login to Azure US Government cloud
|
||||
|
||||
jobs:
|
||||
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: azure/login@v2
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
|
||||
environment: 'AzureUSGovernment'
|
||||
enable-AzPSSession: true
|
||||
```
|
||||
|
||||
### Login to Azure Stack Hub
|
||||
Refer to the [Azure PowerShell](https://github.com/azure/powershell) GitHub Action to run your Azure PowerShell scripts.
|
||||
|
||||
## Sample Azure Login workflow that uses Azure login action to run az cli on Azure Stack Hub
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
|
||||
on: [push]
|
||||
|
||||
name: Login to Azure Stack Hub cloud
|
||||
name: AzureLoginSample
|
||||
|
||||
jobs:
|
||||
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: azure/login@v2
|
||||
- uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
environment: 'AzureStack'
|
||||
enable-AzPSSession: true
|
||||
|
||||
- run: |
|
||||
az webapp list --query "[?state=='Running']"
|
||||
|
||||
```
|
||||
|
||||
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?view=azs-2008) for more detailed instructions.
|
||||
|
||||
### Login without subscription
|
||||
## Configure deployment credentials
|
||||
|
||||
### Configure a service principal with a secret
|
||||
|
||||
For using any credentials like Azure Service Principal, Publish Profile etc add them as [secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the GitHub repository and then use them in the workflow.
|
||||
|
||||
Follow the following steps to configure Azure Service Principal with a secret:
|
||||
|
||||
- Define a new secret under your repository settings, Add secret menu
|
||||
- Store the output of the below [az cli](https://learn.microsoft.com/cli/azure/?view=azure-cli-latest) command as the value of secret variable, for example 'AZURE_CREDENTIALS'
|
||||
|
||||
```bash
|
||||
|
||||
az ad sp create-for-rbac --name "myApp" --role contributor \
|
||||
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
|
||||
--sdk-auth
|
||||
```
|
||||
|
||||
Replace `{subscription-id}` and `{resource-group}` with the subscription and resource group details, respectively.
|
||||
|
||||
The command should output a JSON object similar to this:
|
||||
|
||||
```bash
|
||||
{
|
||||
"clientId": "<GUID>",
|
||||
"clientSecret": "<STRING>",
|
||||
"subscriptionId": "<GUID>",
|
||||
"tenantId": "<GUID>",
|
||||
"resourceManagerEndpointUrl": "<URL>"
|
||||
(...)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Now in the workflow file in your branch: `.github/workflows/workflow.yml` replace the secret in Azure login action with your secret (Refer to the example above)
|
||||
- Note: The above `az ad sp create-for-rbac` command will give you the `--sdk-auth` deprecation warning. As we are working with CLI for this deprecation process, we strongly recommend users to use this `--sdk-auth` flag as the result dictionary output changes and not accepted by login action if `--sdk-auth` is not used.
|
||||
- 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 (creds) to address the [security concerns](https://docs.github.com/actions/security-guides/encrypted-secrets) for Non-OIDC login, below snippet can help with the same.
|
||||
|
||||
```yaml
|
||||
- uses: Azure/login@v1
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.CLIENT_ID }}","clientSecret":"${{ secrets.CLIENT_SECRET }}","subscriptionId":"${{ secrets.SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TENANT_ID }}"}'
|
||||
```
|
||||
|
||||
In a similar way, any additional parameter can be added to creds such as resourceManagerEndpointUrl for Azure Stack, for example.
|
||||
|
||||
### Manually creating the Credentials object
|
||||
|
||||
If you already created and assigned a Service Principal in Azure you can manually create the .json object above by finding the `clientId` and `clientSecret` on the Service Principal, and your `subscriptionId` and `tenantId` of the subscription and tenant respectively. The `resourceManagerEndpointUrl` will be `https://management.azure.com/` if you are using the public Azure cloud.
|
||||
|
||||
### Configure a Federated Credential to use OIDC based authentication
|
||||
|
||||
Please refer to Microsoft's documentation at ["Configure a federated identity credential on an app”](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) and ["Configure a user-assigned managed identity"](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust-user-assigned-managed-identity?pivots=identity-wif-mi-methods-azp#github-actions-deploying-azure-resources) to trust an external identity provider (preview) which has more details about the Azure Workload Identity Federation (OIDC) support.
|
||||
|
||||
You can add federated credentials in the Azure portal or with the Microsoft Graph REST API.
|
||||
|
||||
## Support for using `allow-no-subscriptions` flag with az login
|
||||
|
||||
Capability has been added to support access to tenants without subscriptions for both OIDC and non-OIDC. This can be useful to run tenant level commands, such as `az ad`. The action accepts an optional parameter `allow-no-subscriptions` which is `false` by default.
|
||||
|
||||
@@ -525,7 +255,7 @@ Capability has been added to support access to tenants without subscriptions for
|
||||
|
||||
on: [push]
|
||||
|
||||
name: Run Azure Login without subscription
|
||||
name: AzureLoginWithNoSubscriptions
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -533,165 +263,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
- uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
allow-no-subscriptions: true
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Azure CLI script
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
az account show
|
||||
|
||||
- name: Run Azure PowerShell
|
||||
uses: azure/powershell@v2
|
||||
with:
|
||||
azPSVersion: "latest"
|
||||
inlineScript: |
|
||||
Get-AzContext
|
||||
```
|
||||
|
||||
### Enable/Disable the cleanup steps
|
||||
## Az logout and security hardening
|
||||
|
||||
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.
|
||||
This action doesn't implement ```az logout``` by default at the end of execution. However there is no way of tampering the credentials or account information because the github hosted runner is on a VM that will get reimaged for every customer run which gets everything deleted. But if the runner is self-hosted which is not github provided it is recommended to manually logout 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).
|
||||
|
||||
```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
|
||||
- name: Azure CLI script
|
||||
uses: azure/CLI@v1
|
||||
with:
|
||||
inlineScript: |
|
||||
az logout
|
||||
az cache purge
|
||||
az account clear
|
||||
```
|
||||
|
||||
```yaml
|
||||
# File: .github/workflows/workflow.yml
|
||||
## Az CLI dependency
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## Reference
|
||||
|
||||
### GitHub Action
|
||||
|
||||
[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
|
||||
|
||||
With [GitHub Actions for Azure](https://github.com/Azure/actions/), you can create workflows that you can set up in your repository to build, test, package, release and **deploy** to Azure.
|
||||
|
||||
### Azure CLI Action
|
||||
|
||||
Refer to the [Azure CLI](https://github.com/azure/cli) GitHub Action to run your Azure CLI scripts.
|
||||
|
||||
### Azure PowerShell Action
|
||||
|
||||
Refer to the [Azure PowerShell](https://github.com/azure/powershell) GitHub Action to run your Azure PowerShell scripts.
|
||||
Internally in this action, we use azure CLI and execute `az login` with the credentials provided through secrets. In order to validate the new az CLI releases for this action, [canary test workflow](.github/workflows/azure-login-canary.yml) is written which will execute the action on [az 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
import { LoginConfig } from "../src/common/LoginConfig";
|
||||
|
||||
describe("LoginConfig Test", () => {
|
||||
|
||||
function setEnv(name: string, value: string) {
|
||||
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value;
|
||||
}
|
||||
|
||||
function cleanEnv() {
|
||||
for (const envKey in process.env) {
|
||||
if (envKey.startsWith('INPUT_')) {
|
||||
delete process.env[envKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testCreds(creds:any){
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
let loginConfig = new LoginConfig();
|
||||
try{
|
||||
await loginConfig.initialize();
|
||||
throw new Error("The last step should fail.");
|
||||
}catch(error){
|
||||
expect(error.message.includes("Not all parameters are provided in 'creds'.")).toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
function testValidateWithErrorMessage(loginConfig:LoginConfig, errorMessage:string){
|
||||
try{
|
||||
loginConfig.validate();
|
||||
throw new Error("The last step should fail.");
|
||||
}catch(error){
|
||||
expect(error.message.includes(errorMessage)).toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cleanEnv();
|
||||
});
|
||||
|
||||
test('initialize with creds, lack of clientId', async () => {
|
||||
let creds1 = {
|
||||
// 'clientId': 'client-id',
|
||||
'clientSecret': 'client-secret',
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
await testCreds(creds1);
|
||||
|
||||
});
|
||||
|
||||
test('initialize with creds, lack of clientSecret', async () => {
|
||||
let creds1 = {
|
||||
'clientId': 'client-id',
|
||||
// 'clientSecret': 'client-secret',
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
await testCreds(creds1);
|
||||
|
||||
});
|
||||
|
||||
test('initialize with creds, lack of tenantId', async () => {
|
||||
let creds1 = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': 'client-secret',
|
||||
// 'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
await testCreds(creds1);
|
||||
|
||||
});
|
||||
|
||||
test('initialize with creds, lack of subscriptionId, but allowNoSubscriptionsLogin=true', async () => {
|
||||
let creds1 = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': 'client-secret',
|
||||
'tenantId': 'tenant-id',
|
||||
// 'subscriptionId': 'subscription-id'
|
||||
}
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
setEnv('creds', JSON.stringify(creds1));
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
expect(loginConfig.environment).toBe("azurecloud");
|
||||
expect(loginConfig.enableAzPSSession).toBeTruthy();
|
||||
expect(loginConfig.allowNoSubscriptionsLogin).toBeTruthy();
|
||||
expect(loginConfig.authType).toBe("SERVICE_PRINCIPAL");
|
||||
expect(loginConfig.servicePrincipalId).toBe("client-id");
|
||||
expect(loginConfig.servicePrincipalSecret).toBe("client-secret");
|
||||
expect(loginConfig.tenantId).toBe("tenant-id");
|
||||
expect(loginConfig.subscriptionId).toBe(undefined);
|
||||
});
|
||||
|
||||
test('initialize with creds', async () => {
|
||||
let creds = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': 'client-secret',
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
expect(loginConfig.environment).toBe("azurecloud");
|
||||
expect(loginConfig.enableAzPSSession).toBeTruthy();
|
||||
expect(loginConfig.allowNoSubscriptionsLogin).toBeFalsy();
|
||||
expect(loginConfig.authType).toBe("SERVICE_PRINCIPAL");
|
||||
expect(loginConfig.servicePrincipalId).toBe("client-id");
|
||||
expect(loginConfig.servicePrincipalSecret).toBe("client-secret");
|
||||
expect(loginConfig.tenantId).toBe("tenant-id");
|
||||
expect(loginConfig.subscriptionId).toBe("subscription-id");
|
||||
});
|
||||
|
||||
test('initialize with individual parameters', async () => {
|
||||
setEnv('environment', 'azureusgovernment');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
expect(loginConfig.environment).toBe("azureusgovernment");
|
||||
expect(loginConfig.enableAzPSSession).toBeFalsy();
|
||||
expect(loginConfig.allowNoSubscriptionsLogin).toBeTruthy();
|
||||
expect(loginConfig.authType).toBe("SERVICE_PRINCIPAL");
|
||||
expect(loginConfig.servicePrincipalId).toBe("client-id");
|
||||
expect(loginConfig.tenantId).toBe("tenant-id");
|
||||
expect(loginConfig.subscriptionId).toBe("subscription-id");
|
||||
});
|
||||
|
||||
test('initialize with both creds and individual parameters', async () => {
|
||||
setEnv('environment', 'azureusgovernment');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
|
||||
setEnv('tenant-id', 'tenant-id-aa');
|
||||
setEnv('subscription-id', 'subscription-id-aa');
|
||||
setEnv('client-id', 'client-id-aa');
|
||||
|
||||
let creds = {
|
||||
'clientId': 'client-id-bb',
|
||||
'clientSecret': 'client-secret-bb',
|
||||
'tenantId': 'tenant-id-bb',
|
||||
'subscriptionId': 'subscription-id-bb'
|
||||
}
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
expect(loginConfig.environment).toBe("azureusgovernment");
|
||||
expect(loginConfig.enableAzPSSession).toBeFalsy();
|
||||
expect(loginConfig.allowNoSubscriptionsLogin).toBeTruthy();
|
||||
expect(loginConfig.authType).toBe("SERVICE_PRINCIPAL");
|
||||
expect(loginConfig.servicePrincipalId).toBe("client-id-aa");
|
||||
expect(loginConfig.servicePrincipalSecret).toBeNull();
|
||||
expect(loginConfig.tenantId).toBe("tenant-id-aa");
|
||||
expect(loginConfig.subscriptionId).toBe("subscription-id-aa");
|
||||
});
|
||||
|
||||
test('validate with wrong environment', async () => {
|
||||
setEnv('environment', 'aWrongCloud');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
|
||||
setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
testValidateWithErrorMessage(loginConfig, "Unsupported value 'awrongcloud' for environment is passed.");
|
||||
});
|
||||
|
||||
test('validate with wrong authType', async () => {
|
||||
setEnv('environment', 'azurestack');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE-PRINCIPAL');
|
||||
|
||||
setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
testValidateWithErrorMessage(loginConfig, "Unsupported value 'SERVICE-PRINCIPAL' for authentication type is passed.");
|
||||
});
|
||||
|
||||
test('validate with SERVICE_PRINCIPAL, lack of tenant id', async () => {
|
||||
setEnv('environment', 'azurestack');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
|
||||
// setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
testValidateWithErrorMessage(loginConfig, "Using auth-type: SERVICE_PRINCIPAL. Not all values are present. Ensure 'client-id' and 'tenant-id' are supplied.");
|
||||
});
|
||||
|
||||
test('validate with SERVICE_PRINCIPAL, lack of client id', async () => {
|
||||
setEnv('environment', 'azurestack');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
|
||||
setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
// setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
testValidateWithErrorMessage(loginConfig, "Using auth-type: SERVICE_PRINCIPAL. Not all values are present. Ensure 'client-id' and 'tenant-id' are supplied.");
|
||||
});
|
||||
|
||||
test('validate without subscriptionId and allowNoSubscriptionsLogin=false', async () => {
|
||||
setEnv('environment', 'azurestack');
|
||||
setEnv('enable-AzPSSession', 'false');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
setEnv('auth-type', 'IDENTITY');
|
||||
|
||||
// setEnv('subscription-id', 'subscription-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
testValidateWithErrorMessage(loginConfig, "Ensure 'subscription-id' is supplied or 'allow-no-subscriptions' is 'true'.");
|
||||
});
|
||||
|
||||
test('validate without subscriptionId and allowNoSubscriptionsLogin=true', async () => {
|
||||
setEnv('environment', 'azurestack');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'IDENTITY');
|
||||
|
||||
// setEnv('subscription-id', 'subscription-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
loginConfig.validate();
|
||||
expect(loginConfig.environment).toBe("azurestack");
|
||||
expect(loginConfig.enableAzPSSession).toBeTruthy();
|
||||
expect(loginConfig.allowNoSubscriptionsLogin).toBeTruthy();
|
||||
expect(loginConfig.authType).toBe("IDENTITY");
|
||||
expect(loginConfig.servicePrincipalId).toBe("");
|
||||
expect(loginConfig.servicePrincipalSecret).toBeNull();
|
||||
expect(loginConfig.tenantId).toBe("");
|
||||
expect(loginConfig.subscriptionId).toBe("");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
import * as os from 'os';
|
||||
|
||||
import { AzPSLogin } from '../../src/PowerShell/AzPSLogin';
|
||||
import { LoginConfig } from '../../src/common/LoginConfig';
|
||||
import { AzPSConstants, AzPSUtils } from '../../src/PowerShell/AzPSUtils';
|
||||
|
||||
let azpsLogin: AzPSLogin;
|
||||
jest.setTimeout(30000);
|
||||
|
||||
beforeAll(() => {
|
||||
var loginConfig = new LoginConfig();
|
||||
loginConfig.servicePrincipalId = "servicePrincipalID";
|
||||
loginConfig.servicePrincipalSecret = "servicePrincipalSecret";
|
||||
loginConfig.tenantId = "tenantId";
|
||||
loginConfig.subscriptionId = "subscriptionId";
|
||||
azpsLogin = new AzPSLogin(loginConfig);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Testing login', () => {
|
||||
let loginSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
loginSpy = jest.spyOn(azpsLogin, 'login');
|
||||
});
|
||||
|
||||
test('ServicePrincipal login should pass', async () => {
|
||||
loginSpy.mockImplementationOnce(() => Promise.resolve());
|
||||
await azpsLogin.login();
|
||||
expect(loginSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing set module path', () => {
|
||||
test('setDefaultPSModulePath should work', () => {
|
||||
AzPSUtils.setPSModulePathForGitHubRunner();
|
||||
const runner: string = process.env.RUNNER_OS || os.type();
|
||||
if(runner.toLowerCase() === "linux"){
|
||||
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
|
||||
}
|
||||
if(runner.toLowerCase().startsWith("windows")){
|
||||
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Testing runPSScript', () => {
|
||||
test('Get PowerShell Version', async () => {
|
||||
let script = `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
$output['Success'] = $true
|
||||
$output['Result'] = $PSVersionTable.PSVersion.ToString()
|
||||
}
|
||||
catch {
|
||||
$output['Success'] = $false
|
||||
$output['Error'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
|
||||
let psVersion: string = await AzPSUtils.runPSScript(script);
|
||||
expect(psVersion === null).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Get PowerShell Version with Wrong Name', async () => {
|
||||
let script = `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
$output['Success'] = $true
|
||||
$output['Result'] = $PSVersionTableWrongName.PSVersion.ToString()
|
||||
}
|
||||
catch {
|
||||
$output['Success'] = $false
|
||||
$output['Error'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
|
||||
try{
|
||||
await AzPSUtils.runPSScript(script);
|
||||
throw new Error("The last step should fail.");
|
||||
}catch(error){
|
||||
expect(error.message.includes("Azure PowerShell login failed with error: You cannot call a method on a null-valued expression.")).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,153 +0,0 @@
|
||||
import AzPSSCriptBuilder from "../../src/PowerShell/AzPSScriptBuilder";
|
||||
import { LoginConfig } from "../../src/common/LoginConfig";
|
||||
|
||||
describe("Getting AzLogin PS script", () => {
|
||||
|
||||
function setEnv(name: string, value: string) {
|
||||
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value;
|
||||
}
|
||||
|
||||
function cleanEnv() {
|
||||
for (const envKey in process.env) {
|
||||
if (envKey.startsWith('INPUT_')) {
|
||||
delete process.env[envKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cleanEnv();
|
||||
});
|
||||
|
||||
test('getImportLatestModuleScript', () => {
|
||||
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path");
|
||||
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("Import-Module -Name $latestModulePath");
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
let creds = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': "client-secret",
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('service principal with secret');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true, secret with single-quote', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
let creds = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': "client-se'cret",
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-se''cret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('service principal with secret');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=false', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false'); // same as true
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
let creds = {
|
||||
'clientId': 'client-id',
|
||||
'clientSecret': 'client-secret',
|
||||
'tenantId': 'tenant-id',
|
||||
'subscriptionId': 'subscription-id'
|
||||
}
|
||||
setEnv('creds', JSON.stringify(creds));
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('service principal with secret');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for OIDC', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
setEnv('tenant-id', 'tenant-id');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('client-id', 'client-id');
|
||||
setEnv('auth-type', 'SERVICE_PRINCIPAL');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";});
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -ApplicationId 'client-id' -FederatedToken 'fake-token' -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('OIDC');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for System MI', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('auth-type', 'IDENTITY');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('system-assigned managed identity');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for System MI without subscription id', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'false');
|
||||
// setEnv('subscription-id', 'subscription-id');
|
||||
setEnv('auth-type', 'IDENTITY');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('system-assigned managed identity');
|
||||
});
|
||||
});
|
||||
|
||||
test('getAzPSLoginScript for user-assigned MI', () => {
|
||||
setEnv('environment', 'azurecloud');
|
||||
setEnv('enable-AzPSSession', 'true');
|
||||
setEnv('allow-no-subscriptions', 'true');
|
||||
setEnv('auth-type', 'IDENTITY');
|
||||
setEnv('client-id', 'client-id');
|
||||
|
||||
let loginConfig = new LoginConfig();
|
||||
loginConfig.initialize();
|
||||
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
|
||||
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' -InformationAction Ignore | out-null;")).toBeTruthy();
|
||||
expect(loginMethod).toBe('user-assigned managed identity');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
44
__tests__/PowerShell/ServicePrinicipalLogin.test.ts
Normal file
44
__tests__/PowerShell/ServicePrinicipalLogin.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ServicePrincipalLogin } from '../../src/PowerShell/ServicePrincipalLogin';
|
||||
import { LoginConfig } from '../../src/common/LoginConfig';
|
||||
|
||||
jest.mock('../../src/PowerShell/Utilities/Utils');
|
||||
jest.mock('../../src/PowerShell/Utilities/PowerShellToolRunner');
|
||||
let spnlogin: ServicePrincipalLogin;
|
||||
|
||||
beforeAll(() => {
|
||||
var loginConfig = new LoginConfig();
|
||||
loginConfig.servicePrincipalId = "servicePrincipalID";
|
||||
loginConfig.servicePrincipalKey = "servicePrinicipalkey";
|
||||
loginConfig.tenantId = "tenantId";
|
||||
loginConfig.subscriptionId = "subscriptionId";
|
||||
spnlogin = new ServicePrincipalLogin(loginConfig);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Testing initialize', () => {
|
||||
let initializeSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
initializeSpy = jest.spyOn(spnlogin, 'initialize');
|
||||
});
|
||||
test('ServicePrincipalLogin initialize should pass', async () => {
|
||||
await spnlogin.initialize();
|
||||
expect(initializeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing login', () => {
|
||||
let loginSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
loginSpy = jest.spyOn(spnlogin, 'login');
|
||||
});
|
||||
test('ServicePrincipal login should pass', async () => {
|
||||
loginSpy.mockImplementationOnce(() => Promise.resolve());
|
||||
await spnlogin.login();
|
||||
expect(loginSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
25
__tests__/PowerShell/Utilities/ScriptBuilder.test.ts
Normal file
25
__tests__/PowerShell/Utilities/ScriptBuilder.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import ScriptBuilder from "../../../src/PowerShell/Utilities/ScriptBuilder";
|
||||
import Constants from "../../../src/PowerShell/Constants";
|
||||
|
||||
describe("Getting AzLogin PS script" , () => {
|
||||
const scheme = Constants.ServicePrincipal;
|
||||
let args: any = {
|
||||
servicePrincipalId: "service-principal-id",
|
||||
servicePrincipalKey: "service-principal-key",
|
||||
environment: "environment",
|
||||
scopeLevel: Constants.Subscription,
|
||||
subscriptionId: "subId",
|
||||
allowNoSubscriptionsLogin: true
|
||||
}
|
||||
|
||||
test("PS script should not set context while passing allowNoSubscriptionsLogin as true", () => {
|
||||
const loginScript = new ScriptBuilder().getAzPSLoginScript(scheme, "tenant-id", args);
|
||||
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeFalsy();
|
||||
});
|
||||
|
||||
test("PS script should set context while passing allowNoSubscriptionsLogin as false", () => {
|
||||
args["allowNoSubscriptionsLogin"] = false;
|
||||
const loginScript = new ScriptBuilder().getAzPSLoginScript(scheme, "tenant-id", args);
|
||||
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
45
__tests__/PowerShell/Utilities/Utils.test.ts
Normal file
45
__tests__/PowerShell/Utilities/Utils.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import Utils from '../../../src/PowerShell/Utilities/Utils';
|
||||
|
||||
const version: string = '9.0.0';
|
||||
const moduleName: string = 'az';
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Testing isValidVersion', () => {
|
||||
const validVersion: string = '1.2.4';
|
||||
const invalidVersion: string = 'a.bcd';
|
||||
|
||||
test('isValidVersion should be true', () => {
|
||||
expect(Utils.isValidVersion(validVersion)).toBeTruthy();
|
||||
});
|
||||
test('isValidVersion should be false', () => {
|
||||
expect(Utils.isValidVersion(invalidVersion)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing setPSModulePath', () => {
|
||||
test('PSModulePath with azPSVersion non-empty', () => {
|
||||
Utils.setPSModulePath(version);
|
||||
expect(process.env.PSModulePath).toContain(version);
|
||||
});
|
||||
test('PSModulePath with azPSVersion empty', () => {
|
||||
const prevPSModulePath = process.env.PSModulePath;
|
||||
Utils.setPSModulePath();
|
||||
expect(process.env.PSModulePath).not.toEqual(prevPSModulePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing getLatestModule', () => {
|
||||
let getLatestModuleSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLatestModuleSpy = jest.spyOn(Utils, 'getLatestModule');
|
||||
});
|
||||
test('getLatestModule should pass', async () => {
|
||||
getLatestModuleSpy.mockImplementationOnce((_moduleName: string) => Promise.resolve(version));
|
||||
await Utils.getLatestModule(moduleName);
|
||||
expect(getLatestModuleSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
20
action.yml
20
action.yml
@@ -1,7 +1,7 @@
|
||||
# Login to Azure subscription
|
||||
name: 'Azure Login'
|
||||
description: 'Authenticate to Azure and run your Azure CLI or Azure PowerShell based actions or scripts.'
|
||||
inputs:
|
||||
description: 'Authenticate to Azure using OIDC and run your Az CLI or Az PowerShell based actions or scripts. github.com/Azure/Actions'
|
||||
inputs:
|
||||
creds:
|
||||
description: 'Paste output of `az ad sp create-for-rbac` as value of secret variable: AZURE_CREDENTIALS'
|
||||
required: false
|
||||
@@ -14,8 +14,8 @@ inputs:
|
||||
subscription-id:
|
||||
description: 'Azure subscriptionId'
|
||||
required: false
|
||||
enable-AzPSSession:
|
||||
description: 'Set this value to true to enable Azure PowerShell Login in addition to Azure CLI login'
|
||||
enable-AzPSSession:
|
||||
description: 'Set this value to true to enable Azure PowerShell Login in addition to Az CLI login'
|
||||
required: false
|
||||
default: false
|
||||
environment:
|
||||
@@ -27,18 +27,12 @@ inputs:
|
||||
required: false
|
||||
default: false
|
||||
audience:
|
||||
description: 'Provide audience field for access-token. Default value is api://AzureADTokenExchange'
|
||||
description: 'Provide audience field for access-token. Default value is api://AzureADTokenExchange'
|
||||
required: false
|
||||
default: 'api://AzureADTokenExchange'
|
||||
auth-type:
|
||||
description: 'The type of authentication. Supported values are SERVICE_PRINCIPAL, IDENTITY. Default value is SERVICE_PRINCIPAL'
|
||||
required: false
|
||||
default: 'SERVICE_PRINCIPAL'
|
||||
branding:
|
||||
icon: 'login.svg'
|
||||
color: 'blue'
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'lib/main/index.js'
|
||||
post-if: (!env.AZURE_LOGIN_POST_CLEANUP || env.AZURE_LOGIN_POST_CLEANUP != 'false')
|
||||
post: 'lib/cleanup/index.js'
|
||||
using: 'node16'
|
||||
main: 'lib/main.js'
|
||||
|
||||
4233
package-lock.json
generated
4233
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,20 +1,17 @@
|
||||
{
|
||||
"name": "login",
|
||||
"version": "2.2.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure",
|
||||
"main": "lib/main/index.js",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"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",
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "Microsoft",
|
||||
"author": "Sumiran Aggarwal",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.4",
|
||||
"@types/node": "^20.11.1",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"@types/node": "^12.7.11",
|
||||
"jest": "^29.3.1",
|
||||
"jest-circus": "^29.3.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
@@ -24,6 +21,7 @@
|
||||
"@actions/core": "1.9.1",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/io": "^1.0.1",
|
||||
"actions-secret-parser": "^1.0.2",
|
||||
"package-lock": "^1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,14 @@ import * as io from '@actions/io';
|
||||
export class AzureCliLogin {
|
||||
loginConfig: LoginConfig;
|
||||
azPath: string;
|
||||
loginOptions: ExecOptions;
|
||||
azVersion: string;
|
||||
|
||||
|
||||
constructor(loginConfig: LoginConfig) {
|
||||
this.loginConfig = loginConfig;
|
||||
this.loginOptions = defaultExecOptions();
|
||||
}
|
||||
|
||||
async login() {
|
||||
core.info(`Running Azure CLI Login.`);
|
||||
this.azPath = await io.which("az", true);
|
||||
core.debug(`Azure CLI path: ${this.azPath}`);
|
||||
core.debug(`az cli path: ${this.azPath}`);
|
||||
|
||||
let output: string = "";
|
||||
const execOptions: any = {
|
||||
@@ -28,44 +24,43 @@ export class AzureCliLogin {
|
||||
}
|
||||
}
|
||||
};
|
||||
await this.executeAzCliCommand("--version", true, execOptions);
|
||||
core.debug(`az cli version used:\n${output}`);
|
||||
|
||||
await this.executeAzCliCommand(["version"], true, execOptions);
|
||||
core.debug(`Azure CLI version used:\n${output}`);
|
||||
try {
|
||||
this.azVersion = JSON.parse(output)["azure-cli"];
|
||||
this.setAzurestackEnvIfNecessary();
|
||||
|
||||
await this.executeAzCliCommand(`cloud set -n "${this.loginConfig.environment}"`, false);
|
||||
console.log(`Done setting cloud: "${this.loginConfig.environment}"`);
|
||||
|
||||
// Attempting Az cli login
|
||||
var commonArgs = ["--service-principal",
|
||||
"-u", this.loginConfig.servicePrincipalId,
|
||||
"--tenant", this.loginConfig.tenantId
|
||||
];
|
||||
if (this.loginConfig.allowNoSubscriptionsLogin) {
|
||||
commonArgs = commonArgs.concat("--allow-no-subscriptions");
|
||||
}
|
||||
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}"`);
|
||||
|
||||
if (this.loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) {
|
||||
let args = ["--service-principal",
|
||||
"--username", this.loginConfig.servicePrincipalId,
|
||||
"--tenant", this.loginConfig.tenantId
|
||||
];
|
||||
if (this.loginConfig.servicePrincipalSecret) {
|
||||
await this.loginWithSecret(args);
|
||||
}
|
||||
else {
|
||||
await this.loginWithOIDC(args);
|
||||
}
|
||||
if (this.loginConfig.enableOIDC) {
|
||||
commonArgs = commonArgs.concat("--federated-token", this.loginConfig.federatedToken);
|
||||
}
|
||||
else {
|
||||
let args = ["--identity"];
|
||||
if (this.loginConfig.servicePrincipalId) {
|
||||
await this.loginWithUserAssignedIdentity(args);
|
||||
}
|
||||
else {
|
||||
await this.loginWithSystemAssignedIdentity(args);
|
||||
}
|
||||
console.log("Note: Azure/login action also supports OIDC login mechanism. Refer https://github.com/azure/login#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication for more details.")
|
||||
commonArgs = commonArgs.concat(`--password=${this.loginConfig.servicePrincipalKey}`);
|
||||
}
|
||||
|
||||
const loginOptions: ExecOptions = defaultExecOptions();
|
||||
await this.executeAzCliCommand(`login`, true, loginOptions, commonArgs);
|
||||
|
||||
if (!this.loginConfig.allowNoSubscriptionsLogin) {
|
||||
var args = [
|
||||
"--subscription",
|
||||
this.loginConfig.subscriptionId
|
||||
];
|
||||
await this.executeAzCliCommand(`account set`, true, loginOptions, args);
|
||||
}
|
||||
}
|
||||
|
||||
async registerAzurestackEnvIfNecessary() {
|
||||
async setAzurestackEnvIfNecessary() {
|
||||
if (this.loginConfig.environment != "azurestack") {
|
||||
return;
|
||||
}
|
||||
@@ -73,16 +68,16 @@ export class AzureCliLogin {
|
||||
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
|
||||
}
|
||||
|
||||
core.info(`Unregistering cloud: "${this.loginConfig.environment}" first if it exists`);
|
||||
console.log(`Unregistering cloud: "${this.loginConfig.environment}" first if it exists`);
|
||||
try {
|
||||
await this.executeAzCliCommand(["cloud", "set", "-n", "AzureCloud"], true);
|
||||
await this.executeAzCliCommand(["cloud", "unregister", "-n", this.loginConfig.environment], false);
|
||||
await this.executeAzCliCommand(`cloud set -n AzureCloud`, true);
|
||||
await this.executeAzCliCommand(`cloud unregister -n "${this.loginConfig.environment}"`, false);
|
||||
}
|
||||
catch (error) {
|
||||
core.info(`Ignore cloud not registered error: "${error}"`);
|
||||
console.log(`Ignore cloud not registered error: "${error}"`);
|
||||
}
|
||||
|
||||
core.info(`Registering cloud: "${this.loginConfig.environment}" with ARM endpoint: "${this.loginConfig.resourceManagerEndpointUrl}"`);
|
||||
console.log(`Registering cloud: "${this.loginConfig.environment}" with ARM endpoint: "${this.loginConfig.resourceManagerEndpointUrl}"`);
|
||||
try {
|
||||
let baseUri = this.loginConfig.resourceManagerEndpointUrl;
|
||||
if (baseUri.endsWith('/')) {
|
||||
@@ -91,75 +86,22 @@ 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}"`);
|
||||
throw error;
|
||||
core.error(`Error while trying to register cloud "${this.loginConfig.environment}": "${error}"`);
|
||||
}
|
||||
|
||||
core.info(`Done registering cloud: "${this.loginConfig.environment}"`)
|
||||
}
|
||||
|
||||
async loginWithSecret(args: string[]) {
|
||||
core.info("Note: Azure/login action also supports OIDC login mechanism. Refer https://github.com/azure/login#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication for more details.")
|
||||
args.push(`--password=${this.loginConfig.servicePrincipalSecret}`);
|
||||
await this.callCliLogin(args, 'service principal with secret');
|
||||
}
|
||||
|
||||
async loginWithOIDC(args: string[]) {
|
||||
await this.loginConfig.getFederatedToken();
|
||||
args.push("--federated-token", this.loginConfig.federatedToken);
|
||||
await this.callCliLogin(args, 'OIDC');
|
||||
}
|
||||
|
||||
async loginWithUserAssignedIdentity(args: string[]) {
|
||||
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');
|
||||
}
|
||||
|
||||
async loginWithSystemAssignedIdentity(args: string[]) {
|
||||
await this.callCliLogin(args, 'system-assigned managed identity');
|
||||
}
|
||||
|
||||
async callCliLogin(args: string[], methodName: string) {
|
||||
core.info(`Attempting Azure CLI login by using ${methodName}...`);
|
||||
args.unshift("login");
|
||||
if (this.loginConfig.allowNoSubscriptionsLogin) {
|
||||
args.push("--allow-no-subscriptions");
|
||||
}
|
||||
await this.executeAzCliCommand(args, true, this.loginOptions);
|
||||
if (this.loginConfig.subscriptionId) {
|
||||
await this.setSubscription();
|
||||
}
|
||||
core.info(`Azure CLI login succeeds by using ${methodName}.`);
|
||||
}
|
||||
|
||||
async setSubscription() {
|
||||
let args = ["account", "set", "--subscription", this.loginConfig.subscriptionId];
|
||||
await this.executeAzCliCommand(args, true, this.loginOptions);
|
||||
core.info("Subscription is set successfully.");
|
||||
console.log(`Done registering cloud: "${this.loginConfig.environment}"`)
|
||||
}
|
||||
|
||||
async executeAzCliCommand(
|
||||
args: string[],
|
||||
command: string,
|
||||
silent?: boolean,
|
||||
execOptions: any = {}) {
|
||||
execOptions: any = {},
|
||||
args: any = []) {
|
||||
execOptions.silent = !!silent;
|
||||
await exec.exec(`"${this.azPath}"`, args, execOptions);
|
||||
await exec.exec(`"${this.azPath}" ${command}`, args, execOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +117,9 @@ function defaultExecOptions(): exec.ExecOptions {
|
||||
if (error && error.trim().length !== 0 && !startsWithWarning) {
|
||||
if (startsWithError) {
|
||||
//removing the keyword 'ERROR' to avoid duplicates while throwing error
|
||||
error = error.slice(7);
|
||||
error = error.slice(5);
|
||||
}
|
||||
core.error(error);
|
||||
core.setFailed(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import AzPSScriptBuilder from './AzPSScriptBuilder';
|
||||
import { AzPSUtils } from './AzPSUtils';
|
||||
import { LoginConfig } from '../common/LoginConfig';
|
||||
|
||||
export class AzPSLogin {
|
||||
loginConfig: LoginConfig;
|
||||
|
||||
constructor(loginConfig: LoginConfig) {
|
||||
this.loginConfig = loginConfig;
|
||||
}
|
||||
|
||||
async login() {
|
||||
core.info(`Running Azure PowerShell Login.`);
|
||||
AzPSUtils.setPSModulePathForGitHubRunner();
|
||||
await AzPSUtils.importLatestAzAccounts();
|
||||
const [loginMethod, loginScript] = await AzPSScriptBuilder.getAzPSLoginScript(this.loginConfig);
|
||||
core.info(`Attempting Azure PowerShell login by using ${loginMethod}...`);
|
||||
core.debug(`Azure PowerShell Login Script: ${loginScript}`);
|
||||
await AzPSUtils.runPSScript(loginScript);
|
||||
console.log(`Running Azure PowerShell Login successfully.`);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import { LoginConfig } from '../common/LoginConfig';
|
||||
|
||||
export default class AzPSScriptBuilder {
|
||||
|
||||
static getImportLatestModuleScript(moduleName: string): string {
|
||||
let script = `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
$latestModulePath = (Get-Module -Name '${moduleName}' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path
|
||||
Import-Module -Name $latestModulePath
|
||||
$output['Success'] = $true
|
||||
$output['Result'] = $latestModulePath
|
||||
}
|
||||
catch {
|
||||
$output['Success'] = $false
|
||||
$output['Error'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
static async getAzPSLoginScript(loginConfig: LoginConfig) {
|
||||
let loginMethodName = "";
|
||||
let commands = "";
|
||||
|
||||
if (loginConfig.environment.toLowerCase() == "azurestack") {
|
||||
commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${loginConfig.resourceManagerEndpointUrl}' | out-null;`;
|
||||
}
|
||||
if (loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) {
|
||||
if (loginConfig.servicePrincipalSecret) {
|
||||
commands += AzPSScriptBuilder.loginWithSecret(loginConfig);
|
||||
loginMethodName = 'service principal with secret';
|
||||
} else {
|
||||
commands += await AzPSScriptBuilder.loginWithOIDC(loginConfig);
|
||||
loginMethodName = "OIDC";
|
||||
}
|
||||
} else {
|
||||
if (loginConfig.servicePrincipalId) {
|
||||
commands += AzPSScriptBuilder.loginWithUserAssignedIdentity(loginConfig);
|
||||
loginMethodName = 'user-assigned managed identity';
|
||||
} else {
|
||||
commands += AzPSScriptBuilder.loginWithSystemAssignedIdentity(loginConfig);
|
||||
loginMethodName = 'system-assigned managed identity';
|
||||
}
|
||||
}
|
||||
|
||||
let script = `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
${commands}
|
||||
$output['Success'] = $true
|
||||
$output['Result'] = ""
|
||||
}
|
||||
catch {
|
||||
$output['Success'] = $false
|
||||
$output['Error'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
|
||||
return [loginMethodName, script];
|
||||
}
|
||||
|
||||
private static loginWithSecret(loginConfig: LoginConfig): string {
|
||||
let servicePrincipalSecret: string = loginConfig.servicePrincipalSecret.split("'").join("''");
|
||||
let loginCmdlet = `$psLoginSecrets = ConvertTo-SecureString '${servicePrincipalSecret}' -AsPlainText -Force; `;
|
||||
loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${loginConfig.servicePrincipalId}', $psLoginSecrets); `;
|
||||
|
||||
let cmdletSuffix = "-Credential $psLoginCredential";
|
||||
loginCmdlet += AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
|
||||
|
||||
return loginCmdlet;
|
||||
}
|
||||
|
||||
private static async loginWithOIDC(loginConfig: LoginConfig) {
|
||||
await loginConfig.getFederatedToken();
|
||||
let cmdletSuffix = `-ApplicationId '${loginConfig.servicePrincipalId}' -FederatedToken '${loginConfig.federatedToken}'`;
|
||||
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
|
||||
}
|
||||
|
||||
private static loginWithSystemAssignedIdentity(loginConfig: LoginConfig): string {
|
||||
let cmdletSuffix = "";
|
||||
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
|
||||
}
|
||||
|
||||
static loginWithUserAssignedIdentity(loginConfig: LoginConfig): string {
|
||||
let cmdletSuffix = `-AccountId '${loginConfig.servicePrincipalId}'`;
|
||||
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
|
||||
}
|
||||
|
||||
private static psLoginCmdlet(authType:string, environment:string, tenantId:string, subscriptionId:string, cmdletSuffix:string){
|
||||
let loginCmdlet = `Connect-AzAccount `;
|
||||
if(authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){
|
||||
loginCmdlet += "-ServicePrincipal ";
|
||||
}else{
|
||||
loginCmdlet += "-Identity ";
|
||||
}
|
||||
loginCmdlet += `-Environment '${environment}' `;
|
||||
if(tenantId){
|
||||
loginCmdlet += `-Tenant '${tenantId}' `;
|
||||
}
|
||||
if(subscriptionId){
|
||||
loginCmdlet += `-Subscription '${subscriptionId}' `;
|
||||
}
|
||||
loginCmdlet += `${cmdletSuffix} -InformationAction Ignore | out-null;`;
|
||||
return loginCmdlet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as io from '@actions/io';
|
||||
import AzPSScriptBuilder from './AzPSScriptBuilder';
|
||||
|
||||
interface PSResultType {
|
||||
Result: string;
|
||||
Success: boolean;
|
||||
Error: string;
|
||||
}
|
||||
|
||||
export class AzPSConstants {
|
||||
static readonly DEFAULT_AZ_PATH_ON_LINUX: string = '/usr/share';
|
||||
static readonly DEFAULT_AZ_PATH_ON_WINDOWS: string = 'C:\\Modules';
|
||||
static readonly AzAccounts: string = "Az.Accounts";
|
||||
static readonly PowerShell_CmdName = "pwsh";
|
||||
}
|
||||
|
||||
export class AzPSUtils {
|
||||
static async setPSModulePathForGitHubRunner() {
|
||||
const runner: string = process.env.RUNNER_OS || os.type();
|
||||
switch (runner.toLowerCase()) {
|
||||
case "linux":
|
||||
AzPSUtils.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
|
||||
break;
|
||||
case "windows":
|
||||
case "windows_nt":
|
||||
AzPSUtils.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
|
||||
break;
|
||||
case "macos":
|
||||
case "darwin":
|
||||
core.warning(`Skip setting the default PowerShell module path for OS ${runner.toLowerCase()}.`);
|
||||
break;
|
||||
default:
|
||||
core.warning(`Skip setting the default PowerShell module path for unknown OS ${runner.toLowerCase()}.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static pushPSModulePath(psModulePath: string) {
|
||||
process.env.PSModulePath = `${psModulePath}${path.delimiter}${process.env.PSModulePath}`;
|
||||
core.debug(`Set PSModulePath as ${process.env.PSModulePath}`);
|
||||
}
|
||||
|
||||
static async importLatestAzAccounts() {
|
||||
let importLatestAccountsScript: string = AzPSScriptBuilder.getImportLatestModuleScript(AzPSConstants.AzAccounts);
|
||||
core.debug(`The script to import the latest Az.Accounts: ${importLatestAccountsScript}`);
|
||||
let azAccountsPath: string = await AzPSUtils.runPSScript(importLatestAccountsScript);
|
||||
core.debug(`The latest Az.Accounts used: ${azAccountsPath}`);
|
||||
}
|
||||
|
||||
static async runPSScript(psScript: string): Promise<string> {
|
||||
let outputString: string = "";
|
||||
let commandStdErr = false;
|
||||
const options: any = {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
outputString += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
let error = data.toString();
|
||||
if (error && error.trim().length !== 0) {
|
||||
commandStdErr = true;
|
||||
core.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true);
|
||||
await exec.exec(`"${psPath}"`, ["-Command", psScript], options)
|
||||
if (commandStdErr) {
|
||||
throw new Error('Azure PowerShell login failed with errors.');
|
||||
}
|
||||
const result: PSResultType = JSON.parse(outputString.trim());
|
||||
console.log(result);
|
||||
if (!(result.Success)) {
|
||||
throw new Error(`Azure PowerShell login failed with error: ${result.Error}`);
|
||||
}
|
||||
return result.Result;
|
||||
}
|
||||
}
|
||||
13
src/PowerShell/Constants.ts
Normal file
13
src/PowerShell/Constants.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default class Constants {
|
||||
static readonly prefix: string = "az_";
|
||||
static readonly moduleName: string = "Az.Accounts";
|
||||
static readonly versionPattern = /[0-9]+\.[0-9]+\.[0-9]+/;
|
||||
|
||||
static readonly AzureCloud: string = "AzureCloud";
|
||||
static readonly Subscription: string = "Subscription";
|
||||
static readonly ServicePrincipal: string = "ServicePrincipal";
|
||||
|
||||
static readonly Success: string = "Success";
|
||||
static readonly Error: string = "Error";
|
||||
static readonly AzVersion: string = "AzVersion";
|
||||
}
|
||||
4
src/PowerShell/IAzurePowerShellSession.ts
Normal file
4
src/PowerShell/IAzurePowerShellSession.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
interface IAzurePowerShellSession {
|
||||
initialize();
|
||||
login();
|
||||
}
|
||||
62
src/PowerShell/ServicePrincipalLogin.ts
Normal file
62
src/PowerShell/ServicePrincipalLogin.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import Utils from './Utilities/Utils';
|
||||
import PowerShellToolRunner from './Utilities/PowerShellToolRunner';
|
||||
import ScriptBuilder from './Utilities/ScriptBuilder';
|
||||
import Constants from './Constants';
|
||||
import { LoginConfig } from '../common/LoginConfig';
|
||||
|
||||
export class ServicePrincipalLogin implements IAzurePowerShellSession {
|
||||
static readonly scopeLevel: string = Constants.Subscription;
|
||||
static readonly scheme: string = Constants.ServicePrincipal;
|
||||
loginConfig: LoginConfig;
|
||||
|
||||
constructor(loginConfig: LoginConfig) {
|
||||
this.loginConfig = loginConfig;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
Utils.setPSModulePath();
|
||||
const azLatestVersion: string = await Utils.getLatestModule(Constants.moduleName);
|
||||
core.debug(`Az Module version used: ${azLatestVersion}`);
|
||||
Utils.setPSModulePath(`${Constants.prefix}${azLatestVersion}`);
|
||||
}
|
||||
|
||||
async login() {
|
||||
let output: string = "";
|
||||
let commandStdErr = false;
|
||||
const options: any = {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
let error = data.toString();
|
||||
if (error && error.trim().length !== 0) {
|
||||
commandStdErr = true;
|
||||
core.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const args: any = {
|
||||
servicePrincipalId: this.loginConfig.servicePrincipalId,
|
||||
servicePrincipalKey: this.loginConfig.servicePrincipalKey,
|
||||
federatedToken: this.loginConfig.federatedToken,
|
||||
subscriptionId: this.loginConfig.subscriptionId,
|
||||
environment: this.loginConfig.environment,
|
||||
scopeLevel: ServicePrincipalLogin.scopeLevel,
|
||||
allowNoSubscriptionsLogin: this.loginConfig.allowNoSubscriptionsLogin,
|
||||
resourceManagerEndpointUrl: this.loginConfig.resourceManagerEndpointUrl
|
||||
}
|
||||
const script: string = new ScriptBuilder().getAzPSLoginScript(ServicePrincipalLogin.scheme, this.loginConfig.tenantId, args);
|
||||
await PowerShellToolRunner.init();
|
||||
await PowerShellToolRunner.executePowerShellScriptBlock(script, options);
|
||||
const result: any = JSON.parse(output.trim());
|
||||
if (!(Constants.Success in result)) {
|
||||
throw new Error(`Azure PowerShell login failed with error: ${result[Constants.Error]}`);
|
||||
}
|
||||
console.log(`Azure PowerShell session successfully initialized`);
|
||||
}
|
||||
|
||||
}
|
||||
16
src/PowerShell/Utilities/PowerShellToolRunner.ts
Normal file
16
src/PowerShell/Utilities/PowerShellToolRunner.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as io from '@actions/io';
|
||||
import * as exec from '@actions/exec';
|
||||
|
||||
export default class PowerShellToolRunner {
|
||||
static psPath: string;
|
||||
static async init() {
|
||||
if(!PowerShellToolRunner.psPath) {
|
||||
PowerShellToolRunner.psPath = await io.which("pwsh", true);
|
||||
}
|
||||
}
|
||||
|
||||
static async executePowerShellScriptBlock(scriptBlock: string, options: any = {}) {
|
||||
//Options for error handling
|
||||
await exec.exec(`"${PowerShellToolRunner.psPath}" -Command`, [scriptBlock], options)
|
||||
}
|
||||
}
|
||||
67
src/PowerShell/Utilities/ScriptBuilder.ts
Normal file
67
src/PowerShell/Utilities/ScriptBuilder.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import Constants from "../Constants";
|
||||
|
||||
export default class ScriptBuilder {
|
||||
script: string = "";
|
||||
|
||||
getAzPSLoginScript(scheme: string, tenantId: string, args: any): string {
|
||||
let command = `Clear-AzContext -Scope Process;
|
||||
Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue;`;
|
||||
|
||||
if (scheme === Constants.ServicePrincipal) {
|
||||
|
||||
if (args.environment.toLowerCase() == "azurestack") {
|
||||
command += `Add-AzEnvironment -Name ${args.environment} -ARMEndpoint ${args.resourceManagerEndpointUrl} | out-null;`;
|
||||
}
|
||||
// Separate command script for OIDC and non-OIDC
|
||||
if (!!args.federatedToken) {
|
||||
command += `Connect-AzAccount -ServicePrincipal -ApplicationId '${args.servicePrincipalId}' -Tenant '${tenantId}' -FederatedToken '${args.federatedToken}' \
|
||||
-Environment '${args.environment}' | out-null;`;
|
||||
}
|
||||
else {
|
||||
command += `Connect-AzAccount -ServicePrincipal -Tenant '${tenantId}' -Credential \
|
||||
(New-Object System.Management.Automation.PSCredential('${args.servicePrincipalId}',(ConvertTo-SecureString '${args.servicePrincipalKey.replace("'", "''")}' -AsPlainText -Force))) \
|
||||
-Environment '${args.environment}' | out-null;`;
|
||||
}
|
||||
// command to set the subscription
|
||||
if (args.scopeLevel === Constants.Subscription && !args.allowNoSubscriptionsLogin) {
|
||||
command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`;
|
||||
}
|
||||
}
|
||||
|
||||
this.script += `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
${command}
|
||||
$output['${Constants.Success}'] = "true"
|
||||
}
|
||||
catch {
|
||||
$output['${Constants.Error}'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
|
||||
core.debug(`Azure PowerShell Login Script: ${this.script}`);
|
||||
return this.script;
|
||||
}
|
||||
|
||||
getLatestModuleScript(moduleName: string): string {
|
||||
const command: string = `Get-Module -Name ${moduleName} -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1`;
|
||||
this.script += `try {
|
||||
$ErrorActionPreference = "Stop"
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$output = @{}
|
||||
$data = ${command}
|
||||
$output['${Constants.AzVersion}'] = $data.Version.ToString()
|
||||
$output['${Constants.Success}'] = "true"
|
||||
}
|
||||
catch {
|
||||
$output['${Constants.Error}'] = $_.exception.Message
|
||||
}
|
||||
return ConvertTo-Json $output`;
|
||||
core.debug(`GetLatestModuleScript: ${this.script}`);
|
||||
return this.script;
|
||||
}
|
||||
|
||||
}
|
||||
61
src/PowerShell/Utilities/Utils.ts
Normal file
61
src/PowerShell/Utilities/Utils.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as os from 'os';
|
||||
|
||||
import Constants from '../Constants';
|
||||
import ScriptBuilder from './ScriptBuilder';
|
||||
import PowerShellToolRunner from './PowerShellToolRunner';
|
||||
|
||||
export default class Utils {
|
||||
/**
|
||||
* Add the folder path where Az modules are present to PSModulePath based on runner
|
||||
* @param azPSVersion
|
||||
* If azPSVersion is empty, folder path in which all Az modules are present are set
|
||||
* If azPSVersion is not empty, folder path of exact Az module version is set
|
||||
*/
|
||||
static setPSModulePath(azPSVersion: string = "") {
|
||||
let modulePath: string = "";
|
||||
const runner: string = process.env.RUNNER_OS || os.type();
|
||||
switch (runner.toLowerCase()) {
|
||||
case "linux":
|
||||
modulePath = `/usr/share/${azPSVersion}:`;
|
||||
break;
|
||||
case "windows":
|
||||
case "windows_nt":
|
||||
modulePath = `C:\\Modules\\${azPSVersion};`;
|
||||
break;
|
||||
case "macos":
|
||||
case "darwin":
|
||||
throw new Error(`OS not supported`);
|
||||
default:
|
||||
throw new Error(`Unknown os: ${runner.toLowerCase()}`);
|
||||
}
|
||||
process.env.PSModulePath = `${modulePath}${process.env.PSModulePath}`;
|
||||
}
|
||||
|
||||
static async getLatestModule(moduleName: string): Promise<string> {
|
||||
let output: string = "";
|
||||
const options: any = {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
output += data.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
await PowerShellToolRunner.init();
|
||||
await PowerShellToolRunner.executePowerShellScriptBlock(new ScriptBuilder()
|
||||
.getLatestModuleScript(moduleName), options);
|
||||
const result = JSON.parse(output.trim());
|
||||
if (!(Constants.Success in result)) {
|
||||
throw new Error(result[Constants.Error]);
|
||||
}
|
||||
const azLatestVersion: string = result[Constants.AzVersion];
|
||||
if (!Utils.isValidVersion(azLatestVersion)) {
|
||||
throw new Error(`Invalid AzPSVersion: ${azLatestVersion}`);
|
||||
}
|
||||
return azLatestVersion;
|
||||
}
|
||||
|
||||
static isValidVersion(version: string): boolean {
|
||||
return !!version.match(Constants.versionPattern);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import { setUserAgent, cleanupAzCLIAccounts, cleanupAzPSAccounts } from './common/Utils';
|
||||
|
||||
async function cleanup() {
|
||||
try {
|
||||
setUserAgent();
|
||||
await cleanupAzCLIAccounts();
|
||||
if(core.getInput('enable-AzPSSession').toLowerCase() === "true"){
|
||||
await cleanupAzPSAccounts();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Login cleanup failed with ${error}. Cleanup will be skipped.`);
|
||||
core.debug(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as core from '@actions/core';
|
||||
import { FormatType, SecretParser } from 'actions-secret-parser';
|
||||
|
||||
export class LoginConfig {
|
||||
static readonly AUTH_TYPE_SERVICE_PRINCIPAL = "SERVICE_PRINCIPAL";
|
||||
static readonly AUTH_TYPE_IDENTITY = "IDENTITY";
|
||||
static readonly azureSupportedCloudName = new Set([
|
||||
"azureusgovernment",
|
||||
"azurechinacloud",
|
||||
@@ -10,107 +9,78 @@ export class LoginConfig {
|
||||
"azurecloud",
|
||||
"azurestack"]);
|
||||
|
||||
static readonly azureSupportedAuthType = new Set([
|
||||
LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL,
|
||||
LoginConfig.AUTH_TYPE_IDENTITY]);
|
||||
|
||||
authType: string;
|
||||
servicePrincipalId: string;
|
||||
servicePrincipalSecret: string;
|
||||
servicePrincipalKey: string;
|
||||
tenantId: string;
|
||||
subscriptionId: string;
|
||||
resourceManagerEndpointUrl: string;
|
||||
allowNoSubscriptionsLogin: boolean;
|
||||
enableOIDC: boolean;
|
||||
environment: string;
|
||||
enableAzPSSession: boolean;
|
||||
audience: string;
|
||||
federatedToken: string;
|
||||
|
||||
constructor() {
|
||||
this.enableOIDC = true;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.environment = core.getInput("environment").toLowerCase();
|
||||
this.enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
|
||||
this.allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
|
||||
this.authType = core.getInput('auth-type').toUpperCase();
|
||||
|
||||
this.servicePrincipalId = core.getInput('client-id', { required: false });
|
||||
this.servicePrincipalSecret = null;
|
||||
this.servicePrincipalKey = null;
|
||||
this.tenantId = core.getInput('tenant-id', { required: false });
|
||||
this.subscriptionId = core.getInput('subscription-id', { required: false });
|
||||
|
||||
this.readParametersFromCreds();
|
||||
|
||||
this.audience = core.getInput('audience', { required: false });
|
||||
this.federatedToken = null;
|
||||
|
||||
this.mask(this.servicePrincipalId);
|
||||
this.mask(this.servicePrincipalSecret);
|
||||
}
|
||||
|
||||
private readParametersFromCreds() {
|
||||
let creds = core.getInput('creds', { required: false });
|
||||
if (!creds) {
|
||||
return;
|
||||
}
|
||||
let secrets = JSON.parse(creds);
|
||||
let secrets = creds ? new SecretParser(creds, FormatType.JSON) : null;
|
||||
|
||||
if(this.authType != LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.servicePrincipalId || this.tenantId || this.subscriptionId) {
|
||||
core.warning("At least one of the parameters 'client-id', 'subscription-id' or 'tenant-id' is set. 'creds' will be ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
core.debug('Reading creds in JSON...');
|
||||
this.servicePrincipalId = this.servicePrincipalId ? this.servicePrincipalId : secrets.clientId;
|
||||
this.servicePrincipalSecret = secrets.clientSecret;
|
||||
this.tenantId = this.tenantId ? this.tenantId : secrets.tenantId;
|
||||
this.subscriptionId = this.subscriptionId ? this.subscriptionId : secrets.subscriptionId;
|
||||
this.resourceManagerEndpointUrl = secrets.resourceManagerEndpointUrl;
|
||||
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'.");
|
||||
if (creds) {
|
||||
core.debug('using creds JSON...');
|
||||
this.enableOIDC = false;
|
||||
this.servicePrincipalId = secrets.getSecret("$.clientId", true);
|
||||
this.servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
|
||||
this.tenantId = secrets.getSecret("$.tenantId", true);
|
||||
this.subscriptionId = secrets.getSecret("$.subscriptionId", true);
|
||||
this.resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
|
||||
}
|
||||
this.getFederatedTokenIfNecessary();
|
||||
}
|
||||
|
||||
async getFederatedToken() {
|
||||
async getFederatedTokenIfNecessary() {
|
||||
if (!this.enableOIDC) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.federatedToken = await core.getIDToken(this.audience);
|
||||
this.mask(this.federatedToken);
|
||||
}
|
||||
catch (error) {
|
||||
core.error("Failed to fetch federated token from GitHub. Please make sure to give write permissions to id-token in the workflow.");
|
||||
core.error(`Please make sure to give write permissions to id-token in the workflow.`);
|
||||
throw error;
|
||||
}
|
||||
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);
|
||||
if (!!this.federatedToken) {
|
||||
let [issuer, subjectClaim] = await jwtParser(this.federatedToken);
|
||||
console.log("Federated token details: \n issuer - " + issuer + " \n subject claim - " + subjectClaim);
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Failed to parse the federated token. Error: ${error}`);
|
||||
else {
|
||||
throw new Error("Failed to fetch federated token.");
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
if (!LoginConfig.azureSupportedCloudName.has(this.environment)) {
|
||||
throw new Error(`Unsupported value '${this.environment}' for environment is passed. The list of supported values for environment are '${Array.from(LoginConfig.azureSupportedCloudName).join("', '")}'. `);
|
||||
}
|
||||
if (!LoginConfig.azureSupportedAuthType.has(this.authType)) {
|
||||
throw new Error(`Unsupported value '${this.authType}' for authentication type is passed. The list of supported values for auth-type are '${Array.from(LoginConfig.azureSupportedAuthType).join("', '")}'.`);
|
||||
}
|
||||
if (this.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) {
|
||||
if (!this.servicePrincipalId || !this.tenantId) {
|
||||
throw new Error(`Using auth-type: ${LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL}. Not all values are present. Ensure 'client-id' and 'tenant-id' are supplied.`);
|
||||
}
|
||||
async validate() {
|
||||
if (!this.servicePrincipalId || !this.tenantId || !(this.servicePrincipalKey || this.enableOIDC)) {
|
||||
throw new Error("Not all values are present in the credentials. Ensure clientId, clientSecret and tenantId are supplied.");
|
||||
}
|
||||
if (!this.subscriptionId && !this.allowNoSubscriptionsLogin) {
|
||||
throw new Error("Ensure 'subscription-id' is supplied or 'allow-no-subscriptions' is 'true'.");
|
||||
throw new Error("Not all values are present in the credentials. Ensure subscriptionId is supplied.");
|
||||
}
|
||||
}
|
||||
|
||||
mask(parameterValue: string) {
|
||||
if (parameterValue) {
|
||||
core.setSecret(parameterValue);
|
||||
if (!LoginConfig.azureSupportedCloudName.has(this.environment)) {
|
||||
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are ‘azureusgovernment', ‘azurechinacloud’, ‘azuregermancloud’, ‘azurecloud’ or ’azurestack’");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,20 +89,5 @@ async function jwtParser(federatedToken: string) {
|
||||
let tokenPayload = federatedToken.split('.')[1];
|
||||
let bufferObj = Buffer.from(tokenPayload, "base64");
|
||||
let decodedPayload = JSON.parse(bufferObj.toString("utf8"));
|
||||
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]];
|
||||
}
|
||||
return [decodedPayload['iss'], decodedPayload['sub']];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as io from '@actions/io';
|
||||
import * as crypto from 'crypto';
|
||||
import { AzPSConstants, AzPSUtils } from '../PowerShell/AzPSUtils';
|
||||
|
||||
export function setUserAgent(): void {
|
||||
let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex');
|
||||
let actionName = 'AzureLogin';
|
||||
process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@v2_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`;
|
||||
process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v2_${usrAgentRepo}_${process.env.RUNNER_ENVIRONMENT}_${process.env.GITHUB_RUN_ID}`;
|
||||
}
|
||||
|
||||
export async function cleanupAzCLIAccounts(): Promise<void> {
|
||||
let azPath = await io.which("az", true);
|
||||
core.debug(`Azure CLI path: ${azPath}`);
|
||||
core.info("Clearing azure cli accounts from the local cache.");
|
||||
await exec.exec(`"${azPath}"`, ["account", "clear"]);
|
||||
}
|
||||
|
||||
export async function cleanupAzPSAccounts(): Promise<void> {
|
||||
let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true);
|
||||
core.debug(`PowerShell path: ${psPath}`);
|
||||
core.debug("Importing Azure PowerShell module.");
|
||||
AzPSUtils.setPSModulePathForGitHubRunner();
|
||||
await AzPSUtils.importLatestAzAccounts();
|
||||
core.info("Clearing azure powershell accounts from the local cache.");
|
||||
await exec.exec(`"${psPath}"`, ["-Command", "Clear-AzContext", "-Scope", "Process"]);
|
||||
await exec.exec(`"${psPath}"`, ["-Command", "Clear-AzContext", "-Scope", "CurrentUser", "-Force", "-ErrorAction", "SilentlyContinue"]);
|
||||
}
|
||||
52
src/main.ts
52
src/main.ts
@@ -1,40 +1,54 @@
|
||||
import * as core from '@actions/core';
|
||||
import { cleanupAzCLIAccounts, cleanupAzPSAccounts, setUserAgent } from './common/Utils';
|
||||
import { AzPSLogin } from './PowerShell/AzPSLogin';
|
||||
import { ServicePrincipalLogin } from './PowerShell/ServicePrincipalLogin';
|
||||
import { LoginConfig } from './common/LoginConfig';
|
||||
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();
|
||||
}
|
||||
}
|
||||
var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : "";
|
||||
var azPSHostEnv = !!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT}` : "";
|
||||
|
||||
// prepare the login configuration
|
||||
async function main() {
|
||||
var isAzCLISuccess = false;
|
||||
try {
|
||||
let usrAgentRepo = `${process.env.GITHUB_REPOSITORY}`;
|
||||
let actionName = 'AzureLogin';
|
||||
let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
|
||||
let azurePSHostEnv = (!!azPSHostEnv ? `${azPSHostEnv}+` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
|
||||
core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString);
|
||||
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv);
|
||||
|
||||
// perpare the login configuration
|
||||
var loginConfig = new LoginConfig();
|
||||
await loginConfig.initialize();
|
||||
await loginConfig.validate();
|
||||
|
||||
// login to Azure CLI
|
||||
// login to Azure Cli
|
||||
var cliLogin = new AzureCliLogin(loginConfig);
|
||||
await cliLogin.login();
|
||||
isAzCLISuccess = true;
|
||||
|
||||
//login to Azure PowerShell
|
||||
if (loginConfig.enableAzPSSession) {
|
||||
var psLogin: AzPSLogin = new AzPSLogin(loginConfig);
|
||||
await psLogin.login();
|
||||
console.log(`Running Azure PS Login`);
|
||||
var spnlogin: ServicePrincipalLogin = new ServicePrincipalLogin(loginConfig);
|
||||
await spnlogin.initialize();
|
||||
await spnlogin.login();
|
||||
}
|
||||
|
||||
console.log("Login successful.");
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(`Login failed with ${error}. Double check if the 'auth-type' is correct. Refer to https://github.com/Azure/login#readme for more information.`);
|
||||
core.debug(error.stack);
|
||||
if (!isAzCLISuccess) {
|
||||
core.setFailed(`Az CLI Login failed with ${error}. Please check the credentials and make sure az is installed on the runner. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows`);
|
||||
}
|
||||
else {
|
||||
core.setFailed(`Azure PowerShell Login failed with ${error}. Please check the credentials and make sure az is installed on the runner. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows`);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Reset AZURE_HTTP_USER_AGENT
|
||||
core.exportVariable('AZURE_HTTP_USER_AGENT', prefix);
|
||||
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azPSHostEnv);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user