Azure Citadel
  • Blogs

  • Azure Arc
    • Overview
    • Azure Arc-enabled Kubernetes
      • Prereqs
      • Background
      • Deploy Cluster
      • Connect to Arc
      • Enable GitOps
      • Deploy Application
      • Enable Azure AD
      • Enforce Policy
      • Enable Monitoring
      • Enable Azure Defender
      • Enable Data Services
      • Enable Application Delivery
    • Azure Arc-enabled Servers
      • Prereqs
      • Scenario
      • Hack Overview
      • Azure Landing Zone
      • Arc Pilot resource group
      • Azure Monitoring Agent
      • Additional policy assignments
      • Access your on prem VMs
      • Create onboarding scripts
      • Onboarding using scripts
      • Inventory
      • Monitoring
      • SSH
      • Windows Admin Center
      • Governance
      • Custom Script Extension
      • Key Vault Extension
      • Managed Identity
    • Useful Links
  • Azure CLI
    • Install
    • Get started
    • JMESPATH queries
    • Integrate with Bash
  • Azure landing zone
    • Overview
    • Run the ALZ Accelerator
      • Prereqs
      • Elevate
      • Bootstrap
      • Demote
      • Browse the deployed resources
    • Deploy an Azure landing zone
      • What is the Azure landing zone?
      • Create an initial ALZ config
      • Add a local override library
      • Test locally
      • Run through the CI/CD workflow
    • Understanding libraries
      • What is a library?
      • Policies, Assignments and Roles
      • Archetypes, Overrides and Architecture
      • Metadata and Policy Default Values
      • Custom libraries
    • Reference configs
      • Azure landing zone library
      • Azure landing zone library with overrides
  • Azure Lighthouse
    • Minimal Lighthouse definition
    • Using service principals
    • Privileged Identity Management
  • Azure Policy
    • Azure Policy Basics
      • Policy Basics in the Azure Portal
      • Creating Policy via the CLI
      • Deploy If Not Exists
      • Management Groups and Initiatives
    • Creating Custom Policies
      • Customer scenario
      • Policy Aliases
      • Determine the logic
      • Create the custom policy
      • Define, assign and test
  • Customer Managed Keys
    • Sovereignty scenarios
    • Key management options
    • 🧪 Azure Key Vault Premium
    • L2: Encryption at rest with CMK
    • 🧪 CMK for Storage
    • 🧪 CMK for VM Disks and AKS
  • Marketplace
    • Introduction
      • Terminology
      • Offer Types
    • Partner Center
    • Offer Type
    • Publish a VM Offer HOL
      • Getting Started
      • Create VM Image
      • Test VM Image
      • VM Offer with SIG
      • VM Offer with SAS
      • Publish Offer
      • Other VM Resources
    • Publish a Solution Template HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Publish a Managed App HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Managed Apps with AKS HOL
    • Other Managed App Resources
    • SaaS Offer HOLs
    • SaaS Offer Video Series
      • Video 1 - SaaS Offer Overview
      • Video 2 - Purchasing a SaaS Offer
      • Video 3 - Purchasing a Private SaaS Plan
      • Video 4 - Publishing a SaaS Offer
      • Video 5 - Publishing a Private SaaS Plan
      • Video 6 - SaaS Offer Technical Overview
      • Video 7 - Azure AD Application Registrations
      • Video 8 - Using the SaaS Offer REST Fulfillment API
      • Video 9 - The SaaS Client Library for .NET
      • Video 10 - Building a Simple SaaS Landing Page in .NET
      • Video 11 - Building a Simple SaaS Publisher Portal in .NET
      • Video 12 - SaaS Webhook Overview
      • Video 13 - Implementing a Simple SaaS Webhook in .NET
      • Video 14 - Securing a Simple SaaS Webhook in .NET
      • Video 15 - SaaS Metered Billing Overview
      • Video 16 - The SaaS Metered Billing API with REST
  • Microsoft Fabric
    • Theory
    • Prereqs
    • Fabric Capacity
    • Set up a Remote State
    • Create a repo from a GitHub template
    • Configure an app reg for development
    • Initial Terraform workflow
    • Expanding your config
    • Configure a workload identity
    • GitHub Actions for Microsoft Fabric
    • GitLab pipeline for Microsoft Fabric
  • Packer & Ansible
    • Packer
    • Ansible
    • Dynamic Inventories
    • Playbooks & Roles
    • Custom Roles
    • Shared Image Gallery
  • Partner Admin Link
    • Understanding PAL
    • Quick reference page
    • User and guest IDs
    • Service principals with credentials
    • Workflows and pipelines
    • Using AzAPI in Terraform
    • PAL tagging with a service principal
    • Azure Lighthouse & PAL
    • PAL FAQ
  • REST API
    • REST API theory
    • Using az rest
  • Setup
  • Sovereign landing zone
    • Overview
    • Run the ALZ Accelerator
      • Prereqs
      • Elevate
      • Bootstrap
      • Demote
      • Components
    • Deploy Sovereign landing zone
      • Create an initial SLZ config
      • Add a local override library
      • Test locally
      • Run through the CI/CD workflow
    • Understanding libraries
      • What is a library?
      • Policies, Assignments and Roles
      • Archetypes, Overrides and Architecture
      • Metadata and Policy Default Values
      • Custom libraries
    • Reference configs
      • Sovereign landing zone
      • Sovereign landing zone library with overrides
      • SLZ extended with a country pack
  • Terraform
    • Fundamentals
      • Initialise
      • Format
      • Validate
      • Plan
      • Apply
      • Adding resources
      • Locals and outputs
      • Managing state
      • Importing resources
      • Destroy
    • Get set up for Terraform
      • Cloud Shell
      • macOS
      • Windows with PowerShell
      • Windows with Ubuntu in WSL2
    • Using AzAPI
      • Using the REST API
      • azapi_resource
      • Removing azapi_resource
      • azapi_update_resource
      • Data sources and outputs
      • Removing azapi_update_resource
  • Virtual Machines
    • Azure Bastion with native tools & AAD
    • Managed Identities

  • About
  • Archive
  1. Home
  2. Partner Admin Link
  3. Workflows and pipelines
