Skip to content

OIDC Federated Authentication

Purpose

This document describes the OpenID Connect (OIDC) federated identity configuration used by GitHub Actions CI/CD pipelines to authenticate to Azure and AWS without long-lived credentials.

Why OIDC

Traditional CI/CD authentication uses long-lived service account credentials (client secrets, access keys) stored in GitHub Secrets. OIDC eliminates these by establishing a trust relationship between GitHub's identity provider and Azure/AWS. Each workflow run receives a short-lived token that expires after the job completes.

Benefits:

  • No credentials to rotate or manage
  • No risk of credential leakage from GitHub Secrets
  • Each workflow run gets a unique, short-lived token
  • Trust is scoped to specific repositories and branches

Azure Configuration

Azure PROD Tenant (d477c9f8 — ciriusgroup.com)

App RegistrationRepositoryFederated Credential SubjectPurpose
github-actions-azure-infraazure-infrarepo:Cirius-Group-Inc/azure-infra:ref:refs/heads/mainTerraform apply — main branch
github-actions-azure-infraazure-infrarepo:Cirius-Group-Inc/azure-infra:environment:productionTerraform apply — environment protection gate
github-deploy-mainops-automationrepo:Cirius-Group-Inc/ops-automation:ref:refs/heads/mainCost reporting, HIPAA audit, SES send, EDL upload, backup audit
github-actions-maesterops-automationrepo:Cirius-Group-Inc/ops-automation:ref:refs/heads/mainMaester M365 security audit (Graph API read-only)
cirius-findings-trackerbedrock-hubrepo:Cirius-Group-Inc/bedrock-hub:ref:refs/heads/mainSecOps platform Container App deploys

github-actions-azure-infra has two federated credentials — both are required. The main credential covers direct branch pushes; the environment:production credential covers environment-gated deploys that require reviewer approval before the job runs.

github-deploy-main is the multi-purpose ops-automation role. It holds policies for Cost Explorer read, SES send, cross-account backup audit assume-role, pentest target discovery, and EDL S3 upload. This is an intentional consolidation — ops-automation workflows run from a single trusted identity.

Azure DDE Tenant (ff1c5d68 — ciriusdde.com)

App RegistrationRepositoryFederated Credential SubjectPurpose
github-deploy-appazure-dde-infrarepo:Cirius-Group-Inc/azure-dde-infra:ref:refs/heads/mainTerraform apply for DDE infrastructure
github-deploy-appops-automationrepo:Cirius-Group-Inc/ops-automation:ref:refs/heads/mainops-automation workflows authenticating to DDE tenant
kobe-auditops-automationrepo:Cirius-Group-Inc/ops-automation:ref:refs/heads/mainDDE Maester / security audit (Graph API read-only, DDE tenant)

github-deploy-app has two separate federated credentials: one for azure-dde-infra (DDE infrastructure repo) and one for ops-automation (compliance pipelines). This allows ops-automation workflows to authenticate independently to the DDE tenant without sharing credentials with the azure-dde-infra pipeline.

kobe-audit is the DDE-tenant equivalent of github-actions-maester. It holds the Maester Graph API permissions (read-only) and the DDE security agent permissions for the DDE tenant.

RBAC Assignments

The app registrations above are granted the following roles at subscription level:

App RegistrationAzure RoleScope
github-actions-azure-infraContributorPROD subscription
github-actions-azure-infraStorage Blob Data ContributorTerraform state storage account
github-deploy-appContributorDDE subscription
github-deploy-appStorage Blob Data ContributorDDE Terraform state storage account
github-actions-maesterReaderPROD subscription
kobe-auditReaderDDE subscription
cirius-findings-tracker(Container App scoped)rg-logging-logs

GitHub Actions Workflow Snippet (Azure)

yaml
permissions:
  id-token: write
  contents: read

- name: Azure Login
  uses: azure/login@v1
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

AWS Configuration

