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