Skip to content

Azure Setup

One command configures everything you need to run APEX workflows against your Azure environment — Entra ID app registration, OIDC federated credentials, RBAC roles, GitHub secrets, variables, and environments.

From inside the dev container:

Terminal window
npm run setup

The wizard prompts for your Azure subscription, management group, and app name, then creates everything automatically. Safe to re-run — it skips completed steps.

RequirementHow to Check
Azure CLI logged inaz account show
GitHub CLI authenticatedgh auth status
jq installedjq --version
Permission to create Entra app registrationsAsk your Azure AD admin
Permission to assign RBAC rolesOwner or User Access Administrator

The wizard creates these resources across Azure and GitHub:

ResourceDetails
Entra ID App Registrationapex-github-oidc-{repo-name}
Service PrincipalLinked to the app registration
Federated Credential: github-mainSubject: repo:{owner}/{repo}:ref:refs/heads/main
Federated Credential: github-env-devSubject: repo:{owner}/{repo}:environment:dev
Federated Credential: github-env-stagingSubject: repo:{owner}/{repo}:environment:staging
Federated Credential: github-env-prodSubject: repo:{owner}/{repo}:environment:prod
RBAC: ReaderAt Management Group scope (governance reads)
RBAC: ContributorAt Subscription scope (deployments)
ResourceDetails
Secret: AZURE_CLIENT_IDEntra app client ID
Secret: AZURE_TENANT_IDAzure AD tenant ID
Secret: AZURE_SUBSCRIPTION_IDTarget subscription ID
Variable: GOVERNANCE_BASELINE_ENABLEDtrue (kill switch)
Variable: GOVERNANCE_MG_IDManagement Group to scan
Variable: GOVERNANCE_MAX_SUBSCRIPTIONSMax subscriptions (default: 100)
Environment: devFor development deployments
Environment: stagingFor staging deployments
Environment: prodFor production deployments
GitHub PagesEnabled with Actions source
Auto-mergeEnabled on repository

GitHub Actions workflows authenticate to Azure using OpenID Connect (OIDC) — no client secrets to rotate.

sequenceDiagram
    participant GH as GitHub Actions
    participant OIDC as GitHub OIDC Provider
    participant Entra as Microsoft Entra ID
    participant ARM as Azure Resource Manager

    GH->>OIDC: Request OIDC token
    OIDC->>GH: JWT with repo/branch/env claims
    GH->>Entra: Exchange JWT for Azure token
    Note over Entra: Validates issuer, subject,<br/>audience against federated credential
    Entra->>GH: Azure access token
    GH->>ARM: API calls with Azure token
    ARM->>GH: Resource data

Why OIDC? No secrets to store or rotate. The federated credential binds a specific GitHub repo + branch/environment to the Azure service principal. Tokens are short-lived and scoped.

For CI automation or scripted provisioning, pass --non-interactive and set environment variables:

Terminal window
export AZURE_TENANT_ID="00000000-0000-0000-0000-000000000000"
export AZURE_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export GOVERNANCE_MG_ID="mg-contoso-root"
export GOVERNANCE_MAX_SUBSCRIPTIONS="100"
export APP_DISPLAY_NAME="apex-github-oidc-my-project"
export DEPLOY_ENVIRONMENTS="dev,staging,prod"
npm run setup -- --non-interactive

All variables are required in headless mode. The wizard exits with an error if any are missing.

If you cannot run the wizard (for example, a different admin must create the Entra app), follow these steps manually.

Terminal window
# Create the app
az ad app create --display-name "apex-github-oidc-my-project"
# Note the appId from the output, then create the service principal
az ad sp create --id <APP_ID>
Terminal window
APP_ID="<your-app-id>"
REPO="owner/repo"
# Main branch (for scheduled workflows like governance baseline)
az ad app federated-credential create --id "$APP_ID" --parameters '{
"name": "github-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:'"$REPO"':ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# Repeat for each environment (dev, staging, prod)
for ENV in dev staging prod; do
az ad app federated-credential create --id "$APP_ID" --parameters '{
"name": "github-env-'"$ENV"'",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:'"$REPO"':environment:'"$ENV"'",
"audiences": ["api://AzureADTokenExchange"]
}'
done
Terminal window
SP_OBJECT_ID=$(az ad sp show --id "$APP_ID" --query id -o tsv)
# Reader at Management Group (for governance policy reads)
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Reader" \
--scope "/providers/Microsoft.Management/managementGroups/<MG_ID>"
# Contributor at Subscription (for deployments)
az role assignment create \
--assignee-object-id "$SP_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope "/subscriptions/<SUBSCRIPTION_ID>"
Terminal window
# Secrets
gh secret set AZURE_CLIENT_ID --body "$APP_ID"
gh secret set AZURE_TENANT_ID --body "<TENANT_ID>"
gh secret set AZURE_SUBSCRIPTION_ID --body "<SUBSCRIPTION_ID>"
# Variables
gh variable set GOVERNANCE_BASELINE_ENABLED --body "true"
gh variable set GOVERNANCE_MG_ID --body "<MG_ID>"
gh variable set GOVERNANCE_MAX_SUBSCRIPTIONS --body "100"
# Environments
for ENV in dev staging prod; do
gh api "repos/<OWNER>/<REPO>/environments/$ENV" --method PUT
done

Run az login --use-device-code inside the dev container.

Your account needs the Management Group Reader role at the tenant root level, or at least at the target MG. Ask your Azure admin.

GitHub environment creation requires admin access to the repository. If you are a collaborator, ask the repo owner to create the environments or run the wizard from their account.

”Federated credential subject mismatch”

Section titled “”Federated credential subject mismatch””

The scheduled governance workflow runs on main with no environment context. Its OIDC subject is repo:{owner}/{repo}:ref:refs/heads/main. If you see authentication failures, verify this exact subject exists on the federated credential.

The wizard is idempotent. State files in .azure/.setup-state/ track completed phases. To start fresh:

Terminal window
npm run setup -- --reset
npm run setup

To completely remove all resources created by the wizard:

Terminal window
APP_ID=$(az ad app list --display-name "apex-github-oidc-my-project" \
--query '[0].appId' -o tsv)
SP_OBJECT_ID=$(az ad sp show --id "$APP_ID" --query id -o tsv)
# Remove RBAC assignments
az role assignment delete --assignee "$SP_OBJECT_ID" --role "Reader" \
--scope "/providers/Microsoft.Management/managementGroups/<MG_ID>"
az role assignment delete --assignee "$SP_OBJECT_ID" --role "Contributor" \
--scope "/subscriptions/<SUBSCRIPTION_ID>"
# Delete the app registration (also deletes SP and federated credentials)
az ad app delete --id "$APP_ID"
# Remove GitHub secrets and variables
gh secret delete AZURE_CLIENT_ID
gh secret delete AZURE_TENANT_ID
gh secret delete AZURE_SUBSCRIPTION_ID
gh variable delete GOVERNANCE_BASELINE_ENABLED
gh variable delete GOVERNANCE_MG_ID
gh variable delete GOVERNANCE_MAX_SUBSCRIPTIONS