Skip to content

Azure setup

The product’s npm run setup configures an automation identity and GitHub repository settings. It is not required to read documentation or draft requirements. It is also not a read-only environment check.

From inside the dev container:

Terminal window
npm run setup

The wizard prompts for Azure and repository configuration, then performs its setup phases. It tracks completion in local state. Re-running it is not proof that remote resources still match that state.

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

The wizard creates or reuses these resources and settings. Some repository-setting failures are warnings, so inspect the results rather than assuming every row succeeded.

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_IDMicrosoft Entra 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) rather than a long-lived client secret.

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

The federated credential binds a repository branch or environment identity to the service principal without a long-lived client secret. Azure role assignments still determine what that principal can access.

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

Some values have defaults or come from the current Azure context. Set them explicitly for a reviewed headless run. The script’s --help output documents defaults. Missing required values or invalid IDs stop setup.

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.

Ask your Azure administrator for the read permissions required at the target management-group scope. Do not request tenant-root access when a narrower assignment is sufficient.

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.

State files in .azure/.setup-state/ track completed phases. Inspect remote results before retrying. The following resets local setup tracking, not Azure resources or GitHub settings:

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

Inventory exact application and service-principal IDs, role-assignment IDs, federated credentials, and repository changes. Distinguish resources created by this run from reused resources with other consumers.

Obtain approval for the specific removals. Do not select an application for deletion by display name or delete a shared principal. Cleanup also needs an explicit decision about environments, Pages, and auto-merge settings. Removing three secrets and variables does not undo the full setup.