Workflows and pipelines
Workflows and pipelines
Partner Admin Link
Understanding PAL
Quick reference page
User and guest IDs
Service principals with credentials
Workflows and pipelines
Using AzAPI in Terraform
PAL tagging with a service principal
Azure Lighthouse & PAL
PAL FAQ
  • Introduction
  • Example workflows
  • OpenID Connect federated credentials
    • Viewing federated credentials
    • Example federated credentials
  • Next

Workflows and pipelines

Workflows and pipelines commonly use service principals, authenticating with either a secret or preferably using an OpenID Connect federated credential.

Table of Contents

  • Introduction
  • Example workflows
  • OpenID Connect federated credentials
    • Viewing federated credentials
    • Example federated credentials
  • Next

Introduction

This scenario is for when you no longer have the secret or cert for a service principal, but you do have permission to create or modify pipelines or workflows in your CI/CD platform. These commonly use service principals when interacting with Azure environments. Historically they would have the client secret stored as a pipeline secret whereas it is now increasingly common to leverage OpenID Connect (OIDC) using a federated workload credential.

We’ll start with example workflows, but scroll down if you want some additional information on OpenID Connect federated credentials for service principals and managed identities.

Example workflows

These are OpenID Connect examples, but the Partner Admin Link steps can be easily inserted into existing workflows or adapted for secret based auth.

# Partner Admin Link using the Azure CLI with OpenID Connect

name: Configure PAL using the Azure CLI
on:
  workflow_dispatch:
    inputs:
      action:
        type: choice
        description: 'Action to perform'
        default: 'Create'
        options:
          - Create
          - Delete
        required: true
      partnerId:
        description: 'Partner ID'
        required: true

permissions:
      id-token: write
      contents: read

