AAD github

Deep diver – Azure AD Federated Credentials

I decided to document some cool things about Azure AD Federated Credentials for future use:

  1. How to create credentials with script and action template (YAML) for GitHub using Azure CLI
  2. 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

Configure a GitHub Actions workflow to exchange a token from GitHub for an access token from Microsoft identity platform and access Azure AD protected resources without needing to manage secrets

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?

  • 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

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

Jätä kommentti