AWS uses an IAM OIDC identity provider trusting GitHub's token endpoint (token.actions.githubusercontent.com), with IAM roles scoped to specific repositories via trust policy conditions.

Management Account (206820231356)

IAM RoleRepositoryTrust Condition SubjectPurpose
github-actions-aws-infraaws-infrarepo:Cirius-Group-Inc/aws-infra:ref:refs/heads/mainTerraform apply for all AWS accounts
github-oidc-cost-reportingops-automationrepo:Cirius-Group-Inc/ops-automation:ref:refs/heads/mainCost reporting, backup audit, SES send, pentest discovery, EDL upload

Both roles live in the management account (206820231356). Cross-account access uses role assumption from these management account roles to per-account roles — only the management account roles have GitHub OIDC trust. All member accounts are reached via sts:AssumeRole chained from here.

github-oidc-cost-reporting is the AWS counterpart to github-deploy-main in Azure: the single ops-automation identity that holds all read-only + email-send permissions for compliance workflows.

OIDC Provider Configuration

One OIDC provider exists in the management account, shared by both roles:

hcl
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

GitHub Actions Workflow Snippet (AWS)

yaml
permissions:
  id-token: write
  contents: read

- name: Configure AWS Credentials (infra deploy)
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::206820231356:role/github-actions-aws-infra
    aws-region: us-west-2

- name: Configure AWS Credentials (ops-automation)
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::206820231356:role/github-oidc-cost-reporting
    aws-region: us-west-2

Multi-Tenant Workflow (ops-automation)

The ops-automation repository authenticates to all three environments (Azure PROD, Azure DDE, AWS) within the same workflow runs. Each environment requires a separate OIDC exchange:

EnvironmentApp / RoleSecret Names
Azure PRODgithub-deploy-mainAZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID
Azure DDEgithub-deploy-app (DDE)AZURE_DDE_CLIENT_ID, AZURE_DDE_TENANT_ID, AZURE_DDE_SUBSCRIPTION_ID
Azure PROD Maestergithub-actions-maesterMAESTER_CLIENT_ID, AZURE_TENANT_ID
Azure DDE Maesterkobe-audit (DDE)DDE_AUDIT_CLIENT_ID, AZURE_DDE_TENANT_ID
AWSgithub-oidc-cost-reportingAWS_ROLE_ARN, AWS_REGION

All federated credential subjects are repo:Cirius-Group-Inc/ops-automation:ref:refs/heads/main — each pointing to a different app registration or IAM role in their respective environment. A single workflow job can only hold one AWS credential at a time; re-run configure-aws-credentials to switch roles within a job.

Common Failure Modes

AADSTS70021: No matching federated identity record found

Cause: The GitHub OIDC token subject does not exactly match any federated credential on the app registration.

The subject is constructed as:

  • Branch run: repo:<org>/<repo>:ref:refs/heads/<branch>
  • Tag run: repo:<org>/<repo>:ref:refs/tags/<tag>
  • Environment-gated run: repo:<org>/<repo>:environment:<environment-name>
  • PR: repo:<org>/<repo>:pull_request

Common mistakes:

  • Org name casing: CiriusGroup or cirius-group instead of Cirius-Group-Inc
  • Repo name mismatch: any typo in the repo name
  • Branch name: master vs main, or a feature branch that isn't in the credential
  • Environment name mismatch: Production vs production — case-sensitive

Fix: Open the app registration in Entra ID → Certificates & secrets → Federated credentials. Compare the Subject identifier field character-by-character with the workflow's actual subject.

Error: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity

Cause: AWS IAM trust policy condition does not match the token subject.

Fix: Check the IAM role trust policy StringLike condition. The subject pattern must use the exact form repo:Cirius-Group-Inc/<repo>:ref:refs/heads/<branch>. Wildcards (*) work in StringLike but not StringEquals.

Error: Missing id-token permission

