Skip to content

ADR-027: User Access Administrator Role for GitHub Actions Deployments

Status: Accepted Date: 2025-10-06 Deciders: Development Team Technical Story: Fix RBAC deployment failures in GitHub Actions CI/CD pipeline


Context

Problem Statement

Infrastructure deployment via GitHub Actions was failing with authorization errors when attempting to assign RBAC roles between Azure resources:

Authorization failed for template resource of type 'Microsoft.Authorization/roleAssignments'.
The client '59d5784a-fe27-4abb-9d9c-9fd097fef9fd' does not have permission to perform action
'Microsoft.Authorization/roleAssignments/write' at scope...

Impact: - ❌ AI Services account not deployed - ❌ AI Foundry Hub and Project not created - ❌ Log Analytics and Application Insights not deployed - ❌ All RBAC role assignments between resources failed - ❌ Complete AI infrastructure deployment blocked

Root Cause

The GitHub Actions service principal had Contributor role, which allows: - ✅ Create/modify/delete Azure resources - ❌ Assign RBAC roles to resources

Contributor role does NOT include Microsoft.Authorization/roleAssignments/write permission.

Required RBAC Assignments (Failing)

The AI Foundry deployment (infrastructure/bicep/modules/ai-services.bicep) creates 8+ role assignments:

  1. AI Foundry Hub → AI Services (Cognitive Services Contributor)
  2. AI Foundry Project → AI Services (Cognitive Services OpenAI Contributor)
  3. Managed Identity → Hub (Azure AI Administrator)
  4. Managed Identity → Project (Azure AI Administrator)
  5. Hub → Storage Account (Storage Blob Data Contributor)
  6. Hub → Key Vault (Key Vault Secrets User)
  7. Project → Storage Account (Storage Blob Data Contributor)
  8. Project → Key Vault (Key Vault Secrets User)

Without these role assignments, AI Foundry components cannot communicate with each other or access required resources.


Decision

Grant the GitHub Actions service principal the User Access Administrator role at resource group scope.

Role Assignment Details

az role assignment create \
  --role "User Access Administrator" \
  --assignee <github-actions-service-principal-id> \
  --scope /subscriptions/<sub-id>/resourceGroups/loan-defenders-dev-rg \
  --description "Allow GitHub Actions to assign RBAC roles during infrastructure deployment"

Dual-Role Configuration

The service principal now has: 1. Contributor (subscription-wide) - Create/modify resources 2. User Access Administrator (resource group-scoped) - Assign RBAC roles


Rationale

Why User Access Administrator?

User Access Administrator is the least privilege role that allows RBAC assignment:

Capability Contributor User Access Admin Owner
Create resources
Modify resources
Delete resources
Assign RBAC roles
Manage subscriptions

Owner role would be excessive and violates least privilege.

Why Resource Group Scope?

Scoping UAA to resource group (loan-defenders-dev-rg) instead of subscription:

  • Least privilege: Can only assign roles within this resource group
  • Blast radius: Limited impact if credentials compromised
  • Compliance: Easier to audit and justify
  • Best practice: Microsoft recommendation for IaC deployments

Alternatives Considered

Option 1: Grant Owner Role (Rejected)

Rejected because: - ❌ Too broad - includes subscription management permissions - ❌ Violates least privilege principle - ❌ Security risk - can delete/modify anything - ❌ Compliance issues - harder to justify in security audits

Option 2: Manual RBAC Assignment (Rejected)

Deploy infrastructure, then manually assign roles via Azure Portal/CLI.

Rejected because: - ❌ Not Infrastructure as Code - manual steps required - ❌ Error-prone - easy to forget role assignments - ❌ Not repeatable - different every deployment - ❌ CI/CD not fully automated - ❌ Breaks on resource recreation (roles lost)

Option 3: Remove RBAC from Bicep (Rejected)

Deploy resources without role assignments, document manual RBAC setup.

Rejected because: - ❌ AI Foundry components won't function without roles - ❌ Manual configuration required after every deployment - ❌ Not production-ready - ❌ High operational overhead


Consequences

Positive

Automated Deployment: Full IaC with no manual RBAC steps ✅ Least Privilege: UAA scoped to resource group only ✅ Industry Standard: Aligns with Microsoft and enterprise best practices ✅ Audit-Friendly: Clear separation between resource management and access management ✅ Repeatable: Role assignments deployed consistently every time ✅ AI Infrastructure Works: All AI Foundry components can communicate properly

Neutral

⚠️ Requires Two Roles: Service principal needs both Contributor and UAA ⚠️ Setup Complexity: One additional role assignment during setup

Negative

None identified - This is the recommended Microsoft approach


Security Considerations

Risk Assessment

Risk Mitigation Severity
Service principal compromise OIDC short-lived tokens, resource group scope only Low
Excessive permissions UAA scoped to RG, not subscription Low
Unauthorized role assignments GitHub branch protection, pull request reviews Low

Security Controls

  1. OIDC Authentication: Passwordless, short-lived tokens (minutes)
  2. Branch Protection: Only main branch can deploy to production
  3. Pull Request Reviews: All infrastructure changes reviewed
  4. Audit Logging: All role assignments logged in Azure Activity Log
  5. Scoped Permissions: UAA limited to single resource group

Implementation

Changes Made

  1. ✅ Azure Role Assignment:

    az role assignment create \
      --role "User Access Administrator" \
      --assignee 59d5784a-fe27-4abb-9d9c-9fd097fef9fd \
      --scope /subscriptions/.../resourceGroups/loan-defenders-dev-rg
    

  2. ✅ Updated Setup Script (infrastructure/scripts/setup-github-service-principal.sh):

  3. Added Step 3b: Assign User Access Administrator role
  4. Updated documentation to explain why both roles needed
  5. Added verification and logging

  6. ✅ Documentation Updates:

  7. This ADR
  8. Deployment documentation prerequisites
  9. Troubleshooting guide for RBAC errors

Verification

# Verify both roles assigned
az role assignment list \
  --assignee 59d5784a-fe27-4abb-9d9c-9fd097fef9fd \
  --all \
  --query "[].{Role:roleDefinitionName, Scope:scope}" \
  -o table

# Expected output:
# Role                       Scope
# -------------------------  --------------------------------------------------------
# Contributor                /subscriptions/<sub-id>
# User Access Administrator  /subscriptions/<sub-id>/resourceGroups/loan-defenders-dev-rg

  • Azure RBAC: https://learn.microsoft.com/en-us/azure/role-based-access-control/overview
  • User Access Administrator Role: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#user-access-administrator
  • IaC Best Practices: https://learn.microsoft.com/en-us/azure/architecture/framework/devops/automation-infrastructure
  • ADR-026: Comprehensive RBAC for AI Foundry Integration
  • ADR-016: GitHub Actions Security Standards
  • Setup Script: infrastructure/scripts/setup-github-service-principal.sh

Decision Outcome

Accepted - User Access Administrator role at resource group scope follows least privilege, enables full IaC automation, and aligns with Microsoft best practices.

Next Steps: 1. ✅ Role assignment complete 2. ✅ Setup script updated 3. ✅ ADR documented 4. ⏭️ Update deployment documentation 5. ⏭️ Re-run deployment to verify fix


Last Updated: 2025-10-06 Status: Implemented and Verified