Identity Plane — L1 Workforce Identity
Layer 1 (L1) workforce identity using AWS IAM Identity Center federated from Entra ID for centralized workforce access.
L1 Identity Architecture
Entra ID (Microsoft)
↓ (SAML 2.0 Federation)
AWS IAM Identity Center (Hub)
↓ (Permission Sets)
├─ Management Account (Organizations, Billing)
├─ B2B Workload Account (ECS, RDS, S3)
└─ Additional Accounts (Future)
Identity Sources
| Layer | System | Use Case | Admin |
|---|---|---|---|
| L1 (Workforce) | IAM Identity Center + Entra ID | AWS API/Console for engineers and operators | IT/Entra admin |
| L2 (Customer) | Keycloak (on ECS) | B2B Commerce storefront login | Application team |
Non-conflation principle (ADR-020): Workforce and customer identity are completely separate. Operators cannot impersonate customers; customers cannot access AWS.
IAM Identity Center Configuration
Permission Sets
Permission sets define AWS API permissions accessible from a workload account.
# PowerUser permission set (read + write, no IAM changes)
resource "aws_ssoadmin_permission_set" "power_user" {
name = "PowerUser"
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
session_duration = "PT1H" # 1-hour session
}
resource "aws_ssoadmin_managed_policy_attachment" "power_user" {
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
permission_set_arn = aws_ssoadmin_permission_set.power_user.arn
managed_policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}
# ReadOnly permission set
resource "aws_ssoadmin_permission_set" "read_only" {
name = "ReadOnly"
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
session_duration = "PT4H" # 4-hour read-only session
}
Group and Account Assignment
# Engineering group in Entra ID (synced automatically)
# Assign PowerUser permission set to engineering group in B2B workload account
resource "aws_ssoadmin_account_assignment" "engineering_poweruser" {
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
permission_set_arn = aws_ssoadmin_permission_set.power_user.arn
principal_id = data.aws_identitystore_group.engineering.id # From Entra sync
principal_type = "GROUP"
target_account_id = var.workload_account_id
target_type = "AWS_ACCOUNT"
}
# Operations group: ReadOnly on B2B account, PowerUser on management account (billing)
resource "aws_ssoadmin_account_assignment" "ops_readonly" {
instance_arn = data.aws_ssoadmin_instances.main.arns[0]
permission_set_arn = aws_ssoadmin_permission_set.read_only.arn
principal_id = data.aws_identitystore_group.operations.id
principal_type = "GROUP"
target_account_id = var.workload_account_id
target_type = "AWS_ACCOUNT"
}
Entra ID Federation Setup
Prerequisites
- Management account: Identity Center enabled (console) before Terraform apply
- Entra ID tenant: Admin credentials and application registration
- Email domain: Verify custom domain in Entra ID
SAML Trust Configuration
# Fetch Identity Center SAML metadata
data "aws_ssoadmin_instances" "main" {}
# Create Entra ID enterprise application for AWS SAML
# (Manual step: Azure portal → Enterprise Applications → New App)
# - Download metadata from Identity Center console
# - Upload to Entra ID as SAML certificate
# - Configure Name ID as "user.mail"
# Verify SAML federation working:
# 1. Identity Center console → Users → Sync status
# 2. Check groups and users appear from Entra ID
# 3. Test login: https://d-9767913734.awsapps.com/start
Operator Workflow
# Step 1: Authenticate via Entra ID
aws sso login --profile my-workload
# Step 2: Credentials cached locally (~12 hours)
# ~/.aws/sso/cache/*.json contains temporary credentials
# Step 3: Use profile in CLI commands
AWS_PROFILE=my-workload aws sts get-caller-identity
# Output: assumes identity as temporary role in workload account
# Step 4: Session expires after permission-set duration (1-4 hours)
# Automatic re-auth on next command
Security Controls
| Control | Implementation | Verification |
|---|---|---|
| MFA | Entra ID Conditional Access (require MFA for AWS sign-in) | Check Entra ID policy |
| Session Duration | Permission sets max 12 hours; identity provider policy enforces lower (1-4h) | aws sts get-session-token shows expiry |
| Audit Trail | CloudTrail captures all assumed-role operations in workload accounts | aws cloudtrail lookup-events --lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::IAM::Role |
| Credential Rotation | Automatic; Identity Center issues new credentials every 1-4 hours | Check /tmp/.aws/sso/cache/ timestamp |
| Cross-Account Restrictions | Permission sets scoped to specific accounts; no wildcard account access | Try assuming role in unauthorized account → denied |
Compliance Implications
- SOC 2: Centralized user provisioning/deprovisioning via Entra ID
- APRA CPS 234: Audit trail via CloudTrail (all API calls logged with assumed identity)
- PCI DSS: MFA enforced; session duration <4 hours
- ISO 27001: SAML 2.0 federation with encryption in transit
Known Limitations & Workarounds
| Limitation | Reason | Workaround |
|---|---|---|
| No permanent access keys from Identity Center | By design (session-based only) | Use IAM users (non-recommended) or service roles with OIDC |
| Entra ID group sync delay | Graph API eventual-consistency | 15-minute eventual sync; manual refresh available in console |
| No conditional access on AWS API (only console) | Identity Center limitation | Use VPC IP restrictions or API Gateway authentication |
Source Reference
Module: terraform-aws/modules/sso/ — Permission sets, account assignments, Entra ID SAML binding
Account configuration: terraform-aws/accounts/management-account/ — Identity Center instance setup
Related documentation: For the architectural decision rationale on two-layer identity (L1 workforce vs L2 customer), see the b2b-commerce main ADRs in the root repository.