Create a minimal‑privilege root role that only trusts a limited set of admin roles, then layer downstream roles that inherit only the permissions they need via permission boundaries and explicit deny.
1. Define the top‑level root role – Use aws iam create-role with a trust policy that allows only the organization’s management account (OU) to assume it. Example:
aws iam create-role \
--role-name OrgRootRole \
--assume-role-policy-document file://trust-org.json2. Attach a permission boundary that caps the maximum actions any child role can obtain. Sample boundary JSON:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["ec2:*", "s3:*", "dynamodb:*"],
"Resource": "*"
}]
}3. Create tier‑1 admin roles (e.g., AccountAdmin) that can iam:PassRole for all downstream roles and manage resources within the account. Grant iam:CreateRole, iam:AttachRolePolicy, and organizations:Describe.
4. Define tier‑2 operational roles (e.g., OpsReadOnly) with ec2:Describe, s3:GetObject, and cloudwatch:Read. Scope them to specific resource ARNs using condition keys like aws:ResourceTag/Env.
5. Build tier‑3 application roles that are assumed by services (ECS, Lambda). Restrict them to the exact resources they need, e.g., dynamodb:Query on arn:aws:dynamodb:us-east-1:123456789012:table/Orders.
6. Mirror the hierarchy in GCP by creating an organization‑level custom role, then project‑level roles with iam.serviceAccounts.actAs and roles/owner limited by resource.name conditions.
7. Audit continuously – Enable IAM Access Analyzer, CloudTrail Event History, and GCP Cloud Asset Inventory. Set alerts for iam:PassRole usage exceeding a 30‑day rolling average.
| Layer | Typical Scope | Example Permissions |
|---|---|---|
| 0 (Root) | Org‑wide admin | iam: on |
| 1 (Admin) | Account admin | iam:CreateRole, iam:PassRole, s3:ListAllMyBuckets |
| 2 (Ops) | Service ops | ec2:Describe, s3:GetObject |
| 3 (App) | Application role | dynamodb:Query on specific table |
Gotcha: Permission boundaries, SCPs, and explicit deny statements are evaluated independently; a deny in any one layer overrides allows elsewhere, so missing a deny can cause silent permission failures.