|
| 1 | +--- |
| 2 | +name: "integration-test-prerequisites" |
| 3 | +description: "Set up Azure and GitHub prerequisites for the integration-test workflow using a user-assigned managed identity, OIDC federated credentials, RBAC roles, and environment secrets. Use when troubleshooting AADSTS70025/AADSTS700213 or authorization failures during integration-test workflow runs." |
| 4 | +domain: "ci-cd" |
| 5 | +confidence: "high" |
| 6 | +source: "manual + observed from integration-test OIDC and RBAC troubleshooting" |
| 7 | +--- |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +Use this skill when preparing or repairing prerequisites for `.github/workflows/integration-test.yml`. |
| 12 | + |
| 13 | +This workflow expects: |
| 14 | +- OIDC login through `azure/login@v2` |
| 15 | +- GitHub environment `integration-test` |
| 16 | +- Azure identity with enough permissions to deploy resources and create role assignments in test resource groups |
| 17 | + |
| 18 | +Preferred identity model: user-assigned managed identity (UAMI). |
| 19 | + |
| 20 | +## Required Inputs |
| 21 | + |
| 22 | +Set these values before running commands: |
| 23 | + |
| 24 | +```bash |
| 25 | +set -euo pipefail |
| 26 | + |
| 27 | +SUBSCRIPTION_ID="<your-subscription-id>" |
| 28 | +TENANT_ID="<your-tenant-id>" |
| 29 | +IDENTITY_RESOURCE_GROUP="<resource-group-for-uami>" |
| 30 | +IDENTITY_NAME="apiops-cli-integration-test-uami" |
| 31 | +APIM_PUBLISHER_EMAIL="<publisher-email>" |
| 32 | + |
| 33 | +GITHUB_OWNER="Azure" |
| 34 | +GITHUB_REPO="apiops-cli" |
| 35 | +GITHUB_ENVIRONMENT="integration-test" |
| 36 | + |
| 37 | +OIDC_ISSUER="https://token.actions.githubusercontent.com" |
| 38 | +OIDC_AUDIENCE="api://AzureADTokenExchange" |
| 39 | +``` |
| 40 | + |
| 41 | +## Patterns |
| 42 | + |
| 43 | +### 1) Build Correct OIDC Subject Dynamically |
| 44 | + |
| 45 | +Do not hard-code numeric GitHub IDs. Build the subject based on repo OIDC customization settings. |
| 46 | + |
| 47 | +```bash |
| 48 | +OIDC_SUBJECT="repo:${GITHUB_OWNER}/${GITHUB_REPO}:environment:${GITHUB_ENVIRONMENT}" |
| 49 | + |
| 50 | +USE_DEFAULT_SUB="$(gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/actions/oidc/customization/sub --jq '.use_default')" |
| 51 | +if [[ "${USE_DEFAULT_SUB}" == "false" ]]; then |
| 52 | + OWNER_ID="$(gh api repos/${GITHUB_OWNER}/${GITHUB_REPO} --jq '.owner.id')" |
| 53 | + REPO_ID="$(gh api repos/${GITHUB_OWNER}/${GITHUB_REPO} --jq '.id')" |
| 54 | + OIDC_SUBJECT="repository_owner_id:${OWNER_ID}:repository_id:${REPO_ID}:environment:${GITHUB_ENVIRONMENT}" |
| 55 | +fi |
| 56 | + |
| 57 | +echo "OIDC_SUBJECT=${OIDC_SUBJECT}" |
| 58 | +``` |
| 59 | + |
| 60 | +### 2) Provision/Reuse UAMI |
| 61 | + |
| 62 | +```bash |
| 63 | +az account set --subscription "${SUBSCRIPTION_ID}" |
| 64 | + |
| 65 | +# Create resource group if it doesn't exist |
| 66 | +az group create \ |
| 67 | + --name "${IDENTITY_RESOURCE_GROUP}" \ |
| 68 | + --location eastus 1>/dev/null || true |
| 69 | + |
| 70 | +# Create or reuse UAMI |
| 71 | +az identity create \ |
| 72 | + --resource-group "${IDENTITY_RESOURCE_GROUP}" \ |
| 73 | + --name "${IDENTITY_NAME}" 1>/dev/null |
| 74 | + |
| 75 | +IDENTITY_CLIENT_ID="$(az identity show \ |
| 76 | + --resource-group "${IDENTITY_RESOURCE_GROUP}" \ |
| 77 | + --name "${IDENTITY_NAME}" \ |
| 78 | + --query clientId -o tsv)" |
| 79 | + |
| 80 | +IDENTITY_PRINCIPAL_ID="$(az identity show \ |
| 81 | + --resource-group "${IDENTITY_RESOURCE_GROUP}" \ |
| 82 | + --name "${IDENTITY_NAME}" \ |
| 83 | + --query principalId -o tsv)" |
| 84 | +``` |
| 85 | + |
| 86 | +### 3) Configure Federated Credential |
| 87 | + |
| 88 | +```bash |
| 89 | +az identity federated-credential create \ |
| 90 | + --resource-group "${IDENTITY_RESOURCE_GROUP}" \ |
| 91 | + --identity-name "${IDENTITY_NAME}" \ |
| 92 | + --name "github-env-integration-test" \ |
| 93 | + --issuer "${OIDC_ISSUER}" \ |
| 94 | + --subject "${OIDC_SUBJECT}" \ |
| 95 | + --audiences "${OIDC_AUDIENCE}" |
| 96 | +``` |
| 97 | + |
| 98 | +### 4) Assign Azure RBAC at Subscription Scope |
| 99 | + |
| 100 | +`User Access Administrator` is required for `Microsoft.Authorization/roleAssignments/write` during deployment. |
| 101 | + |
| 102 | +```bash |
| 103 | +SCOPE="/subscriptions/${SUBSCRIPTION_ID}" |
| 104 | + |
| 105 | +for ROLE in "Contributor" "User Access Administrator" "Key Vault Administrator" "API Management Service Contributor"; do |
| 106 | + az role assignment create \ |
| 107 | + --assignee-object-id "${IDENTITY_PRINCIPAL_ID}" \ |
| 108 | + --assignee-principal-type ServicePrincipal \ |
| 109 | + --role "${ROLE}" \ |
| 110 | + --scope "${SCOPE}" \ |
| 111 | + 1>/dev/null |
| 112 | +done |
| 113 | +``` |
| 114 | + |
| 115 | +### 5) Set GitHub Environment Secrets |
| 116 | + |
| 117 | +```bash |
| 118 | +unset GITHUB_TOKEN GH_TOKEN |
| 119 | + |
| 120 | +gh secret set AZURE_CLIENT_ID \ |
| 121 | + --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \ |
| 122 | + --env "${GITHUB_ENVIRONMENT}" \ |
| 123 | + --body "${IDENTITY_CLIENT_ID}" |
| 124 | + |
| 125 | +gh secret set AZURE_TENANT_ID \ |
| 126 | + --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \ |
| 127 | + --env "${GITHUB_ENVIRONMENT}" \ |
| 128 | + --body "${TENANT_ID}" |
| 129 | + |
| 130 | +gh secret set AZURE_SUBSCRIPTION_ID \ |
| 131 | + --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \ |
| 132 | + --env "${GITHUB_ENVIRONMENT}" \ |
| 133 | + --body "${SUBSCRIPTION_ID}" |
| 134 | + |
| 135 | +gh secret set APIM_PUBLISHER_EMAIL \ |
| 136 | + --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \ |
| 137 | + --env "${GITHUB_ENVIRONMENT}" \ |
| 138 | + --body "${APIM_PUBLISHER_EMAIL}" |
| 139 | +``` |
| 140 | + |
| 141 | +## Verification |
| 142 | + |
| 143 | +```bash |
| 144 | +az identity federated-credential list \ |
| 145 | + --resource-group "${IDENTITY_RESOURCE_GROUP}" \ |
| 146 | + --identity-name "${IDENTITY_NAME}" \ |
| 147 | + --query "[].{name:name,subject:subject,issuer:issuer,audience:audiences[0]}" -o table |
| 148 | + |
| 149 | +az role assignment list \ |
| 150 | + --assignee-object-id "${IDENTITY_PRINCIPAL_ID}" \ |
| 151 | + --scope "/subscriptions/${SUBSCRIPTION_ID}" \ |
| 152 | + --query "[].{role:roleDefinitionName,scope:scope}" -o table |
| 153 | + |
| 154 | +unset GITHUB_TOKEN GH_TOKEN |
| 155 | +gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/environments/${GITHUB_ENVIRONMENT}/secrets --jq '.secrets[].name' |
| 156 | +``` |
| 157 | + |
| 158 | +## Failure Mapping |
| 159 | + |
| 160 | +- `AADSTS70025`: Missing federated credential for presented subject. |
| 161 | +- `AADSTS700213`: Subject mismatch between token claim and federated credential. |
| 162 | +- `Microsoft.Authorization/roleAssignments/write`: Missing `User Access Administrator` or `Owner`. |
| 163 | + |
| 164 | +## Anti-Patterns |
| 165 | + |
| 166 | +- Hard-coding `repository_owner_id` and `repository_id` values in docs/scripts. |
| 167 | +- Assuming `repo:<owner>/<repo>:environment:<env>` subject when customization is enabled. |
| 168 | +- Using only `Contributor` when deployment creates RBAC assignments. |
| 169 | +- Using ephemeral `GITHUB_TOKEN` integration auth for `gh secret` management without checking scopes. |
0 commit comments