Least privilege
Least privilege means every identity, human or machine, holds only the permissions its role needs, for only as long as it needs them. In the BuiltForProd AWS Baseline, people sign in through IAM Identity Center to role-based permission sets that narrow toward production, pipelines authenticate with GitHub OIDC and assume a scoped deployer role, and no long-lived access key exists anywhere.
Why it matters in production
Access that grew one exception at a time is how a former contractor still has admin, how a leaked key stays valid for six months, and how a session meant for development deletes something in production. Least privilege limits what a mistaken or compromised identity can do, and short-lived credentials limit for how long. It is also the control every security questionnaire asks about first.
How the platform applies it
People: groups, permission sets, and production read-only
Human access is IAM Identity Center only, administered from core-identity. Groups carry the ACME_ prefix and are created in the identity provider; the iam-identity-center unit looks them up by name and assigns permission sets per account from one declarative map. Sessions last twelve hours; there are no IAM users.
| Group | Core accounts | sandbox, dev, staging | prod |
|---|---|---|---|
ACME_PlatformLeads | PlatformAdmin | PlatformAdmin | PlatformAdmin |
ACME_PlatformEngineers | PlatformEngineer | PlatformEngineer | ReadOnly |
ACME_DevOpsLeads | ReadOnly | DevOpsAdmin | DevOpsAdmin |
ACME_DevOpsEngineers | ReadOnly | DevOpsEngineer | ReadOnly |
ACME_AppDevelopers, ACME_ETLEngineers, ACME_AIEngineers | none | sandbox and dev (leads also staging) | ReadOnly |
ACME_SecurityAuditors | SecurityAuditor | SecurityAuditor | SecurityAuditor |
Write access narrows as environments approach production: engineers hold PowerUser in lower environments and ReadOnly in prod; only Platform Leads and DevOps Leads can change prod. The auditor permission sets add an explicit deny on reading data and secrets (S3 objects, SSM parameters, Secrets Manager, DynamoDB, RDS data, log events), so an auditor can inspect configuration without reading what it protects.
Pipelines: OIDC, then a role chain
No AWS key is stored in GitHub. The OIDC provider lives in core-auto; each infrastructure repository has its own acme-<repo>-deployer role that trusts its own repository and only the subjects the delivered workflows present: ref:refs/heads/main, pull_request and environment:*. A workflow on any other branch is refused. That role chains into acme-terraform-deployer in each target account, which account-baseline creates and which trusts only core-auto roles matching acme-*-deployer. The landing-zone role may assume the deployer in any account; blueprint roles only in the workload accounts plus the state-access role. The secrets repository has its own provider and acme-secrets-syncer role in core-security.
Cross-account roles express one relationship each
acme-terraform-state-access (state bucket read and write), acme-route53-cross-account (validation and ExternalDNS records into the public and private zones), acme-ssm-cross-account (read /acme/* parameters). Each role has a narrow policy; there is no broad trust between accounts.
Secrets: per-stage keys
Application secrets are SOPS-encrypted in acme-aws-blueprint-secrets with one KMS key per stage (acme-sops-dev, acme-sops-staging, acme-sops-prod, acme-sops-sandbox). Key policy, not GitHub, decides who can decrypt: every engineer group can use the sandbox and dev keys; only ACME_PlatformLeads and ACME_DevOpsLeads can use staging and prod; the sync role can decrypt only. Inside the cluster, the External Secrets Operator's store is scoped to /acme/usw2/<stage>/*, and the application's service account can read only its own path.
Workloads: Pod Identity, no node-wide permissions
Kubernetes workloads get AWS permissions through EKS Pod Identity (the load balancer controller, ExternalDNS, External Secrets and the application service account), with IAM Roles for Service Accounts only where a component cannot use Pod Identity (Fluent Bit). Nothing runs on node role permissions.
Guardrails above IAM
The acme-security-guardrails service control policy denies creating IAM users, login profiles and access keys in every member account, and denies any action without MFA unless the principal is an SSO session (MFA is enforced by the identity provider), a platform role named acme-*, a service-linked role, root or a role tagged mfa-exempt = true. The policy holds even against an account administrator; see defense in depth.
Worked example: a developer's day
A developer in ACME_AppDevelopers runs aws sso login --profile acme-dev, gets a twelve-hour session with AppDeveloperAccess in plat-dev, and can build and debug there. The same login gives ReadOnly in plat-prod, so a production incident can be inspected but not changed. To change a dev secret, the developer edits dev/sample.yaml with SOPS (the dev key allows it) and opens a pull request; the same edit in prod/ fails at decryption because the prod key policy excludes the group. To ship, the developer merges code and lets the pipeline, holding the deployer role, do the rest, as described under GitOps.
Common mistakes
- Creating an IAM user for a tool. The guardrail policy denies it. Use an Identity Center group for people or an OIDC-federated role for automation.
- Granting a group prod write access to save time. Prod is read-only for every role except the two lead groups by design; a change to prod goes through a pull request.
- Naming a workload role outside the namespace without the tag. A role that is not
acme-*and not taggedmfa-exempt = trueis denied by the MFA statement; the symptoms areAccessDeniedorClient.InternalErroron EBS and KMS. - Storing an AWS key in GitHub secrets. The pipelines need only
OIDC_ROLE_ARN; a stored key is a long-lived credential the design set out to remove. - Treating GitHub permissions as the secrets boundary. Who can decrypt
prod/is decided by the KMS key policy, not by repository access.