What you will learn
- AWS Organizations — multi-account management, OUs, consolidated billing, and what SCPs actually do
- SCP blocklist vs allowlist strategies and how inheritance flows down the OU tree
- Why the management account is never affected by SCPs — the most commonly tested fact
- IAM advanced conditions — SourceIp, RequestedRegion, ResourceTag, PrincipalTag, MFA required
- IAM Roles vs Resource-Based Policies — when each is correct for cross-account access
- Permission Boundaries — the ceiling on what a user or role can ever do
- The exact IAM policy evaluation order — Deny, SCP, Resource Policy, Identity Policy, Boundary
- IAM Identity Center — SSO for all accounts and business apps from one login
- Microsoft Active Directory on AWS — three options and when each applies
- AWS Control Tower — automated multi-account governance with preventive and detective guardrails
Why this matters
A startup running everything in one AWS account with one admin user gets away with basic IAM. A company like Razorpay or Hotstar running production, staging, dev, security, and logging in separate accounts — with hundreds of engineers across teams — needs a completely different approach. Getting this wrong means either locking engineers out of what they need to do their jobs, or leaving production open to accidental or malicious changes. SCPs, Permission Boundaries, and IAM Identity Center are the building blocks that serious AWS environments use to manage access at scale without creating security gaps or operational friction.
AWS Organizations
AWS Organizations lets you manage multiple AWS accounts from a single place under one organisational structure.
Key facts:
Global serviceOne management account — has full control, never restricted by SCPsAll other accounts are member accounts — each can only belong to one orgConsolidated billing — one payment method, one invoice, discounts aggregateVolume discounts — EC2 and S3 pricing improves as combined usage growsShared Reserved Instances and Savings Plans across all accountsStructure:
Root OU (top of the tree — one per organisation) | +-- Management Account (SCPs never apply here, full power always) | +-- OU (Dev) | +-- Member Account A | +-- Member Account B | +-- OU (Prod) +-- OU (Finance) | +-- Member Account C +-- OU (Platform) +-- Member Account DWhy multiple accounts instead of multiple VPCs?
Separate accounts: Complete blast radius isolation — breach in one cannot affect another Clear billing per team or project Independent permission boundaries Cannot be replicated with VPCs inside one accountWays to organise OUs:
Business Unit → Sales OU, Retail OU, Finance OUEnvironment → Prod OU, Dev OU, Test OUProject → Project A OU, Project B OUService Control Policies
SCPs restrict what IAM users and roles can do inside an account or OU. They are not permission grants — they set the maximum permissions that can ever be used.
Key rules:
Applied at OU level or individual account levelManagement account is NEVER affected by SCPs — everDefault behavior: deny everything — an explicit Allow must exist at every levelSCPs do not grant permissions — they only restrict what is already allowedEven a user with AdministratorAccess is limited by whatever the SCP allowsBlocklist strategy — allow everything, deny specific services:
{ "Statement": [ {"Effect": "Allow", "Action": "*", "Resource": "*"}, {"Effect": "Deny", "Action": "dynamodb:*", "Resource": "*"} ]}Result: All services allowed except DynamoDB. Use when most services are fine and you just need to block a few risky ones.
Allowlist strategy — only specific services permitted:
{ "Statement": [ {"Effect": "Allow", "Action": ["ec2:*", "cloudwatch:*", "s3:*"], "Resource": "*"} ]}Result: Only EC2, CloudWatch, and S3 allowed. Everything else blocked. Use for locked-down sandbox or restricted environments.
SCP inheritance — how it flows down the tree:
Root OU (FullAWSAccess) | +-- Management Account → can do anything (SCP never applies) | +-- Sandbox OU (Deny S3) | +-- Account A → can do anything EXCEPT S3 (inherited from Sandbox OU) | +-- Account B → can do anything EXCEPT S3 AND EC2 (extra deny on account) | +-- Workloads OU +-- Test OU (Allow EC2 only) | +-- Account D → EC2 only (Allow EC2 at this OU) | +-- Prod OU (FullAWSAccess) +-- Account E → can do anythingA Deny at a parent OU automatically applies to all child accounts. An account inherits all policies from every OU above it all the way to Root.
RememberThe management account is completely exempt from all SCPs always. Never put actual workloads in the management account — it cannot be restricted by any SCP.
IAM Advanced Conditions
Conditions add extra rules to policies for precise access control beyond just service and action.
aws:SourceIp — restrict by caller's IP address (where request comes FROM):
{ "Effect": "Deny", "Action": "*", "Resource": "*", "Condition": { "NotIpAddress": { "aws:SourceIp": ["192.0.2.0/24", "203.0.113.0/24"] } }}Deny everything if the request does not come from these IP ranges — only office network allowed.
aws:RequestedRegion — restrict by target AWS region (where request goes TO):
{ "Effect": "Deny", "Action": ["ec2:*", "rds:*", "dynamodb:*"], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["ap-south-1", "ap-southeast-1"] } }}Deny EC2, RDS, and DynamoDB in every region except Mumbai and Singapore.
Rememberaws:SourceIp = controls where you call FROM (caller's IP). aws:RequestedRegion = controls which region you call TO (target region). Common trick question that catches engineers who confuse the two.
ec2:ResourceTag and aws:PrincipalTag — restrict by tags:
{ "Effect": "Allow", "Action": ["ec2:StartInstances", "ec2:StopInstances"], "Resource": "*", "Condition": { "StringEquals": { "ec2:ResourceTag/Project": "DataAnalytics", "aws:PrincipalTag/Department": "Data" } }}Allow start/stop only if the EC2 instance has Project=DataAnalytics AND the caller has Department=Data on their IAM identity.
ec2:ResourceTag → checks the resource being accessedaws:PrincipalTag → checks the person doing the accessingaws:MultiFactorAuthPresent — require MFA for sensitive actions:
{ "Statement": [ {"Effect": "Allow", "Action": "ec2:*", "Resource": "*"}, { "Effect": "Deny", "Action": ["ec2:StopInstances", "ec2:TerminateInstances"], "Resource": "*", "Condition": { "BoolIfExists": {"aws:MultiFactorAuthPresent": false} } } ]}Allow all EC2 actions, but deny stop and terminate if MFA was not used during login.
aws:PrincipalOrgID — restrict to your entire AWS Organization:
{ "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject"], "Resource": "arn:aws:s3:::devops-shared-bucket/*", "Condition": { "StringEquals": { "aws:PrincipalOrgID": ["o-yyyyyyyyyy"] } }}Allow access only if the requester belongs to your organization. Cleaner than listing every account ID manually.
IAM Roles vs Resource-Based Policies for Cross-Account Access
Two ways to give cross-account access. They behave very differently.
IAM Role — you go to them:
When you assume a role, you give up your original permissions and take on the role's permissions. You switch identity entirely.
You (Account A) → assume Role in Account B → access S3 in Account BWhile assuming the role: your Account A permissions are PAUSEDResource-Based Policy — they invite you:
The target resource adds you to its allowed list directly. You keep your original identity and permissions.
S3 in Account B has your account on its allowed list → you access S3Your Account A permissions remain ACTIVE the whole timeWhen Resource-Based Policy wins:
Scenario: You are in Account A You need to read DynamoDB in Account A AND write to S3 in Account B simultaneously With IAM Role: Assume role in Account B → Account A permissions PAUSED Can access S3 in Account B ✓ Cannot access DynamoDB in Account A ✗ (permissions paused) With Resource-Based Policy on S3 in Account B: Access S3 directly → Account A permissions ACTIVE Can access DynamoDB in Account A ✓ Can access S3 in Account B ✓RememberIf you need to act in two accounts simultaneously, use a Resource-Based Policy — not a role assumption. Role assumption pauses your original permissions.
IAM Permission Boundaries
A Permission Boundary sets the maximum permissions a user or role can ever have — even if their IAM policy allows more. It acts as a ceiling, not a grant.
Supported for: users and roles only (NOT groups)Does not grant any permissions by itselfEffective permissions = what the IAM Policy allows AND what the Boundary allowsExample:
Permission Boundary allows: s3:*, cloudwatch:*, ec2:*IAM Policy allows: iam:CreateUser Result: DENIEDiam:CreateUser is outside the boundary — blocked even though the policy allows itEffective permissions — the intersection of all three layers:
Organizations SCP ∩Permission Boundary ∩Identity-based Policy─────────────────────= Effective Permissions (only what all three allow simultaneously)Use cases for Permission Boundaries:
Delegate safely: A senior engineer creates IAM users for junior engineers Permission Boundary caps what permissions those users can ever have Junior engineers cannot create users with more permissions than their boundary allows Prevent privilege escalation: Developers can manage their own policies Permission Boundary prevents them from granting themselves admin Restrict one specific user without account-wide SCPs: SCPs are blunt (affect entire OUs) Permission Boundary is precise (affects one user or role)IAM Policy Evaluation Logic
Every API call in AWS goes through this exact sequence. A single NO at any step means DENIED.
1. Explicit Deny → any policy has Deny for this action? → DENIED immediately 2. Organizations SCP → account inside an org with SCP? SCP does not have Allow? → DENIED 3. Resource-Based Policy → resource has Allow? → can be allowed (short circuit) 4. Identity-Based Policy → IAM user or role policy has Allow? → proceed No Allow found? → DENIED 5. Permission Boundary → user or role has a boundary? Boundary does not include this action? → DENIED 6. Session Policy → role session or federated session? Session policy does not allow? → DENIED Final default: Implicit Deny → nothing explicitly allowed → DENIEDSimple memory order: Explicit Deny → SCP → Resource Policy → Identity Policy → Boundary → Session → Allow
Practice example:
{ "Statement": [ {"Action": "sqs:*", "Effect": "Deny", "Resource": "*"}, {"Action": ["sqs:DeleteQueue"], "Effect": "Allow", "Resource": "*"} ]}Can you perform sqs:CreateQueue?→ No. sqs:* is explicitly denied. Explicit deny always wins. Can you perform sqs:DeleteQueue?→ No. Even though there is an Allow for it, sqs:* Deny still wins.→ Statement order inside a policy does not matter.→ Explicit Deny beats any Allow regardless of which statement comes first. Can you perform ec2:DescribeInstances?→ No. No Allow exists for EC2 anywhere. Default is implicit deny.AWS IAM Identity Center
Previously called AWS Single Sign-On. One login for everything — instead of signing into each account separately, you log in once and get access to all of them.
One login → access to: All AWS accounts in your AWS Organization Business apps — Salesforce, Slack, Microsoft 365, Dropbox SAML 2.0 enabled custom apps EC2 Windows instancesIdentity providers — where users come from:
| Option | What it is |
|---|---|
| Built-in Identity Store | Users and groups created directly inside IAM Identity Center |
| Third-party | Connect existing Active Directory, Okta, OneLogin, etc |
Login flow:
User opens IAM Identity Center login page ↓Logs in once with credentials ↓IAM Identity Center verifies identity ↓Shows dashboard with all accounts and apps the user can access ↓User clicks account → directly in, no second login neededPermission Sets:
A collection of IAM policies that defines what a user can do across accounts. Create once in IAM Identity Center, assign to users or groups across multiple accounts.
Example: Group: Developers (Arjun, Priya, Rohan) Permission Set: ReadOnlyAccess → Prod Account A, Prod Account B Permission Set: FullAccess → Dev Account A, Dev Account B Developers log in once, see all four accounts, have full access in Dev and read-only in Prod — managed from one central place.Three types of fine-grained permissions:
Multi-Account Permissions → assign Permission Sets to users/groups across many accountsApplication Assignments → SSO to SAML 2.0 business appsAttribute-Based Access Control (ABAC) → control access based on user attributes like cost center, job title, locale Change a user's attribute → permissions update automaticallyMicrosoft Active Directory on AWS
Active Directory is a Windows directory service that stores user identities, group memberships, and computer registrations centrally. Engineers at most enterprises use AD to log into their laptops and access corporate resources.
AWS gives you three options when you need directory services in the cloud:
AWS Managed Microsoft AD:
AWS creates a full AD for you inside AWS. Your users live in AWS. If you have an on-premises AD, connect both via a trust relationship.
On-prem AD ←── trust ──→ AWS Managed ADNumber of ADs: two — your office AD and the AWS one, linked. Use when: you want a full AD in AWS, may have existing on-premises AD. Supports MFA.
AD Connector:
A proxy that redirects authentication requests to your existing on-premises AD. No users or data move to AWS.
User → AD Connector (proxy in AWS) → On-prem AD → answer returnedNumber of ADs: one — your office AD. AD Connector is just a middleman. Use when: keep all users in existing on-prem AD but use AWS services. Supports MFA.
Simple AD:
A basic AD-compatible directory inside AWS with no connection to on-premises AD.
Number of ADs: one — in AWS only. Use when: no existing AD anywhere, just need something simple. Does NOT support MFA. Cannot join to on-premises AD.
| AWS Managed Microsoft AD | AD Connector | Simple AD | |
|---|---|---|---|
| Users managed in | AWS | On-premises | AWS |
| Connects to on-prem AD | Yes (trust) | Yes (proxy) | No |
| MFA support | Yes | Yes | No |
| Use when | Need full AD in AWS | Keep users on-prem | No on-prem AD needed |
AWS Control Tower
When a large company needs many AWS accounts — dev, prod, finance, HR, security — setting up each one with the right security, policies, and compliance individually is slow and error-prone. Control Tower automates all of this.
Control Tower sets up and governs a secure multi-account environment in a few clicks, following AWS best practices automatically. Uses AWS Organizations and AWS Config under the hood.
Guardrails — rules that apply automatically across all accounts:
Preventive Guardrail: Blocks something from happening using SCPs Example: Restrict all accounts from deploying outside allowed regions Action blocked before it occurs Detective Guardrail: Does not block but monitors and alerts using AWS Config Example: Detect resources with no tags When violation found: Member Account (untagged resource detected) ↓ AWS Config triggers NON_COMPLIANT event ↓ SNS sends notification to Admin ↓ Lambda auto-tags the resource| Preventive Guardrail | Detective Guardrail | |
|---|---|---|
| What it does | Blocks the action upfront | Monitors and alerts after |
| Uses | SCP | AWS Config |
| Example | Cannot deploy outside allowed region | Finds untagged resource, auto-fixes |
Hands-on Lab — Organization, SCP, and Permission Boundary
Step 1 — Create an AWS Organization
AWS Organizations → Create an organizationFeatures: All features (required for SCPs)Create organization You are now the management account.The management account is NEVER affected by SCPs — remember this.Step 2 — Create an Organizational Unit
AWS Organizations → AWS accounts → RootActions → Create new → Organizational unitName: Sandbox → Create OU If you have a member account, move it into Sandbox:Select the account → Actions → Move → select Sandbox → MoveStep 3 — Create an SCP that restricts regions
AWS Organizations → Policies → Service control policies → Create policyPolicy name: RestrictToMumbaiOnly Policy content — paste this:{ "Version": "2012-10-17", "Statement": [{ "Effect": "Deny", "Action": ["ec2:*", "rds:*", "s3:CreateBucket"], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": "ap-south-1" } } }]}Create policyStep 4 — Attach SCP to Sandbox OU
AWS Organizations → Policies → RestrictToMumbaiOnlyTargets → Attach → select Sandbox OU → Attach policy Any account inside Sandbox can now only create EC2, RDS, and S3 in ap-south-1.Attempting those actions in us-east-1 returns Access Denied.Step 5 — Create a Permission Boundary
IAM → Policies → Create policy → JSON tab{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:*", "cloudwatch:*", "ec2:Describe*"], "Resource": "*" }]}Policy name: DevEngineerBoundary → Create policy Apply to a user:IAM → Users → arjun-dev → Permissions → Set permissions boundarySelect DevEngineerBoundary → Set boundary Now even if arjun-dev has AdministratorAccess attached,he can only do s3:*, cloudwatch:*, and ec2:Describe*.The boundary is the ceiling — it cannot grant, only limit.Step 6 — Verify the boundary works
Log in as arjun-dev (or use CLI with his credentials)Try: aws ec2 run-instances → Access Denied (ec2:RunInstances not in boundary)Try: aws s3 ls → Works (s3:* is in the boundary) This is Permission Boundaries in action — policies say what you can do,boundary says what the maximum is. Effective = intersection of both.Step 7 — Cleanup
IAM → Users → arjun-dev → Remove permissions boundaryIAM → Policies → DevEngineerBoundary → Delete policyOrganizations → Policies → RestrictToMumbaiOnly → Detach from Sandbox → DeleteOrganizations → Sandbox OU → move accounts back to Root → Delete OUProduction Best Practices and Common Pitfalls
- Never put actual workloads in the management account — it cannot be restricted by any SCP and a breach there affects the entire org
- Use the blocklist SCP strategy for most OUs — allow everything and deny the specific dangerous things — it is easier to maintain than an allowlist
- Test SCPs in a sandbox OU before applying to production — a wrong SCP can silently break services for all accounts under that OU
- Use IAM Identity Center instead of creating IAM users in every account — one login, one place to manage access, and full audit trail
- Apply Permission Boundaries when delegating IAM management to team leads — prevents privilege escalation without removing the ability to manage users
- Use aws:PrincipalOrgID in bucket policies instead of listing account IDs — automatically includes new accounts and does not require policy updates when accounts are added
- Enable AWS Config and Security Hub in every account via Control Tower — centralised compliance visibility from day one
Quick Reference and Troubleshooting Commands
| Task | Command |
|---|---|
| Create organization | aws organizations create-organization --feature-set ALL |
| List roots | aws organizations list-roots |
| Create OU | aws organizations create-organizational-unit --parent-id <root-id> --name <name> |
| List accounts | aws organizations list-accounts |
| Move account to OU | aws organizations move-account --account-id <id> --source-parent-id <root-id> --destination-parent-id <ou-id> |
| Create SCP | aws organizations create-policy --type SERVICE_CONTROL_POLICY --name <name> --content file://scp.json |
| Attach SCP | aws organizations attach-policy --policy-id <p-id> --target-id <ou-id> |
| List SCPs on target | aws organizations list-policies-for-target --target-id <id> --filter SERVICE_CONTROL_POLICY |
Common problems and fixes:
| Problem | Likely cause | Fix |
|---|---|---|
| Action denied in member account despite AdministratorAccess | SCP blocking the action | Check SCPs on all OUs above the account including Root |
| Management account affected by SCP | Thinking SCPs apply to management account | SCPs never apply to management account — this should never happen |
| Cross-account access needed while keeping Account A permissions active | Using role assumption which pauses Account A permissions | Switch to Resource-Based Policy on the target resource |
| User has policy allowing IAM but cannot create admin users | Permission Boundary blocking IAM admin actions | Review boundary — it caps what policies can grant |
| Identity Center users cannot access an account | Permission Set not assigned to that account | Assign the Permission Set to the user/group for that specific account |
Common MistakeConfusing aws:SourceIp with aws:RequestedRegion. SourceIp controls where the API call comes FROM — the caller's IP address. RequestedRegion controls which AWS region the call goes TO. A policy restricting to an office IP range uses SourceIp. A policy restricting to specific regions uses RequestedRegion. They control completely different dimensions.
Common MistakeUsing role assumption when you need to act in two accounts simultaneously. Assuming a role pauses your original account's permissions. If your code needs to read from Account A's DynamoDB and write to Account B's S3 at the same time, use a Resource-Based Policy on the S3 bucket that allows your Account A identity directly — your permissions stay active in both accounts.
SecurityAn SCP Deny at the Root OU level affects every account in the organisation except the management account. Before applying any SCP to Root, test it thoroughly in a non-critical OU. A wrong SCP at Root can silently break services for every team across every account — and because SCPs do not generate obvious error messages, the issue can take hours to diagnose.
Common Mistakes to Avoid
Common MistakePutting workloads in the management account. The management account cannot be restricted by any SCP — ever. A breach of the management account means unrestricted access to every account in the organisation. Keep it empty except for organisational management tasks.
Common MistakeConfusing aws:SourceIp with aws:RequestedRegion. SourceIp controls where the API call comes FROM (the caller's IP). RequestedRegion controls which AWS region the call goes TO. They control completely different dimensions.
SecurityTest SCPs in a sandbox OU before applying to production. A wrong SCP at the Root OU level silently breaks services for every account in the organisation. One wrong deny rule and your entire company loses access to a service — with no obvious error message explaining why.