jobs:
  PAL:
    runs-on: ubuntu-latest
    steps:
    - name: Login to Azure using OIDC
      uses: azure/login@v2
      with:
        tenant-id: ${{ vars.ARM_TENANT_ID }}
        client-id: ${{ vars.ARM_CLIENT_ID }}
        allow-no-subscriptions: true

    - name: ${{ github.event.inputs.action }} the Partner Admin Link
      uses: azure/cli@v2
      with:
        azcliversion: latest
        inlineScript: |
          uri="https://management.azure.com/providers/Microsoft.ManagementPartner/${{ github.event.inputs.partnerId }}?api-version=2018-02-01"
          body='{"properties":{"partnerId":"${{ github.event.inputs.partnerId }}"}}'

          if [ "${{ github.event.inputs.action }}" = "Create" ]; then
            method=$(az rest --method get --output none --url "$uri" 2>/dev/null && echo patch || echo put)
            az rest --method "$method" --output jsonc --url "$uri" --body "$body"
          else
            az rest --method delete --output jsonc --url "$uri"
          fi          
# Partner Admin Link using PowerShell with OpenID Connect

name: Configure PAL using PowerShell
on:
  workflow_dispatch:
    inputs:
      action:
        type: choice
        description: 'Action to perform'
        default: 'Create'
        options:
          - Create
          - Delete
        required: true
      partnerId:
        description: 'Partner ID'
        required: true

permissions:
      id-token: write
      contents: read

jobs:
  PAL:
    runs-on: ubuntu-latest
    steps:
    - name: Login to Azure using OIDC
      uses: azure/login@v2
      with:
        tenant-id: ${{ vars.ARM_TENANT_ID }}
        client-id: ${{ vars.ARM_CLIENT_ID }}
        allow-no-subscriptions: true
        enable-AzPSSession: true

    - name: ${{ github.event.inputs.action }} the Partner Admin Link
      uses: azure/powershell@v2
      with:
        azPSVersion: latest
        inlineScript: |
          Install-Module -Name Az.ManagementPartner -Repository PSGallery -Force
          if ('${{ github.event.inputs.action }}' -eq 'Create') {
            $partner = Get-AzManagementPartner -ErrorAction SilentlyContinue
            if ($null -eq $partner) {
              New-AzManagementPartner -PartnerId ${{ github.event.inputs.partnerId }}
            } else {
              Update-AzManagementPartner -PartnerId ${{ github.event.inputs.partnerId }}
            }
          } else {
            Remove-AzManagementPartner -PartnerId ${{ github.event.inputs.partnerId }}
          }          

Example .gitlab-ci.yml file with a manual step.

spec:
  inputs:
    action:
      description: "Action to perform (e.g. link, unlink)"
      default: "link"
      options:
        - link
        - unlink
---
stages:
  - example

variables:
  PARTNER_ID: "31415927" # Override this in GitLab CI/CD variables if you need a different Partner ID.
  ACTION: $[[ inputs.action ]]

partner_admin_link:
  stage: example
  image: mcr.microsoft.com/azure-cli:latest
  id_tokens:
    AZURE_FEDERATED_TOKEN:
      aud: api://AzureADTokenExchange
  rules:
    - when: manual
  script:
    - |
      az login --service-principal --tenant "$AZURE_TENANT_ID" --username "$AZURE_CLIENT_ID" --federated-token "$AZURE_FEDERATED_TOKEN"

      uri="https://management.azure.com/providers/Microsoft.ManagementPartner/partners/${PARTNER_ID}?api-version=2018-02-01"
      body='{"properties":{"partnerId":"${PARTNER_ID}"}}'

      if [  "$ACTION" = "link" ]; then
        method=$(az rest --method get --output none --url "$uri" 2>/dev/null && echo patch || echo put)
        az rest --method "$method" --output jsonc --url "$uri" --body "$body"
      else
        az rest --method delete --output jsonc --url "$uri"
      fi

      echo "Partner Admin Link request completed for partner ${PARTNER_ID}."      

Note that the az login format for a service principal with a password is:

az login --service-principal --tenant "$AZURE_TENANT_ID" --username "$AZURE_CLIENT_ID" --password "$AZURE_CLIENT_SECRET"

