Compare commits

...

51 Commits

Author SHA1 Message Date
Balaga Gayatri
f66a7f4882 Update Constants.js 2022-09-09 16:20:24 +05:30
Balaga Gayatri
efba1717eb Update Constants.ts 2022-09-09 16:19:38 +05:30
Balaga Gayatri
6453efca84 Rename integration-tests.yml to azure-login-integration-tests.yml 2022-07-11 15:14:41 +05:30
Balaga Gayatri
e6ca011ab7 Create integration-tests.yml 2022-07-11 15:14:11 +05:30
Balaga Gayatri
c4459aba85 Deleting this workflow as this is no longer used 2022-07-11 13:33:42 +05:30
Nathan Gelman
c09ca4f217 Fixed spelling error (#237) 2022-07-11 13:04:44 +05:30
Balaga Gayatri
4443ffd660 Update README.md 2022-07-11 12:54:08 +05:30
Balaga Gayatri
b4a959b8bd Update README.md 2022-06-22 12:42:23 +05:30
Balaga Gayatri
5d09f4d5ca Update README.md 2022-06-22 12:41:16 +05:30
Balaga Gayatri
14a755a4e2 Id token errormsg (#234)
* putting id-token generation in try catch block

* Updating node version in action.yml
2022-06-21 16:43:50 +05:30
Balaga Gayatri
11ff950770 Update azure-login-canary.yml 2022-06-14 12:22:53 +05:30
Balaga Gayatri
980d0f57a2 Added Canary tests related documentation and few more cases(#232) 2022-06-10 17:20:07 +05:30
Balaga Gayatri
819ac8d2a9 Update azure-login-canary.yml (#230) 2022-06-03 17:11:18 +05:30
Balaga Gayatri
63b39ef8c4 Create azure-login-canary.yml (#229) 2022-06-03 16:03:36 +05:30
Balaga Gayatri
e021afe0dc Handling warnings and adding OIDC promotion message (#221)
* Handling warnings and adding OIDC promotion message

* Implementing NIT suggestions
2022-05-02 19:46:30 +05:30
Balaga Gayatri
85f8f21203 Update README.md 2022-04-18 22:50:06 +05:30
Sean MacKay
827604025b Update ClientSecret in the example to not call itself a GUID (#208)
* Update ClientSecret in the example to not call itself a GUID

If manually crafting or verifying a Service Principal credential JSON object, it is helpful to know that only the `clientId` is formatted as a GUID, and the `clientSecret` is just an alphanumeric string.

* Update README.md

Co-authored-by: Balaga Gayatri <balaga-gayatri@github.com>
2022-04-11 19:45:44 +05:30
Sean MacKay
06f50cc138 Add critical information if manually creating SP Credential object (#209)
* Add critical information if manually creating SP Credential object

If you must manually create the credential JSON (for instance, the Service Principal was created and assigned but the output credential was not saved), finding the `clientId`, `clientSecret`, `subscriptionId` and `tenantId` in azure is possible, but the `resourceManagerEndpointUrl` is also required by this Action and was much harder to track down.

By explaining it is possible and including the value for the public azure cloud (the default use case) it will help others be able to use this Action with existing Service Principal's as well.

* Update README.md

Co-authored-by: Balaga Gayatri <balaga-gayatri@github.com>
2022-04-11 19:22:52 +05:30
dependabot[bot]
23801eadd7 Bump ansi-regex from 4.1.0 to 4.1.1 (#219)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 19:06:51 +05:30
dependabot[bot]
8f2def4beb Bump minimist from 1.2.5 to 1.2.6 (#212)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Balaga Gayatri <balaga-gayatri@github.com>
2022-04-11 18:59:52 +05:30
Balaga Gayatri
412f48d98e Update README.md 2022-04-11 18:07:56 +05:30
Balaga Gayatri
c02223cd62 Update README.md 2022-03-29 18:44:11 +05:30
Balaga Gayatri
84519181a8 Adding az installation requirement to login error response (#198)
* Update main.ts

* Update main.js
2022-01-19 22:50:55 +05:30
Balaga Gayatri
db989b3060 Delete .DS_Store 2021-12-16 21:51:49 +05:30
Dhiwakar Kusuma
276f50e2dc Update README.md (#189)
Updated Readme.md with App Registration instructions. Without the instructions new user will be lost with the instructions provided as none of the menus/options appear.
2021-12-13 23:14:29 +05:30
Balaga Gayatri
25454e5e2d printing token logs (#185) 2021-12-07 00:16:39 +05:30
Balaga Gayatri
0dd02392d7 Update README.md 2021-11-29 13:48:09 +05:30
Balaga Gayatri
aa88e1ccbd Update README.md 2021-11-29 12:35:23 +05:30
Balaga Gayatri
c8bf401503 Update README.md 2021-11-19 19:22:51 +05:30
Balaga Gayatri
37a47ac753 Update README.md (#173) 2021-11-17 12:48:23 +05:30
Balaga Gayatri
298eef0366 Updating samples and readme links (#172) 2021-11-15 17:27:52 +05:30
Balaga Gayatri
4799d94391 Adding federated token logs & optional audience parameter (#159)
* cherry pick changes

* added audience field option in input params

* added js

* removed extra spaces

* Adding logs to surface AZ-CLI and powershell errors (#171)

* removing token logs
2021-11-15 12:45:07 +05:30
Balaga Gayatri
bd43696425 Update README.md 2021-11-12 13:09:38 +05:30
Balaga Gayatri
3e6f6e7d1b Update README.md 2021-10-22 18:46:47 +05:30
Balaga Gayatri
38942de1a5 Update README.md 2021-10-22 18:42:53 +05:30
Balaga Gayatri
151a993723 Update README.md 2021-10-22 16:41:44 +05:30
Balaga Gayatri
fd4c9019e3 oidc changes for master (#158)
* oidc changes

* Update ServicePrinicipalLogin.test.ts

Co-authored-by: Kanika Pasrija <58769601+kanika1894@users.noreply.github.com>
2021-10-22 16:27:01 +05:30
Sean MacKay
6bc1b5ecb9 Update Readme to improve examples for hand crafting credentials (#140)
The clientSecret is not a GUID, and this being labelled as such caused confusion when I tried to manually add a client secret and was met with a SecretId which was a GUID and a SecretValue which was not a GUID. Using the SecretId GUID from the Azure UI would not work.

Also, resourceManagerEndpointUrl was required but not shown in any examples. 

Finally, the value for it was hard to determine, as using several azure cli commands did not display it.

Co-authored-by: Kanika Pasrija <58769601+kanika1894@users.noreply.github.com>
2021-09-20 15:00:25 +05:30
Derek Seroky
f8979b3393 docs(README): update the Azure Gov cloud example (#134)
Updates the Azure Gov cloud example to have valid YAML syntax and to increase consistency between examples.

Co-authored-by: Kanika Pasrija <58769601+kanika1894@users.noreply.github.com>
2021-09-15 12:49:50 +05:30
Kanika Pasrija
7f56506e21 Adding repository parameter
This is to pick from forked branch
2021-09-15 12:39:33 +05:30
dependabot[bot]
0cf07a0171 Bump path-parse from 1.0.6 to 1.0.7 (#128)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kanika Pasrija <58769601+kanika1894@users.noreply.github.com>
2021-09-13 17:04:06 +05:30
Balaga Gayatri
c9dca98403 Workflow for testing automation (#131)
* Create azure-login-pr-check.yml

* Update azure-login-pr-check.yml

* Update azure-login-pr-check.yml

* Update azure-login-pr-check.yml

* Update azure-login-pr-check.yml

* Update azure-login-pr-check.yml

* Update azure-login-pr-check.yml
2021-08-23 13:44:28 +05:30
Balaga Gayatri
177ab1c8df Rename bug-report---feature-request.md to bug-report-feature-request.md (#127) 2021-08-09 14:00:43 +05:30
Kanika Pasrija
5dc2a74a43 Create CODEOWNERS 2021-08-05 13:20:35 +05:30
dependabot[bot]
852ef9dc86 Bump lodash from 4.17.19 to 4.17.21 (#110)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kanika Pasrija <58769601+kanika1894@users.noreply.github.com>
2021-06-23 12:50:49 +05:30
dependabot[bot]
92605228cd Bump ws from 7.2.3 to 7.5.0 (#124)
Bumps [ws](https://github.com/websockets/ws) from 7.2.3 to 7.5.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.3...7.5.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 12:47:38 +05:30
dependabot[bot]
49a928a08c Bump y18n from 4.0.0 to 4.0.1 (#101)
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 12:45:36 +05:30
Anat Balzam
b65105a723 Updating README with workaround for passing login arguments (#106)
* Add login with params workaround to README

* Added additional params line

Co-authored-by: Anat Balzam <anatbalzam@microsoft.com>
Co-authored-by: Balaga Gayatri <balaga-gayatri@github.com>
2021-06-21 17:49:36 +05:30
Ishita Chawla
a0110a77ec Create auto-triage-issues (#120)
* Create auto-triage-issues

* Update auto-triage-issues

* Update auto-triage-issues
2021-06-21 16:19:31 +05:30
Balaga Gayatri
ed5c47a214 Merge pull request #123 from Azure/users/balaga-gayatri/docs
Update README.md
2021-06-21 14:16:55 +05:30
Balaga Gayatri
490d296e28 Merge pull request #121 from Azure/users/balaga-gayatri/docs
Update README.md
2021-06-17 17:11:14 +05:30
21 changed files with 9069 additions and 1950 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
@kaverma @kanika1894 @BALAGA-GAYATRI @pulkitaggarwl

View File

@@ -0,0 +1,94 @@
#This workflow is used to test azure login action for CLI edge build. Visit, https://github.com/Azure/azure-cli#edge-builds for more details.
name: Run Azure Login Canary Test
on:
workflow_dispatch:
schedule:
- cron: ' 0 8 * * *'
permissions:
id-token: write
contents: read
jobs:
az-login-test:
runs-on: ubuntu-latest
steps:
- name : Check Az version before installing
run: az --version
- name: Installing Az CLI Edge build
run: |
cd ../..
CWD="$(pwd)"
python3 -m venv canary-venv
. canary-venv/bin/activate
echo "***********activated virual environment**********"
python3 -m pip install --upgrade pip
echo "***************started installing cli edge build******************"
pip3 install -q --upgrade --pre azure-cli --extra-index-url https://azurecliprod.blob.core.windows.net/edge --no-cache-dir --upgrade-strategy=eager
echo "***************installed cli Edge build*******************"
echo "$CWD/canary-venv/bin" >> $GITHUB_PATH
az --version
- name: Check out repository
uses: actions/checkout@v3
- name: 'Az CLI login with subscription'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- run: |
az account show
- name: 'Az CLI login without subscription'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
allow-no-subscriptions: true
- run: |
az account show
- name: 'Az CLI login with subscription OIDC'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }}
- run: |
az account show
- name: 'Az CLI login without subscription OIDC'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
allow-no-subscriptions: true
- run: |
az account show
slack-post-result:
runs-on: ubuntu-latest
# continue-on-error: true
if: ${{ always() }}
needs: [az-login-test]
steps:
- name: Create slack post
id: slack_report
run: |
TITLE="Login action canary tests update - "
DATEVAR=`date "+%d/%m/%YT%H:%M:%S"`
TITLE="${TITLE}${DATEVAR}"
REPORT="${TITLE}\r\nLink to run - https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\r\n"
RUN_URL="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
REPORT="${REPORT}\r\n"
if [ ${{needs.az-login-test.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test>"; fi
echo "::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}}

View File

@@ -0,0 +1,129 @@
name: Run Azure Login Integration Tests
on:
workflow_dispatch:
schedule:
- cron: '0 */3 * * *'
permissions:
id-token: write
contents: write
jobs:
az-login-test-non-oidc:
runs-on: ubuntu-latest
# continue-on-error: true
steps:
- name: 'Az CLI login with subscription'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- run: |
az account show
# az webapp list
- name: 'Az CLI login without subscription'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
allow-no-subscriptions: true
- run: |
az account show
- name: 'Azure PowerShell login with subscription'
uses: azure/login@v1
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: azure/login@v1
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
enable-AzPSSession: true
allow-no-subscriptions: true
- uses: azure/powershell@v1
with:
inlineScript: "Get-AzContext"
azPSVersion: "latest"
az-login-test-oidc:
runs-on: ubuntu-latest
# continue-on-error: true
steps:
- name: 'Az CLI login with subscription'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }}
- run: |
az account show
# az webapp list
- name: 'Az CLI login without subscription'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
allow-no-subscriptions: true
- run: |
az account show
- name: 'Azure PowerShell login with subscription'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID }}
enable-AzPSSession: true
- uses: azure/powershell@v1
with:
inlineScript: "Get-AzContext"
azPSVersion: "latest"
- name: 'Azure PowerShell login without subscription'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENTID }}
tenant-id: ${{ secrets.AZURE_TENANTID }}
enable-AzPSSession: true
allow-no-subscriptions: true
- uses: azure/powershell@v1
with:
inlineScript: "Get-AzContext"
azPSVersion: "latest"
slack-post-result:
runs-on: ubuntu-latest
# continue-on-error: true
if: ${{ always() }}
needs: [az-login-test-non-oidc, az-login-test-oidc]
steps:
- name: Create slack post
id: slack_report
run: |
TITLE="Login action OIDC flow tests update - "
DATEVAR=`date "+%d/%m/%YT%H:%M:%S"`
TITLE="${TITLE}${DATEVAR}"
REPORT="${TITLE}\r\nLink to run - https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\r\n"
RUN_URL="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
REPORT="${REPORT}\r\n"
if [ ${{needs.az-login-test-non-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-non-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-non-oidc>"; fi
if [ ${{needs.az-login-test-oidc.result}} == 'success' ]; then REPORT="${REPORT}\r\n|✅|<${RUN_URL}|az-login-test-oidc>"; else REPORT="${REPORT}\r\n|❌|<${RUN_URL}|az-login-test-oidc>"; fi
echo "::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}}

View File

@@ -0,0 +1,72 @@
name: pr-check
on:
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@v2
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
# Using 12.x version as an example
- name: Set Node.js 12.x for GitHub Action
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: installing node_modules
run: npm install
- name: Build GitHub Action
run: npm run build
- 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"

297
README.md
View File

@@ -2,27 +2,34 @@
## Automate your GitHub workflows using Azure Actions
[GitHub Actions](https://help.github.com/en/articles/about-github-actions) gives you the flexibility to build an automated software development lifecycle workflow.
[GitHub Actions](https://help.github.com/en/articles/about-github-actions) gives you the flexibility to build an automated software development lifecycle workflow.
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.
NOTE: you must have write permissions to the repository in question. If you're using a sample repository from Microsoft, be sure to first fork the repository to your own GitHub account.
Get started today with a [free Azure account](https://azure.com/free/open-source).
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.
# GitHub Action for Azure Login
With the Azure login Action, you can automate your workflow to do an Azure login using [Azure service principal](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) and run Azure CLI and Azure PowerShell scripts. You can leverage this action for the public or soverign clouds including Azure Government and Azure Stack Hub (using the `environment` parameter).
With the [Azure Login](https://github.com/Azure/login/blob/master/action.yml) Action, you can automate your workflow to do an Azure login using [Azure service principal](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) and run Az CLI and Azure PowerShell scripts.
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.
- 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, set the optional parameter environment with supported cloud names AzureUSGovernment or AzureChinaCloud. If this parameter is not specified, it takes the default value AzureCloud and connect to the Azure Public Cloud. Additionally the parameter creds takes the Azure service principal created in the particular cloud to connect (Refer to Configure deployment credentials section below for details).
- 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 [this](#configure-a-service-principal-with-a-secret) section below for details).
This repository contains GitHub Action for [Azure Login](https://github.com/Azure/login/blob/master/action.yml).
- 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 Service Principal with a Federated Identity Credential.
- 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**,
1. Follow [this](#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication) guidance to create a Federated Credential associated with your AD App (Service Principal). This is needed to establish OIDC trust between GitHub deployment workflows and the specific Azure resources scoped by the 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`, `tenant-id` and `subscription-id` of the Azure service principal associated with an OIDC Federated Identity Credential credeted in step (i)
Note:
- Ensure the CLI version is 2.30 or above to use OIDC support.
- OIDC support in Azure is supported only for public clouds. Support for other clouds like Government clouds, Azure Stacks would be added soon.
- By default, Azure access tokens issued during OIDC based login could have limited validity. This expiration time is configurable in Azure.
## Sample workflow that uses Azure login action to run az cli
```yaml
# File: .github/workflows/workflow.yml
on: [push]
@@ -34,63 +41,122 @@ 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: AzurePowerShellSample
name: AzurePowerShellLoginSample
jobs:
build-and-deploy:
build:
runs-on: ubuntu-latest
steps:
- name: Login via Az module
uses: azure/login@v1
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
enable-AzPSSession: true
- name: Run Az CLI script
run: |
az webapp list --query "[?state=='Running']"
- name: Run Azure PowerShell script
uses: azure/powershell@v1
with:
azPSVersion: '3.1.0'
inlineScript: |
Get-AzVM -ResourceGroupName "ActionsDemo"
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:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- 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: 'Run az commands'
run: |
az account show
az group list
pwd
```
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/OIDC_workflow.yml
name: Run Azure Login with OIDC
on: [push]
permissions:
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: 'Get RG with powershell action'
uses: azure/powershell@v1
with:
inlineScript: |
Get-AzResourceGroup
azPSVersion: "latest"
```
Refer [Azure PowerShell](https://github.com/azure/powershell) Github action to run your Azure PowerShell scripts.
## Sample to connect to Azure US Government cloud
```
- name: Login to Azure US Gov Cloud with CLI
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
environment: 'AzureUSGovernment'
enable-AzPSSession: false
- name: Login to Azure US Gov Cloud with Az Powershell
```yaml
- name: Login to Azure US Gov Cloud with CLI
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
environment: 'AzureUSGovernment'
enable-AzPSSession: true
with:
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:
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
environment: 'AzureUSGovernment'
enable-AzPSSession: true
```
Refer to the [Azure PowerShell](https://github.com/azure/powershell) Github action to run your Azure PowerShell scripts.
@@ -122,91 +188,95 @@ jobs:
Refer to the [Azure Stack Hub Login Action Tutorial](https://docs.microsoft.com/en-us/azure-stack/user/ci-cd-github-action-login-cli?view=azs-2008) for more detailed instructions.
## Configure deployment credentials:
### Configure a service principal with a secret:
The previous sample workflows depend on a [secrets](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) named `AZURE_CREDENTIALS` in your repository. The value of this secret is expected to be a JSON object that represents a service principal (an identifer for an application or process) that authenticates the workflow with Azure.
For using any credentials like Azure Service Principal, Publish Profile etc add them as [secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) in the GitHub repository and then use them in the workflow.
To function correctly, this service principal must be assigned the [Contributor]((https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor)) role for the web app or the resource group that contains the web app.
The following steps describe how to create the service principal, assign the role, and create a secret in your repository with the resulting credentials.
Follow the 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://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest) command as the value of secret variable, for example 'AZURE_CREDENTIALS'
```bash
1. Open the Azure Cloud Shell at [https://shell.azure.com](https://shell.azure.com). You can alternately use the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) if you've installed it locally. (For more information on Cloud Shell, see the [Cloud Shell Overview](https://docs.microsoft.com/azure/cloud-shell/overview).)
az ad sp create-for-rbac --name "myApp" --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
--sdk-auth
# Replace {subscription-id}, {resource-group} with the subscription, resource group details
1.1 **(Required ONLY when environment is Azure Stack Hub)** Run the following command to set the SQL Management endpoint to 'not supported'
```bash
# The command should output a JSON object similar to this:
az cloud update -n {environmentName} --endpoint-sql-management https://notsupported
{
"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/en/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
2. Use the [az ad sp create-for-rbac](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac) command to create a service principal and assign a Contributor role:
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.
For web apps (also more secure)
### Configure a service principal with a Federated Credential to use OIDC based authentication:
```azurecli
az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name}
```
For usage with other Azure services (Storage Accounts, Active Directory, etc.)
You can add federated credentials in the Azure portal or with the Microsoft Graph REST API.
```azurecli
az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}
```
#### Azure portal
1. [Register an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) in Azure Portal
2. Within the registered application, Go to **Certificates & secrets**.
3. In the **Federated credentials** tab, select **Add credential**.
4. The **Add a credential** blade opens.
5. In the **Federated credential scenario** box select **GitHub actions deploying Azure resources**.
6. Specify the **Organization** and **Repository** for your GitHub Actions workflow which needs to access the Azure resources scoped by this App (Service Principal)
7. For **Entity type**, select **Environment**, **Branch**, **Pull request**, or **Tag** and specify the value, based on how you have configured the trigger for your GitHub workflow. For a more detailed overview, see [GitHub OIDC guidance]( https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#defining-[…]dc-claims).
8. Add a **Name** for the federated credential.
9. Click **Add** to configure the federated credential.
10. Make sure the above created application has the `contributor` access to the provided subscription. Visit [role-based-access-control](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current#prerequisites) for more details.
Replace the following:
* `{sp-name}` with a suitable name for your service principal, such as the name of the app itself. The name must be unique within your organization.
* `{subscription-id}` with the subscription ID you want to use (found in Subscriptions in portal)
* `{resource-group}` the resource group containing the web app.
* [optional] `{app-name}` if you wish to have a tighter & more secure scope, use the first option and replace this with the name of the web app.
For a more detailed overview, see more guidance around [Azure Federated Credentials](https://docs.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust-github).
More info can be found [here](https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac).
#### Microsoft Graph
This command invokes Azure Active Directory (via the `ad` part of the command) to create a service principal (via `sp`) specifically for [Role-Based Access Control (RBAC)](https://docs.microsoft.com/azure/role-based-access-control/overview) (via `create-for-rbac`).
1. Launch [Azure Cloud Shell](https://portal.azure.com/#cloudshell/) and sign in to your tenant.
1. Create a federated identity credential
The `--role` argument specifies the permissions to grant to the service principal at the specified `--scope`. In this case, you grant the built-in [Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) role at the scope of the web app in the specified resource group in the specified subscription. If desired, you can omit the part of the scope starting with `/providers/...` to grant the service principal the Contributor role for the entire resource group. For security purposes, however, it's always preferable to grant permissions at the most restrictive scope possible.
3. When complete, the `az ad sp create-for-rbac` command displays JSON output in the following form (which is specified by the `--sdk-auth` argument):
```json
{
"clientId": "<GUID>",
"clientSecret": "<GUID>",
"subscriptionId": "<GUID>",
"tenantId": "<GUID>",
(...)
}
```
4. In your repository, use **Add secret** to create a new secret named `AZURE_CREDENTIALS` (as shown in the example workflow), or using whatever name is in your workflow file.
NOTE: While adding secret `AZURE_CREDENTIALS` make sure to add like this
{"clientId": "<GUID>",
"clientSecret": "<GUID>",
"subscriptionId": "<GUID>",
"tenantId": "<GUID>",
(...)}
instead of
{
"clientId": "<GUID>",
"clientSecret": "<GUID>",
"subscriptionId": "<GUID>",
"tenantId": "<GUID>",
(...)
}
to prevent unnecessary masking of `{ } ` in your logs which are in dictionary form.
5. Paste the entire JSON object produced by the `az ad sp create-for-rbac` command as the secret value and save the secret.
NOTE: to manage service principals created with `az ad sp create-for-rbac`, visit the [Azure portal](https://portal.azure.com), navigate to your Azure Active Directory, then select **Manage** > **App registrations** on the left-hand menu. Your service principal should appear in the list. Select a principal to navigate to its properties. You can also manage role assignments using the [az role assignment](https://docs.microsoft.com/cli/azure/role/assignment?view=azure-cli-latest) command.
Run the following command to [create a new federated identity credential](https://docs.microsoft.com/en-us/graph/api/application-post-federatedidentitycredentials?view=graph-rest-beta&preserve-view=true) on your app (specified by the object ID of the app). Substitute the values `APPLICATION-OBJECT-ID`, `CREDENTIAL-NAME`, `SUBJECT`. The options for subject refer to your request filter. These are the conditions that OpenID Connect uses to determine when to issue an authentication token.
* specific environment
```azurecli
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:octo-org/octo-repo:environment:Production","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
```
* pull_request events
```azurecli
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:octo-org/octo-repo:pull_request","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
```
* specific branch
```azurecli
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:octo-org/octo-repo:ref:refs/heads/{Branch}","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
```
* specific tag
```azurecli
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:octo-org/octo-repo:ref:refs/heads/{Tag}","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
```
## Support for using `allow-no-subscriptions` flag with az login
Capability has been added to support access to tenants without subscriptions. 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.
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.
```yaml
# File: .github/workflows/workflow.yml
@@ -233,18 +303,23 @@ This action doesn't implement ```az logout``` by default at the end of execution
- name: Azure CLI script
uses: azure/CLI@v1
with:
azcliversion: 2.0.72
inlineScript: |
az logout
az cache purge
az account clear
```
## Az 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 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
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
For detailed developer guidelines, visit [developer guidelines for azure actions](https://github.com/Azure/actions/blob/main/docs/developer-guildelines.md).
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -5,7 +5,7 @@ jest.mock('../../src/PowerShell/Utilities/PowerShellToolRunner');
let spnlogin: ServicePrincipalLogin;
beforeAll(() => {
spnlogin = new ServicePrincipalLogin("servicePrincipalID", "servicePrinicipalkey", "tenantId", "subscriptionId", false, null, null);
spnlogin = new ServicePrincipalLogin("servicePrincipalID", "servicePrinicipalkey", null, "tenantId", "subscriptionId", false, null, null);
});
afterEach(() => {

View File

@@ -4,7 +4,16 @@ description: 'AuthenticatetoAzureandrunyourAzCLIorAz
inputs:
creds:
description: 'Paste output of `az ad sp create-for-rbac` as value of secret variable: AZURE_CREDENTIALS'
required: true
required: false
client-id:
description: 'ClientId of the Azure Service principal created.'
required: false
tenant-id:
description: 'TenantId of the Azure Service principal created.'
required: false
subscription-id:
description: 'Azure subscriptionId'
required: false
enable-AzPSSession:
description: 'SetthisvaluetotruetoenableAzurePowerShellLogininadditiontoAzCLIlogin'
required: false
@@ -12,14 +21,18 @@ inputs:
environment:
description: 'Name of the environment. Supported values are azurecloud, azurestack, azureusgovernment, azurechinacloud, azuregermancloud. Default being azurecloud'
required: false
default: AzureCloud
default: azurecloud
allow-no-subscriptions:
description: 'Setthisvaluetotrueto enable support for accessing tenants without subscriptions'
required: false
default: false
audience:
description: 'Provide audience field for access-token. Default value is api://AzureADTokenExchange'
required: false
default: 'api://AzureADTokenExchange'
branding:
icon: 'login.svg'
color: 'blue'
runs:
using: 'node12'
main: 'lib/main.js'
using: 'node16'
main: 'lib/main.js'

View File

@@ -5,7 +5,7 @@ class Constants {
exports.default = Constants;
Constants.prefix = "az_";
Constants.moduleName = "Az.Accounts";
Constants.versionPattern = /[0-9]\.[0-9]\.[0-9]/;
Constants.versionPattern = /[0-9]+\.[0-9]+\.[0-9]+/;
Constants.AzureCloud = "AzureCloud";
Constants.Subscription = "Subscription";
Constants.ServicePrincipal = "ServicePrincipal";

View File

@@ -1,4 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
@@ -8,26 +27,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServicePrincipalLogin = void 0;
const core = __importStar(require("@actions/core"));
const Utils_1 = __importDefault(require("./Utilities/Utils"));
const PowerShellToolRunner_1 = __importDefault(require("./Utilities/PowerShellToolRunner"));
const ScriptBuilder_1 = __importDefault(require("./Utilities/ScriptBuilder"));
const Constants_1 = __importDefault(require("./Constants"));
class ServicePrincipalLogin {
constructor(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl) {
constructor(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl) {
this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey;
this.federatedToken = federatedToken;
this.tenantId = tenantId;
this.subscriptionId = subscriptionId;
this.environment = environment;
@@ -45,16 +59,25 @@ class ServicePrincipalLogin {
login() {
return __awaiter(this, void 0, void 0, function* () {
let output = "";
let commandStdErr = false;
const options = {
listeners: {
stdout: (data) => {
output += data.toString();
},
stderr: (data) => {
let error = data.toString();
if (error && error.trim().length !== 0) {
commandStdErr = true;
core.error(error);
}
}
}
};
const args = {
servicePrincipalId: this.servicePrincipalId,
servicePrincipalKey: this.servicePrincipalKey,
federatedToken: this.federatedToken,
subscriptionId: this.subscriptionId,
environment: this.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel,

View File

@@ -1,4 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
@@ -8,13 +27,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const io = __importStar(require("@actions/io"));
const exec = __importStar(require("@actions/exec"));
@@ -28,6 +40,7 @@ class PowerShellToolRunner {
}
static executePowerShellScriptBlock(scriptBlock, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
//Options for error handling
yield exec.exec(`"${PowerShellToolRunner.psPath}" -Command`, [scriptBlock], options);
});
}

View File

@@ -1,9 +1,21 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
@@ -23,9 +35,17 @@ class ScriptBuilder {
if (args.environment.toLowerCase() == "azurestack") {
command += `Add-AzEnvironment -Name ${args.environment} -ARMEndpoint ${args.resourceManagerEndpointUrl} | out-null;`;
}
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;`;
// 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_1.default.Subscription && !args.allowNoSubscriptionsLogin) {
command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`;
}

View File

@@ -1,4 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
@@ -8,13 +27,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};

View File

@@ -1,4 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
@@ -8,13 +27,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
@@ -27,6 +39,25 @@ var azPSHostEnv = !!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREP
function main() {
return __awaiter(this, void 0, void 0, function* () {
try {
//Options for error handling
const loginOptions = {
silent: true,
listeners: {
stderr: (data) => {
let error = data.toString();
let startsWithWarning = error.toLowerCase().startsWith('warning');
let startsWithError = error.toLowerCase().startsWith('error');
// printing ERROR
if (error && error.trim().length !== 0 && !startsWithWarning) {
if (startsWithError) {
//removing the keyword 'ERROR' to avoid duplicates while throwing error
error = error.slice(5);
}
core.setFailed(error);
}
}
}
};
// Set user agent variable
var isAzCLISuccess = false;
let usrAgentRepo = `${process.env.GITHUB_REPOSITORY}`;
@@ -36,6 +67,7 @@ function main() {
core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv);
azPath = yield io.which("az", true);
core.debug(`az cli version used: ${azPath}`);
let azureSupportedCloudName = new Set([
"azureusgovernment",
"azurechinacloud",
@@ -53,25 +85,68 @@ function main() {
};
yield executeAzCliCommand("--version", true, execOptions);
core.debug(`az cli version used:\n${output}`);
let creds = core.getInput('creds', { required: true });
let secrets = new actions_secret_parser_1.SecretParser(creds, actions_secret_parser_1.FormatType.JSON);
let servicePrincipalId = secrets.getSecret("$.clientId", false);
let servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
let tenantId = secrets.getSecret("$.tenantId", false);
let subscriptionId = secrets.getSecret("$.subscriptionId", false);
let resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
let creds = core.getInput('creds', { required: false });
let secrets = creds ? new actions_secret_parser_1.SecretParser(creds, actions_secret_parser_1.FormatType.JSON) : null;
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
if (!servicePrincipalId || !servicePrincipalKey || !tenantId) {
throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret and tenantId are supplied.");
//Check for the credentials in individual parameters in the workflow.
var servicePrincipalId = core.getInput('client-id', { required: false });
var servicePrincipalKey = null;
var tenantId = core.getInput('tenant-id', { required: false });
var subscriptionId = core.getInput('subscription-id', { required: false });
var resourceManagerEndpointUrl = "https://management.azure.com/";
var enableOIDC = true;
var federatedToken = null;
// If any of the individual credentials (clent_id, tenat_id, subscription_id) is present.
if (servicePrincipalId || tenantId || subscriptionId) {
//If few of the individual credentials (clent_id, tenat_id, subscription_id) are missing in action inputs.
if (!(servicePrincipalId && tenantId && (subscriptionId || allowNoSubscriptionsLogin)))
throw new Error("Few credentials are missing. ClientId, tenantId are mandatory. SubscriptionId is also mandatory if allow-no-subscriptions is not set.");
}
else {
if (creds) {
core.debug('using creds JSON...');
enableOIDC = false;
servicePrincipalId = secrets.getSecret("$.clientId", true);
servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
tenantId = secrets.getSecret("$.tenantId", true);
subscriptionId = secrets.getSecret("$.subscriptionId", true);
resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
}
else {
throw new Error("Credentials are not passed for Login action.");
}
}
//generic checks
//servicePrincipalKey is only required in non-oidc scenario.
if (!servicePrincipalId || !tenantId || !(servicePrincipalKey || enableOIDC)) {
throw new Error("Not all values are present in the credentials. Ensure clientId, clientSecret and tenantId are supplied.");
}
if (!subscriptionId && !allowNoSubscriptionsLogin) {
throw new Error("Not all values are present in the creds object. Ensure subscriptionId is supplied.");
throw new Error("Not all values are present in the credentials. Ensure subscriptionId is supplied.");
}
if (!azureSupportedCloudName.has(environment)) {
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are azureusgovernment', azurechinacloud, azuregermancloud, azurecloud or azurestack");
}
// OIDC specific checks
if (enableOIDC) {
console.log('Using OIDC authentication...');
try {
//generating ID-token
let audience = core.getInput('audience', { required: false });
federatedToken = yield core.getIDToken(audience);
if (!!federatedToken) {
if (environment != "azurecloud")
throw new Error(`Your current environment - "${environment}" is not supported for OIDC login.`);
let [issuer, subjectClaim] = yield jwtParser(federatedToken);
console.log("Federated token details: \n issuer - " + issuer + " \n subject claim - " + subjectClaim);
}
}
catch (error) {
core.error(`${error.message.split(':')[1]}. Please make sure to give write permissions to id-token in the workflow.`);
}
}
// Attempting Az cli login
if (environment == "azurestack") {
if (!resourceManagerEndpointUrl) {
@@ -104,35 +179,34 @@ function main() {
yield executeAzCliCommand(`cloud set -n "${environment}"`, false);
console.log(`Done setting cloud: "${environment}"`);
// Attempting Az cli login
var commonArgs = ["--service-principal",
"-u", servicePrincipalId,
"--tenant", tenantId
];
if (allowNoSubscriptionsLogin) {
let args = [
"--allow-no-subscriptions",
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
yield executeAzCliCommand(`login`, true, {}, args);
commonArgs = commonArgs.concat("--allow-no-subscriptions");
}
if (enableOIDC) {
commonArgs = commonArgs.concat("--federated-token", federatedToken);
}
else {
let args = [
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
yield executeAzCliCommand(`login`, true, {}, args);
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("-p", servicePrincipalKey);
}
yield executeAzCliCommand(`login`, true, loginOptions, commonArgs);
if (!allowNoSubscriptionsLogin) {
var args = [
"--subscription",
subscriptionId
];
yield executeAzCliCommand(`account set`, true, {}, args);
yield executeAzCliCommand(`account set`, true, loginOptions, args);
}
isAzCLISuccess = true;
if (enableAzPSSession) {
// Attempting Az PS login
console.log(`Running Azure PS Login`);
const spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl);
var spnlogin;
spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl);
yield spnlogin.initialize();
yield spnlogin.login();
}
@@ -140,12 +214,11 @@ function main() {
}
catch (error) {
if (!isAzCLISuccess) {
core.error("Az CLI Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows");
core.setFailed("Az CLI Login failed. 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.error(`Azure PowerShell Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`);
core.setFailed(`Azure PowerShell Login failed. 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"`);
}
core.setFailed(error);
}
finally {
// Reset AZURE_HTTP_USER_AGENT
@@ -157,12 +230,15 @@ function main() {
function executeAzCliCommand(command, silent, execOptions = {}, args = []) {
return __awaiter(this, void 0, void 0, function* () {
execOptions.silent = !!silent;
try {
yield exec.exec(`"${azPath}" ${command}`, args, execOptions);
}
catch (error) {
throw new Error(error);
}
yield exec.exec(`"${azPath}" ${command}`, args, execOptions);
});
}
function jwtParser(federatedToken) {
return __awaiter(this, void 0, void 0, function* () {
let tokenPayload = federatedToken.split('.')[1];
let bufferObj = Buffer.from(tokenPayload, "base64");
let decodedPayload = JSON.parse(bufferObj.toString("utf8"));
return [decodedPayload['iss'], decodedPayload['sub']];
});
}
main();

9647
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "login",
"version": "1.1.0",
"version": "1.0.0",
"description": "Login Azure wraps the az login, allowing for Azure actions to log into Azure",
"main": "lib/main.js",
"scripts": {
@@ -18,9 +18,10 @@
"typescript": "^3.6.3"
},
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/core": "1.6.0",
"@actions/exec": "^1.0.1",
"@actions/io": "^1.0.1",
"actions-secret-parser": "^1.0.2"
"actions-secret-parser": "^1.0.2",
"package-lock": "^1.0.3"
}
}

View File

@@ -1,7 +1,7 @@
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 versionPattern = /[0-9]+\.[0-9]+\.[0-9]+/;
static readonly AzureCloud: string = "AzureCloud";
static readonly Subscription: string = "Subscription";
@@ -10,4 +10,4 @@ export default class Constants {
static readonly Success: string = "Success";
static readonly Error: string = "Error";
static readonly AzVersion: string = "AzVersion";
}
}

View File

@@ -15,17 +15,20 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
subscriptionId: string;
resourceManagerEndpointUrl: string;
allowNoSubscriptionsLogin: boolean;
federatedToken: string;
constructor(servicePrincipalId: string,
servicePrincipalKey: string,
tenantId: string,
constructor(servicePrincipalId: string,
servicePrincipalKey: string,
federatedToken: string,
tenantId: string,
subscriptionId: string,
allowNoSubscriptionsLogin: boolean,
environment: string,
resourceManagerEndpointUrl: string) {
this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey;
this.federatedToken = federatedToken;
this.tenantId = tenantId;
this.subscriptionId = subscriptionId;
this.environment = environment;
@@ -42,16 +45,26 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
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.servicePrincipalId,
servicePrincipalKey: this.servicePrincipalKey,
federatedToken: this.federatedToken,
subscriptionId: this.subscriptionId,
environment: this.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel,

View File

@@ -3,7 +3,6 @@ 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);
@@ -11,6 +10,7 @@ export default class PowerShellToolRunner {
}
static async executePowerShellScriptBlock(scriptBlock: string, options: any = {}) {
//Options for error handling
await exec.exec(`"${PowerShellToolRunner.psPath}" -Command`, [scriptBlock], options)
}
}

View File

@@ -8,17 +8,28 @@ export default class ScriptBuilder {
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;`;
}
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;`;
// 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"
@@ -30,6 +41,7 @@ export default class ScriptBuilder {
$output['${Constants.Error}'] = $_.exception.Message
}
return ConvertTo-Json $output`;
core.debug(`Azure PowerShell Login Script: ${this.script}`);
return this.script;
}
@@ -51,4 +63,5 @@ export default class ScriptBuilder {
core.debug(`GetLatestModuleScript: ${this.script}`);
return this.script;
}
}
}

View File

@@ -1,176 +1,239 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import { FormatType, SecretParser } from 'actions-secret-parser';
import { ServicePrincipalLogin } from './PowerShell/ServicePrincipalLogin';
var azPath: string;
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}` : "";
async function main() {
try {
// Set user agent variable
var isAzCLISuccess = false;
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);
azPath = await io.which("az", true);
let azureSupportedCloudName = new Set([
"azureusgovernment",
"azurechinacloud",
"azuregermancloud",
"azurecloud",
"azurestack"]);
let output: string = "";
const execOptions: any = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
};
await executeAzCliCommand("--version", true, execOptions);
core.debug(`az cli version used:\n${output}`);
let creds = core.getInput('creds', { required: true });
let secrets = new SecretParser(creds, FormatType.JSON);
let servicePrincipalId = secrets.getSecret("$.clientId", false);
let servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
let tenantId = secrets.getSecret("$.tenantId", false);
let subscriptionId = secrets.getSecret("$.subscriptionId", false);
let resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
if (!servicePrincipalId || !servicePrincipalKey || !tenantId) {
throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret and tenantId are supplied.");
}
if (!subscriptionId && !allowNoSubscriptionsLogin) {
throw new Error("Not all values are present in the creds object. Ensure subscriptionId is supplied.");
}
if (!azureSupportedCloudName.has(environment)){
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are azureusgovernment', azurechinacloud, azuregermancloud, azurecloud or azurestack");
}
// Attempting Az cli login
if (environment == "azurestack") {
if (!resourceManagerEndpointUrl) {
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
}
console.log(`Unregistering cloud: "${environment}" first if it exists`);
try {
await executeAzCliCommand(`cloud set -n AzureCloud`, true);
await executeAzCliCommand(`cloud unregister -n "${environment}"`, false);
}
catch (error) {
console.log(`Ignore cloud not registered error: "${error}"`);
}
console.log(`Registering cloud: "${environment}" with ARM endpoint: "${resourceManagerEndpointUrl}"`);
try {
let baseUri = resourceManagerEndpointUrl;
if (baseUri.endsWith('/')) {
baseUri = baseUri.substring(0, baseUri.length-1); // need to remove trailing / from resourceManagerEndpointUrl to correctly derive suffixes below
}
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 executeAzCliCommand(`cloud register -n "${environment}" --endpoint-resource-manager "${resourceManagerEndpointUrl}" --suffix-keyvault-dns "${suffixKeyvault}" --suffix-storage-endpoint "${suffixStorage}" --profile "${profileVersion}"`, false);
}
catch (error) {
core.error(`Error while trying to register cloud "${environment}": "${error}"`);
}
console.log(`Done registering cloud: "${environment}"`)
}
await executeAzCliCommand(`cloud set -n "${environment}"`, false);
console.log(`Done setting cloud: "${environment}"`);
// Attempting Az cli login
if (allowNoSubscriptionsLogin) {
let args = [
"--allow-no-subscriptions",
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
await executeAzCliCommand(`login`, true, {}, args);
}
else {
let args = [
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
await executeAzCliCommand(`login`, true, {}, args);
args = [
"--subscription",
subscriptionId
];
await executeAzCliCommand(`account set`, true, {}, args);
}
isAzCLISuccess = true;
if (enableAzPSSession) {
// Attempting Az PS login
console.log(`Running Azure PS Login`);
const spnlogin: ServicePrincipalLogin = new ServicePrincipalLogin(
servicePrincipalId,
servicePrincipalKey,
tenantId,
subscriptionId,
allowNoSubscriptionsLogin,
environment,
resourceManagerEndpointUrl);
await spnlogin.initialize();
await spnlogin.login();
}
console.log("Login successful.");
}
catch (error) {
if (!isAzCLISuccess) {
core.error("Az CLI Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows");
}
else {
core.error(`Azure PowerShell Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`);
}
core.setFailed(error);
}
finally {
// Reset AZURE_HTTP_USER_AGENT
core.exportVariable('AZURE_HTTP_USER_AGENT', prefix);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azPSHostEnv);
}
}
async function executeAzCliCommand(
command: string,
silent?: boolean,
execOptions: any = {},
args: any = []) {
execOptions.silent = !!silent;
try {
await exec.exec(`"${azPath}" ${command}`, args, execOptions);
}
catch (error) {
throw new Error(error);
}
}
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import { ExecOptions } from '@actions/exec/lib/interfaces';
import * as io from '@actions/io';
import { FormatType, SecretParser } from 'actions-secret-parser';
import { ServicePrincipalLogin } from './PowerShell/ServicePrincipalLogin';
var azPath: string;
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}` : "";
async function main() {
try {
//Options for error handling
const loginOptions: ExecOptions = {
silent: true,
listeners: {
stderr: (data: Buffer) => {
let error = data.toString();
let startsWithWarning = error.toLowerCase().startsWith('warning');
let startsWithError = error.toLowerCase().startsWith('error');
// printing ERROR
if (error && error.trim().length !== 0 && !startsWithWarning) {
if(startsWithError) {
//removing the keyword 'ERROR' to avoid duplicates while throwing error
error = error.slice(5);
}
core.setFailed(error);
}
}
}
}
// Set user agent variable
var isAzCLISuccess = false;
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);
azPath = await io.which("az", true);
core.debug(`az cli version used: ${azPath}`);
let azureSupportedCloudName = new Set([
"azureusgovernment",
"azurechinacloud",
"azuregermancloud",
"azurecloud",
"azurestack"]);
let output: string = "";
const execOptions: any = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
};
await executeAzCliCommand("--version", true, execOptions);
core.debug(`az cli version used:\n${output}`);
let creds = core.getInput('creds', { required: false });
let secrets = creds ? new SecretParser(creds, FormatType.JSON) : null;
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
//Check for the credentials in individual parameters in the workflow.
var servicePrincipalId = core.getInput('client-id', { required: false });
var servicePrincipalKey = null;
var tenantId = core.getInput('tenant-id', { required: false });
var subscriptionId = core.getInput('subscription-id', { required: false });
var resourceManagerEndpointUrl = "https://management.azure.com/";
var enableOIDC = true;
var federatedToken = null;
// If any of the individual credentials (clent_id, tenat_id, subscription_id) is present.
if (servicePrincipalId || tenantId || subscriptionId) {
//If few of the individual credentials (clent_id, tenat_id, subscription_id) are missing in action inputs.
if (!(servicePrincipalId && tenantId && (subscriptionId || allowNoSubscriptionsLogin)))
throw new Error("Few credentials are missing. ClientId, tenantId are mandatory. SubscriptionId is also mandatory if allow-no-subscriptions is not set.");
}
else {
if (creds) {
core.debug('using creds JSON...');
enableOIDC = false;
servicePrincipalId = secrets.getSecret("$.clientId", true);
servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
tenantId = secrets.getSecret("$.tenantId", true);
subscriptionId = secrets.getSecret("$.subscriptionId", true);
resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
}
else {
throw new Error("Credentials are not passed for Login action.");
}
}
//generic checks
//servicePrincipalKey is only required in non-oidc scenario.
if (!servicePrincipalId || !tenantId || !(servicePrincipalKey || enableOIDC)) {
throw new Error("Not all values are present in the credentials. Ensure clientId, clientSecret and tenantId are supplied.");
}
if (!subscriptionId && !allowNoSubscriptionsLogin) {
throw new Error("Not all values are present in the credentials. Ensure subscriptionId is supplied.");
}
if (!azureSupportedCloudName.has(environment)) {
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are azureusgovernment', azurechinacloud, azuregermancloud, azurecloud or azurestack");
}
// OIDC specific checks
if (enableOIDC) {
console.log('Using OIDC authentication...')
try {
//generating ID-token
let audience = core.getInput('audience', { required: false });
federatedToken = await core.getIDToken(audience);
if (!!federatedToken) {
if (environment != "azurecloud")
throw new Error(`Your current environment - "${environment}" is not supported for OIDC login.`);
let [issuer, subjectClaim] = await jwtParser(federatedToken);
console.log("Federated token details: \n issuer - " + issuer + " \n subject claim - " + subjectClaim);
}
}
catch (error) {
core.error(`${error.message.split(':')[1]}. Please make sure to give write permissions to id-token in the workflow.`);
}
}
// Attempting Az cli login
if (environment == "azurestack") {
if (!resourceManagerEndpointUrl) {
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
}
console.log(`Unregistering cloud: "${environment}" first if it exists`);
try {
await executeAzCliCommand(`cloud set -n AzureCloud`, true);
await executeAzCliCommand(`cloud unregister -n "${environment}"`, false);
}
catch (error) {
console.log(`Ignore cloud not registered error: "${error}"`);
}
console.log(`Registering cloud: "${environment}" with ARM endpoint: "${resourceManagerEndpointUrl}"`);
try {
let baseUri = resourceManagerEndpointUrl;
if (baseUri.endsWith('/')) {
baseUri = baseUri.substring(0, baseUri.length - 1); // need to remove trailing / from resourceManagerEndpointUrl to correctly derive suffixes below
}
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 executeAzCliCommand(`cloud register -n "${environment}" --endpoint-resource-manager "${resourceManagerEndpointUrl}" --suffix-keyvault-dns "${suffixKeyvault}" --suffix-storage-endpoint "${suffixStorage}" --profile "${profileVersion}"`, false);
}
catch (error) {
core.error(`Error while trying to register cloud "${environment}": "${error}"`);
}
console.log(`Done registering cloud: "${environment}"`)
}
await executeAzCliCommand(`cloud set -n "${environment}"`, false);
console.log(`Done setting cloud: "${environment}"`);
// Attempting Az cli login
var commonArgs = ["--service-principal",
"-u", servicePrincipalId,
"--tenant", tenantId
];
if (allowNoSubscriptionsLogin) {
commonArgs = commonArgs.concat("--allow-no-subscriptions");
}
if (enableOIDC) {
commonArgs = commonArgs.concat("--federated-token", federatedToken);
}
else {
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("-p", servicePrincipalKey);
}
await executeAzCliCommand(`login`, true, loginOptions, commonArgs);
if (!allowNoSubscriptionsLogin) {
var args = [
"--subscription",
subscriptionId
];
await executeAzCliCommand(`account set`, true, loginOptions, args);
}
isAzCLISuccess = true;
if (enableAzPSSession) {
// Attempting Az PS login
console.log(`Running Azure PS Login`);
var spnlogin: ServicePrincipalLogin;
spnlogin = new ServicePrincipalLogin(
servicePrincipalId,
servicePrincipalKey,
federatedToken,
tenantId,
subscriptionId,
allowNoSubscriptionsLogin,
environment,
resourceManagerEndpointUrl);
await spnlogin.initialize();
await spnlogin.login();
}
console.log("Login successful.");
}
catch (error) {
if (!isAzCLISuccess) {
core.setFailed("Az CLI Login failed. 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. 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);
}
}
async function executeAzCliCommand(
command: string,
silent?: boolean,
execOptions: any = {},
args: any = []) {
execOptions.silent = !!silent;
await exec.exec(`"${azPath}" ${command}`, args, execOptions);
}
async function jwtParser(federatedToken: string) {
let tokenPayload = federatedToken.split('.')[1];
let bufferObj = Buffer.from(tokenPayload, "base64");
let decodedPayload = JSON.parse(bufferObj.toString("utf8"));
return [decodedPayload['iss'], decodedPayload['sub']];
}
main();