I decided to document some cool things about Azure AD Federated Credentials for future use:
- How to create credentials with script and action template (YAML) for GitHub using Azure CLI
- Example code snippet for node.js (btw… You don’t need to use node if you just want to test the full example)
- It is worth mentioning that there exists already some great documentation (mentioned in references) which I used as background to do the testing in this blog.
Use case?
The first thing mentioned here should be the use-case: Use case is establishing trust to another identity provider for non-user identities using OAuth2. The benefits of such flows are, that you don’t need to maintain credentials in Azure AD, (no public key or password for Client Credential based flows) – Obviously you need in case of GitHub to ensure that the repo and actions are secured properly, as the credential now lives in GitHub.
Background
if you want to have overall view check MS Documentation
What OAuth2 flow?
The flow is technically same to the Azure AD Client Credentials flow with certificate using(urn:ietf:params:oauth:client-assertion-type:jwt-bearer
) which I’ve written extensively about here
Differences compared to Azure AD Client Credentials flow with certificate?
- No public key is uploaded to Azure AD, as the Azure AD knows where to look the metadata and likely then a fetches it from JWKS URI https://token.actions.githubusercontent.com/.well-known/openid-configuration (this means, that the credential type under app registration is also different and shown under federated credentials)
- The claims are different vs the claims given in Client Credential for certificate (in the requesting token)
- Subject claim is used to match the repo and branch declaration
- Correct token for github/jsa2/idtoken using main would be
repo:jsa2/idtoken:ref:refs/heads/main
- Correct token for github/jsa2/idtoken using main would be
For reference: Client Credentials with certificate flow
Example to view the token in pipeline
Place this file into following directory .github\workflows
structure and push to the repo
- This example consists YAML definition shown in the references part
token.yaml
name: OIDC-test
on:
push:
branches:
- '*'
jobs:
auth:
# Add "id-token" with the intended permissions.
permissions:
contents: 'read'
id-token: 'write'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Dump env
run: env
- name: Post the token
run: |
OIDC_TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=api://AzureADTokenExchange" -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN"| jq -r '.value')
TOKEN=$(echo $OIDC_TOKEN | sed 's/./& /g')
jwtd() {
if [[ -x $(command -v jq) ]]; then
jq -R 'split(".") | .[0],.[1] | @base64d | fromjson' <<< "${1}"
echo "Signature: $(echo "${1}" | awk -F'.' '{print $3}')"
fi
}
jwtd $OIDC_TOKEN
echo "::set-output name=idToken::${OIDC_TOKEN}"
Guide for workload federation
If you want to have GUI version, refer to the excellent guide on MS docs federated credentials
Prerequisites
- Azure Cloud Shell (bash) (or local shell with deps installed)
Create the credential for Azure AD and workflow file
GHWF=GH-account-$RANDOM
REPO=repo:jsa2/idtoken:ref:refs/heads/main
SUBID="3539c2a2-cd25-48c6-b295-14e59334ef1c"
CLIENTCREDENTIALS=$(az ad app create --display-name $GHWF \
-o tsv --query "objectId")
APPID=$(az ad sp create --id $CLIENTCREDENTIALS -o tsv --query "appId")
SPNOID=$(az ad sp show --id $APPID -o tsv --query "objectId")
az role assignment create --role reader --scope "/subscriptions/$SUBID" --assignee-object-id $SPNOID --assignee-principal-type ServicePrincipal
# az ad app delete --id $CLIENTCREDENTIALS
az rest --method POST --uri "https://graph.microsoft.com/beta/applications/$CLIENTCREDENTIALS/federatedIdentityCredentials" \
--body "{\"name\":\"$GHWF\",
\"issuer\":\"https://token.actions.githubusercontent.com\",
\"subject\":\"$REPO\"
,\"description\":\"Testing\",
\"audiences\":[\"api://AzureADTokenExchange\"]}"
Tid=$(az account show -o tsv --query "homeTenantId" )
# No actual secrets are in the file. You can reference ClientID etc as secrets, but you should not assume this details private
echo "name: Run Azure Login with OpenID Connect
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: $APPID
tenant-id: $Tid
subscription-id: $SUBID
- name: 'Run Azure CLI commands'
run: |
az account show
az group list
pwd " > .github/workflows/workflow2.yaml
- Push the repo to github
- This example produces the following result
Node.js snippet for Azure Login using workload identity
- For complete example ensure you have deps installed and initiate the node.js from workflo
// Example based actions/core documentation
const core = require('@actions/core');
const { decode } = require('jsonwebtoken');
const { axiosClient } = require('./axioshelp');
getIDTokenAction().catch((error) =>{
console.log('errors?',error)
})
async function getIDTokenAction() {
const audience = core.getInput('audience', {required: false}) || "api://AzureADTokenExchange"
console.log('audience',audience)
const id_token2 = await core.getIDToken(audience).catch((error) => {
console.log('erros',error)})
console.log(decode(id_token2, {complete:true}))
var bearer = {
url: 'https://login.microsoftonline.com/033794f5-7c9d-4e98-923d-7b49114b7ac3/oauth2/v2.0/token',
json: true,
data: {
grant_type:"client_credentials",
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_id: '01cd25b5-4557-49c3-a1a2-b5d01bbb2c8b',
client_assertion: id_token2,
scope: 'https://graph.microsoft.com/.default',
}
}
var {data} = await axiosClient(bearer, true).catch((error) => {
console.log(error?.response?.data)
})
console.log('aad response',data)
}
- This example produces the following result
Workflow for node.js
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
auth:
# Add "id-token" with the intended permissions.
permissions:
contents: 'read'
id-token: 'write'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Post the token
run: |
node token.js
References
There was some excellent information available when testing this solution, which was used to create the examples for Azure AD
0 comments on “Deep diver – Azure AD Federated Credentials”