diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd0a1ea..9c96ee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: "24" cache: "npm" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a31c177..290af1d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,10 +32,10 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login (OIDC — staging managed identity) - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -65,10 +65,10 @@ jobs: APP_NAME: ca-acroyoga-web-staging steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login (OIDC — staging managed identity) - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -114,10 +114,10 @@ jobs: APP_NAME: ca-acroyoga-web-production steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login (OIDC — production managed identity) - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 259f302..33db515 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,9 +19,9 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: "24" cache: "npm" @@ -110,10 +110,10 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login (OIDC — nightly identity for ACR push) - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID_NIGHTLY }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -159,10 +159,10 @@ jobs: APP_NAME: ca-acroyoga-web-nightly steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login (OIDC — nightly managed identity) - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID_NIGHTLY }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -179,8 +179,8 @@ jobs: dbAdminPassword="${{ secrets.DB_ADMIN_PASSWORD }}" \ nextAuthSecret="$(openssl rand -base64 32)" \ sharedContainerRegistryLoginServer="${{ secrets.AZURE_CONTAINER_REGISTRY }}" \ - githubOrg="microsoft" \ - githubRepo="CommunityManagement-Sample-Spec-Kit" + githubOwnerId="${{ github.repository_owner_id }}" \ + githubRepoId="${{ github.repository_id }}" - name: Wait for readiness run: | diff --git a/infra/main.bicep b/infra/main.bicep index 4af7a21..d22a14d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -72,11 +72,11 @@ param memorySize string = '1Gi' @description('Email address for alert notifications') param alertEmailAddress string = '' -@description('GitHub organisation name (e.g. "microsoft"). When set together with githubRepo, OIDC federated identity credentials are provisioned on the managed identity so GitHub Actions can authenticate without stored secrets.') -param githubOrg string = '' +@description('GitHub repository owner (organisation) numeric ID (e.g. "6154722"). When set together with githubRepoId, OIDC federated identity credentials are provisioned on the managed identity so GitHub Actions can authenticate without stored secrets.') +param githubOwnerId string = '' -@description('GitHub repository name (e.g. "CommunityManagement-Sample-Spec-Kit"). Required when githubOrg is set.') -param githubRepo string = '' +@description('GitHub repository numeric ID (e.g. "1182392763"). Required when githubOwnerId is set.') +param githubRepoId string = '' @description('Deploy Azure Front Door CDN. Set to false for non-user-facing environments like nightly.') param deployFrontDoor bool = true @@ -101,8 +101,8 @@ module identity 'modules/managed-identity.bicep' = { params: { environmentName: environmentName location: location - githubOrg: githubOrg - githubRepo: githubRepo + githubOwnerId: githubOwnerId + githubRepoId: githubRepoId // Only the staging identity gets the main-branch FIC (for the build-and-push CI job) addMainBranchFic: environmentName == 'staging' } diff --git a/infra/modules/managed-identity.bicep b/infra/modules/managed-identity.bicep index 1cbd6b7..99e54a2 100644 --- a/infra/modules/managed-identity.bicep +++ b/infra/modules/managed-identity.bicep @@ -4,11 +4,11 @@ param environmentName string @description('Azure region') param location string = resourceGroup().location -@description('GitHub organisation name (e.g. "microsoft"). When set, OIDC federated identity credentials are created on the managed identity so GitHub Actions can authenticate without stored secrets (Constitution XIV).') -param githubOrg string = '' +@description('GitHub repository owner (organisation) numeric ID (e.g. "6154722"). When set together with githubRepoId, OIDC federated identity credentials are created using the org-level customised subject format (Constitution XIV).') +param githubOwnerId string = '' -@description('GitHub repository name (e.g. "CommunityManagement-Sample-Spec-Kit"). Required when githubOrg is set.') -param githubRepo string = '' +@description('GitHub repository numeric ID (e.g. "1182392763"). Required when githubOwnerId is set.') +param githubRepoId string = '' @description('When true, add a federated credential for the main branch push event (used by the build-and-push CI job). Only set to true for the staging environment.') param addMainBranchFic bool = false @@ -28,25 +28,27 @@ resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023- } // Federated Identity Credential — GitHub Actions environment (deploy job) -// Subject: repo:/:environment: (protected by GH environment rules) -resource ficEnvironment 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = if (!empty(githubOrg) && !empty(githubRepo)) { +// Subject uses org-level customised OIDC claim template: +// repository_owner_id::repository_id::environment: +resource ficEnvironment 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = if (!empty(githubOwnerId) && !empty(githubRepoId)) { parent: managedIdentity name: 'github-actions-env-${environmentName}' properties: { issuer: gitHubIssuer - subject: 'repo:${githubOrg}/${githubRepo}:environment:${environmentName}' + subject: 'repository_owner_id:${githubOwnerId}:repository_id:${githubRepoId}:environment:${environmentName}' audiences: ficAudiences } } // Federated Identity Credential — GitHub Actions main branch (build-and-push job) -// Subject: repo:/:ref:refs/heads/main (only granted on staging identity) -resource ficMainBranch 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = if (addMainBranchFic && !empty(githubOrg) && !empty(githubRepo)) { +// Subject uses org-level customised OIDC claim template: +// repository_owner_id::repository_id::ref:refs/heads/main +resource ficMainBranch 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = if (addMainBranchFic && !empty(githubOwnerId) && !empty(githubRepoId)) { parent: managedIdentity name: 'github-actions-main-branch' properties: { issuer: gitHubIssuer - subject: 'repo:${githubOrg}/${githubRepo}:ref:refs/heads/main' + subject: 'repository_owner_id:${githubOwnerId}:repository_id:${githubRepoId}:ref:refs/heads/main' audiences: ficAudiences } } diff --git a/specs/020-azure-nightly-publish/contracts/infrastructure.md b/specs/020-azure-nightly-publish/contracts/infrastructure.md index 74f216c..2b0a152 100644 --- a/specs/020-azure-nightly-publish/contracts/infrastructure.md +++ b/specs/020-azure-nightly-publish/contracts/infrastructure.md @@ -109,8 +109,8 @@ File: `infra/main.parameters.nightly.json` "entraClientId": { "value": "${ENTRA_CLIENT_ID}" }, "entraTenantId": { "value": "${ENTRA_TENANT_ID}" }, "entraTenantDomain": { "value": "${ENTRA_TENANT_DOMAIN}" }, - "githubOrg": { "value": "${GITHUB_ORG}" }, - "githubRepo": { "value": "${GITHUB_REPO}" }, + "githubOwnerId": { "value": "${GITHUB_OWNER_ID}" }, + "githubRepoId": { "value": "${GITHUB_REPO_ID}" }, "customDomainHostname": { "value": "" }, "alertEmailAddress": { "value": "" } } diff --git a/specs/020-azure-nightly-publish/data-model.md b/specs/020-azure-nightly-publish/data-model.md index 7ece43f..3f99ac9 100644 --- a/specs/020-azure-nightly-publish/data-model.md +++ b/specs/020-azure-nightly-publish/data-model.md @@ -29,7 +29,7 @@ This feature does not introduce traditional application data entities (database |----------|-------|-------| | name | `id-acroyoga-nightly` | Pattern: `id-acroyoga-{env}` | | federatedCredentials[0].name | `github-actions-env-nightly` | OIDC for deploy job | -| federatedCredentials[0].subject | `repo:{org}/{repo}:environment:nightly` | GitHub environment binding | +| federatedCredentials[0].subject | `repository_owner_id:{owner_id}:repository_id:{repo_id}:environment:nightly` | Org-level customised OIDC subject | | federatedCredentials[0].issuer | `https://token.actions.githubusercontent.com` | GitHub OIDC issuer | | federatedCredentials[0].audiences | `['api://AzureADTokenExchange']` | Standard Entra audience | diff --git a/specs/020-azure-nightly-publish/quickstart.md b/specs/020-azure-nightly-publish/quickstart.md index 3a08aeb..fbf8230 100644 --- a/specs/020-azure-nightly-publish/quickstart.md +++ b/specs/020-azure-nightly-publish/quickstart.md @@ -39,7 +39,7 @@ az identity federated-credential create \ --identity-name "$IDENTITY_NAME" \ --resource-group "$RG_NIGHTLY" \ --issuer "https://token.actions.githubusercontent.com" \ - --subject "repo:${GH_REPO}:environment:nightly" \ + --subject "repository_owner_id:${GH_OWNER_ID}:repository_id:${GH_REPO_ID}:environment:nightly" \ --audiences "api://AzureADTokenExchange" # ── 4. Role assignments ── @@ -132,7 +132,7 @@ The `deploy` job runs `az deployment group create` with `infra/main.bicep` + `in | Symptom | Likely Cause | Fix | |---------|-------------|-----| -| OIDC login fails | FIC subject mismatch | Verify FIC subject is `repo:microsoft/CommunityManagement-Sample-Spec-Kit:environment:nightly` | +| OIDC login fails | FIC subject mismatch | Verify FIC subject matches org-level OIDC template: `repository_owner_id::repository_id::environment:nightly` | | ACR push denied | Missing AcrPush role | Re-run the `az role assignment create --role AcrPush` command | | Bicep deploy fails on role assignment | Identity needs Owner, not Contributor | Re-run the `az role assignment create --role Owner` command | | Container app can't pull image | Missing AcrPull role | Re-run the `az role assignment create --role AcrPull` command |