OpenID Connect federated credentials

With OpenID Connect the service principal uses a federated credential to define the trust relationship to another identity provider and the context in which that is valid. This is far more secure than having a client secret that may be used for manual authentication, exactly as shown in the section above. (Removing the maintenance overhead of rotating secrets is another benefit.)

We’ll first see how to view them for service principals and for managed identities, and then cover a few example cloud CI/CD platforms.

Viewing federated credentials

  1. Open the Entra admin portal

  2. Open Entra ID > App Registrations

  3. Click on All applications, filter the app registrations using the displayName or appId / clientId, and select ℹ️ If you don’t know the appId then you can search the Enterprise apps for the service principal’s objectId and retrieve the appId from there.

  4. Click on Certificates and secrets to view the federated credential’s Subject identifier or claims matching expression

    The example below shows a federated credential for GitHub.

    App registration’s federated credential for a GitHub repo’s main branch

  1. Open the Azure portal

  2. Open Managed Identities or search for Managed Identities in the global search bar.

  3. Select the relevant user-assigned managed identity (UAMI), or create one if needed.

  4. Open Federated credentials in the left menu.

  5. Review the Subject identifier or claims matching expression for the OIDC trust relationship.

    The example below shows a federated credential for GitLab.

    Managed Identity federated credential for a GitLab repo’s main branch

Example federated credentials

The names seen in the examples below are not mandated. They are just sensible default values that I use.

Example federated credential
{
  "audiences": [
    "api://AzureADTokenExchange"
  ],
  "id": "<snip>",
  "issuer": "https://token.actions.githubusercontent.com",
  "name": "github",
  "resourceGroup": "<snip>",
  "subject": "repo:azurecitadel@219859934/my-terraform-workload-repo@1357395177:ref:refs/heads/main",
  "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials"
}

The subject identifier for the example is repo:azurecitadel/my-terraform-workload-repo:ref:refs/heads/main, so OpenID Connect will only succeed for workflows run from this repository’s main branch.

Note that the immutable subject claims format is now enforced for new federated credentials, which is why you will now see the the repo:azurecitadel@219859934/my-terraform-workload-repo@1357395177 format (including the IDs) for the repo element.

Alternate subject identifiers

I will skip the owner and repository IDs from the examples below. Here are the most common entity types supported for GitHub.

Entity Type Example
Environment repo:azurecitadel/my-terraform-workload-repo:environment:prod
Branch repo:azurecitadel/my-terraform-workload-repo:ref:refs/heads/main
Pull request repo:azurecitadel/my-terraform-workload-repo:pull_request
Tag repo:azurecitadel/my-terraform-workload-repo:ref:refs/tags/v1.0
Complex repo:richeney-org@219859934/my-repo@1357395177:environment:prod:job_workflow_ref:richeney-org/alz-mgmt-templates/.github/workflows/cd-template.yaml@refs/heads/main

The last one is the format used by the ALZ Accelerator, and combines the repo with an environment entity and a specific workflow file stored in another repo.

Example federated credential
{
  "audiences": [
    "api://AzureADTokenExchange"
  ],
  "id": "<snip>",
  "issuer": "https://gitlab.com",
  "name": "gitlab",
  "resourceGroup": "<snip>",
  "subject": "project_path:richeney-group/gitlab-pal-uami-oidc:ref_type:branch:ref:main",
  "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials"
}

The subject identifier for this example is project_path:richeney-group/gitlab-pal-uami-oidc:ref_type:branch:ref:main, so OpenID Connect will only succeed for workflows run from this repository’s main branch.

Alternate subject identifiers

An immutable version would use this format: project_id:57382910:ref_type:branch:ref:main

See the Mutable subjects in federated identity credentials page for examples of more flexible claims covering both mutable and immutable.

Next

Are you using Terraform and wish to embed the link within your Terraform config? See the next page for an azapi example.

Source: https://www.azurecitadel.com/pal/workflows/
Published: 10 Oct 2025
Printed:
Service principals with credentials Workflows and pipelines Using AzAPI in Terraform