Cloud Architecture & Landing Zones

Identity Federation & SSO

18 min Lesson 4 of 28

Identity Federation & SSO

In a multi-account AWS organization, every team, application, and automated pipeline needs to authenticate. Naively, this means thousands of IAM users scattered across dozens of accounts — each with its own password policy, MFA state, and access-key rotation schedule. At scale that model collapses: a single off-boarded employee might still hold active credentials in seven accounts. Identity federation solves this by making your company's existing identity provider (IdP) the single source of truth and issuing short-lived AWS credentials on demand. No persistent IAM users, no long-lived access keys, no off-boarding gaps.

The Core Concept: Federated Trust

Federation works by establishing trust between two parties: an Identity Provider (IdP) — Okta, Azure AD, Google Workspace, or any SAML/OIDC-compliant system — and a Service Provider (SP), in this case AWS. The IdP asserts who a user is (authentication) and what groups they belong to (attributes). AWS maps those assertions to IAM roles and issues temporary STS credentials valid for 1–12 hours. When the session expires the user re-authenticates; there is nothing to revoke because the credentials never persist.

Identity Federation Trust Flow Identity Provider (Okta / Azure AD) SAML 2.0 / OIDC Engineer / CI Bot authenticates IAM Identity Center Permission Sets Account Assignments Portal (sso.aws.amazon.com) AWS STS Temporary Credentials Production Acct IAM Role assumed Staging Acct IAM Role assumed Dev Acct IAM Role assumed SAML assertion AssumeRole
Federated identity flow: the IdP asserts identity, IAM Identity Center maps it to permission sets, and STS issues short-lived credentials per target account.

SAML 2.0 vs OIDC — When to Use Which

SAML 2.0 (Security Assertion Markup Language) is the older, XML-based standard. It is the right choice when your corporate IdP is Okta, Azure AD, or PingFederate and you are federating human users into the AWS console or the IAM Identity Center portal. SAML tokens are large but the round-trip is browser-based so size is not a practical constraint.

OIDC (OpenID Connect) is the modern, JSON/JWT-based protocol built on OAuth 2.0. It is the right choice for machine identities: a GitHub Actions workflow, a GitLab CI job, or a Kubernetes service account that needs to assume an IAM role without storing a long-lived access key. OIDC tokens are compact, verifiable with a public JWKS endpoint, and fit naturally into automated pipelines.

Rule of thumb at big tech: human SSO goes through SAML via IAM Identity Center; CI/CD and workload federation goes through OIDC directly against an IAM identity provider. Never mix the two use cases — SAML tokens cannot be used in headless pipelines and OIDC alone does not give you the portal-based account switcher that engineers need.

AWS IAM Identity Center (Successor to AWS SSO)

IAM Identity Center is the AWS-native control plane for federated access. It lives in the management account, syncs with your IdP via SCIM (automatic user/group provisioning), and lets you assign Permission Sets to groups across accounts. An engineer in the platform-engineers Okta group can get AdministratorAccess in sandbox accounts and ReadOnlyAccess in production — all from a single portal URL, no account-specific IAM user needed.

Setting up Okta as an external IdP for IAM Identity Center involves three steps: register the AWS app in Okta, download the Okta SAML metadata, and upload it to IAM Identity Center. Then enable SCIM so group membership changes in Okta propagate automatically within minutes rather than requiring manual IAM updates.

# Step 1 — Enable IAM Identity Center in the management account (one-time) # Done via Console or via Control Tower; it creates the SSO service-linked role automatically. # Step 2 — Configure Okta as external IdP via AWS CLI aws sso-admin create-identity-provider \ --instance-arn "arn:aws:sso:::instance/ssoins-EXAMPLE" \ --identity-provider-type "SAML" \ --identity-provider-config file://okta-metadata.xml # Step 3 — Create a Permission Set (e.g., PlatformEngineer) aws sso-admin create-permission-set \ --instance-arn "arn:aws:sso:::instance/ssoins-EXAMPLE" \ --name "PlatformEngineer" \ --description "Full access to infra accounts, read-only in prod" \ --session-duration "PT8H" # max session 8 hours # Attach AWS managed policy to the permission set aws sso-admin attach-managed-policy-to-permission-set \ --instance-arn "arn:aws:sso:::instance/ssoins-EXAMPLE" \ --permission-set-arn "arn:aws:sso:::permissionSet/ssoins-EXAMPLE/ps-EXAMPLE" \ --managed-policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" # Step 4 — Assign the group + permission set to a target account aws sso-admin create-account-assignment \ --instance-arn "arn:aws:sso:::instance/ssoins-EXAMPLE" \ --target-id "111122223333" \ # AWS Account ID --target-type "AWS_ACCOUNT" \ --permission-set-arn "arn:aws:sso:::permissionSet/ssoins-EXAMPLE/ps-EXAMPLE" \ --principal-type "GROUP" \ --principal-id "abcd1234-OKTA-GROUP-ID" # from SCIM sync