Cause: The workflow job is missing permissions: id-token: write. Without this, GitHub will not issue an OIDC token.

Fix: Add to the job or workflow level:

yaml
permissions:
  id-token: write
  contents: read

Token expired between jobs

Cause: OIDC tokens are short-lived (15 minutes). If a job waits a long time before calling azure/login or configure-aws-credentials (e.g., waiting on a long previous job), the token may expire.

Fix: Call the login step early in the job, not after long setup steps. Each job gets a fresh token at start.

Auth succeeds but wrong subscription targeted

Cause: AZURE_SUBSCRIPTION_ID secret points to the wrong subscription, or a workflow step runs against the default subscription instead of the intended one.

Fix: Always explicitly pass subscription-id in the azure/login step. Confirm the secret value targets the intended subscription.

403 Forbidden on resource operations after successful login

Cause: OIDC token authenticated successfully but the app registration lacks the required RBAC role on the target resource.

Fix: Verify Contributor (or resource-specific) role assignment on the target subscription/resource group. Note that RBAC assignments can take 1-2 minutes to propagate after creation.

How to Add a New Repository to OIDC

Azure — Adding a New Repo Trust

  1. Identify the app registration that should trust the new repo. For infrastructure repos, use the existing tenant-specific app registration. For ops-automation type repos, create a new app registration with least-privilege RBAC.

  2. Add a federated credential via Terraform in azure-infra/entra/ (PROD) or azure-dde-infra/entra/ (DDE):

hcl
resource "azuread_application_federated_identity_credential" "new_repo_main" {
  application_id = azuread_application.your_app.id
  display_name   = "new-repo-main"
  description    = "Federated identity for new-repo main branch"
  issuer         = "https://token.actions.githubusercontent.com"
  subject        = "repo:Cirius-Group-Inc/new-repo:ref:refs/heads/main"
  audiences      = ["api://AzureADTokenExchange"]
}
  1. Grant RBAC — minimum required roles only. Prefer resource-group scope over subscription scope.

  2. Add secrets to the new repo in GitHub: AZURE_CLIENT_ID (client ID of the app registration), AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID.

  3. Update this document with the new trust entry.

AWS — Adding a New Repo Trust

  1. Prefer reusing an existing role (github-actions-aws-infra for infra, github-oidc-cost-reporting for ops-automation). Add new permissions to the existing role rather than creating a new role unless the new repo has fundamentally different trust requirements.

  2. If a new role is required, add it to aws-infra/main/ with a trust policy referencing the existing OIDC provider (via data "aws_iam_openid_connect_provider" "github"):

hcl
resource "aws_iam_role" "github_new_workflow" {
  name = "github-oidc-new-workflow"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = data.aws_iam_openid_connect_provider.github.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
        StringLike   = { "token.actions.githubusercontent.com:sub" = "repo:Cirius-Group-Inc/new-repo:ref:refs/heads/main" }
      }
    }]
  })
}
  1. Add AWS_ROLE_ARN secret to the new repo pointing to the new role ARN.

  2. Update this document with the new trust entry.

Document History

DateChangeAuthor
April 2026Expanded Azure trust tables (split by tenant, added kobe-audit DDE, cirius-findings-tracker, second azure-infra federated credential); expanded AWS trust table (added github-oidc-cost-reporting); updated multi-tenant section with full secret mapping; replaced troubleshooting table with Common Failure Modes; added How to Add a New Repository sectionKobe
March 2026Added Maester app registration (OIDC WIF), cost reporting unified role (github-deploy-main), and explicit DDE ops-automation trust to Azure configuration table and multi-tenant workflow sectionRory
March 2026Corrected GitHub org name to Cirius-Group-Inc (was incorrectly CiriusGroup), filled in app registration names, added AWS OIDC provider config, added multi-tenant workflow section, expanded troubleshooting tableRory
March 2026Initial draftRory

Internal use only — Cirius Group