OIDC Federation for CI/CD Pipelines

The most important machine-identity pattern in modern DevOps is OIDC federation for CI/CD. Instead of storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as repository secrets (which rotate poorly and leak through log accidents), your pipeline exchanges a short-lived OIDC token issued by GitHub Actions (or GitLab CI, CircleCI, etc.) for temporary AWS credentials via sts:AssumeRoleWithWebIdentity. The IAM trust policy scopes which repos and branches can assume the role — a leaked token from a fork cannot assume your production deploy role.

# Terraform: create the OIDC provider for GitHub Actions (one-time per account) resource "aws_iam_openid_connect_provider" "github" { url = "https://token.actions.githubusercontent.com" client_id_list = ["sts.amazonaws.com"] thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"] } # IAM role that GitHub Actions workflows can assume resource "aws_iam_role" "github_deploy" { name = "github-actions-deploy" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.github.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { # Only the main branch of a specific repo can deploy to prod "token.actions.githubusercontent.com:sub" = "repo:acme-org/platform:ref:refs/heads/main" } } }] }) } # GitHub Actions workflow step using the role (no stored secrets) # jobs: # deploy: # permissions: # id-token: write # REQUIRED — grants the job an OIDC token # contents: read # steps: # - uses: aws-actions/configure-aws-credentials@v4 # with: # role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy # aws-region: us-east-1
Production pitfall — overly broad trust conditions: A common mistake is setting the sub condition to repo:acme-org/* (wildcard over all repos) or omitting the branch restriction entirely. This means any repository in your GitHub org — including a compromised or newly-created one — can assume your production deploy role. Always scope to the specific repo and branch. For multi-environment setups, create separate roles per environment with branch-scoped conditions: refs/heads/main for prod, refs/heads/release/* for staging.

AWS CLI Profile Setup for SSO

Engineers authenticate through the IAM Identity Center portal and then use aws sso login to populate local credentials. The AWS CLI v2 natively supports SSO-backed profiles. Each profile maps to an account + permission set combination, enabling engineers to switch contexts with --profile or the AWS_PROFILE environment variable.

# ~/.aws/config — one profile per account/role the engineer needs [profile platform-sandbox] sso_session = acme-sso sso_account_id = 111122223333 sso_role_name = PlatformEngineer region = us-east-1 output = json [profile platform-prod-readonly] sso_session = acme-sso sso_account_id = 444455556666 sso_role_name = ReadOnlyAccess region = us-east-1 output = json [sso-session acme-sso] sso_start_url = https://acme.awsapps.com/start sso_region = us-east-1 sso_registration_scopes = sso:account:access # Authenticate (opens browser, caches token for 8 hours) aws sso login --sso-session acme-sso # Use a specific account — no MFA prompts, no key rotation needed aws s3 ls --profile platform-prod-readonly aws ec2 describe-instances --profile platform-sandbox

Governance and Audit Considerations

Every AssumeRole call lands in AWS CloudTrail with the federation source identity attached (sourceIdentity field). This means you can answer "who accessed the production database at 2 AM" with a single Athena query across your org's CloudTrail lake — the human's corporate email address, not an anonymous role ARN. Enforce sts:SetSourceIdentity in your IAM Identity Center permission set boundary and require it in all service control policies so no pipeline can assume a role without propagating the originating identity.

Key architecture decision: Centralize identity in a single IdP and never create long-lived IAM users for humans in member accounts. Treat IAM users as a break-glass mechanism only (one per account, MFA-protected, credentials in Secrets Manager, access logged and alarmed). The moment you allow teams to self-manage IAM users, the federated trust model breaks — it only takes one hardcoded key in a GitHub repo to expose the entire account.