Phase 2 · AWS IAM · Lesson 1 of 4
Article
·
25 min
·
+12 pts
AWS Identity and Access Management is where cloud security gets concrete. Every permission, every access decision, every audit finding in an AWS environment traces back to IAM. If the previous lessons in this phase gave you the conceptual framework — authentication vs authorization, principal vs resource, least privilege as a principle — this lesson gives you the actual building blocks you will encounter in every AWS account you audit, configure, or assess.
This is the longest lesson in the phase for a reason. AWS IAM has more moving parts than most identity systems, and the interactions between those parts are where security gaps hide. Understanding each component individually is necessary. Understanding how they interact is what makes you effective.
'Action: *, Resource: *' found in code review
An IAM user is a persistent identity within an AWS account. It has a name, an ARN (Amazon Resource Name), and can have two types of credentials: a console password for browser-based access and access keys for programmatic (CLI/API) access.
IAM users were the original way to grant access to AWS. In the early days of AWS, every person and every application got an IAM user. That model has significant problems:
Modern AWS security practice avoids IAM users for day-to-day access. Instead, humans authenticate through an identity provider (Okta, Entra ID, AWS IAM Identity Center) and assume IAM roles with temporary credentials. IAM users still have two legitimate use cases:
For GRC purposes, IAM users are a findings magnet. Auditors look for them specifically.
An IAM group is a collection of IAM users. Groups have no credentials of their own — they exist solely to organize policy attachments. When you attach a policy to a group, every user in that group inherits those permissions.
The rule is simple: attach policies to groups, never to individual users. This keeps permissions auditable. When an auditor asks "who has S3 access?", you can point to the s3-readers group and its membership list. If policies are scattered across individual users, answering that question requires inspecting every user — a process that does not scale and inevitably misses something.
Groups typically mirror organizational structure:
security-team — CloudTrail, GuardDuty, Security Hub, IAM read-onlydevelopers — CodeCommit, CodeBuild, specific S3 buckets, specific DynamoDB tablesbilling-admins — Cost Explorer, Budgets, billing consoleOne important limitation: IAM groups cannot be referenced as principals in IAM policies. You cannot write a resource-based policy that grants access to a group — only to users, roles, or accounts. This is one of several reasons roles are preferred over groups for cross-account and service-to-service access patterns.
IAM roles are the central mechanism for granting access in modern AWS environments. Unlike users, roles do not have permanent credentials. Instead, a principal assumes a role and receives temporary security credentials from the AWS Security Token Service (STS). These credentials expire automatically — typically after one hour, though the duration is configurable.
The assume-role flow works like this: a principal (a user, an application, another AWS service, or even a principal in a different AWS account) calls sts:AssumeRole, presenting the role's ARN. AWS checks the role's trust policy to verify whether the requesting principal is allowed to assume the role. If allowed, STS returns temporary credentials (an access key ID, a secret access key, and a session token) that carry the role's permissions until they expire.
Two patterns dominate real-world role usage:
When the security team in Account A needs to audit resources in Account B, they assume a role in Account B. The role in Account B has a trust policy that specifies which principals in Account A are allowed to assume it, and what conditions must be met (such as MFA). This is the standard pattern for cross-account access — no long-lived credentials are shared between accounts.
Here is a trust policy that allows a specific role in Account A to assume a role in Account B, but only if MFA is present:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/SecurityTeamRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
AWS services themselves assume roles to perform actions on your behalf. When a Lambda function executes, it assumes its execution role to get credentials for accessing other AWS resources. When an EC2 instance needs to read from S3, it uses an instance profile (a container for an IAM role) that gives it temporary credentials automatically via the instance metadata service.
Service roles are where least privilege matters most. A Lambda function that processes images should only have access to the specific S3 bucket containing those images and the specific DynamoDB table where it writes metadata — not AdministratorAccess. In practice, overprivileged service roles are one of the most common findings in AWS security assessments.
IAM policies are JSON documents that define permissions. Every access decision in AWS ultimately resolves to one or more policies being evaluated. A policy contains one or more statements, and each statement has a consistent structure:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-quarterly-reports",
"arn:aws:s3:::company-quarterly-reports/*"
]
}
]
}
The fields:
"2012-10-17". This is not a date you choose; it is the policy language version."Allow" or "Deny". There is nothing else.service:ActionName. Wildcards are supported (s3:*, s3:Get*)."*" means all resources — avoid it when possible.Policies come in three flavors:
AdministratorAccess, ReadOnlyAccess, AmazonS3ReadOnlyAccess. Convenient but often too broad for production use.Policies are also categorized by where they attach:
Quick check
A developer's IAM role has an identity-based policy that allows s3:PutObject on all S3 buckets. However, the S3 bucket 'finance-data' has a resource-based bucket policy that explicitly denies s3:PutObject from that role. Can the developer write to the finance-data bucket?
Service Control Policies operate at the AWS Organizations level. They are not IAM policies in the traditional sense — they are guardrails that restrict what actions can be taken within an account or organizational unit (OU), regardless of what IAM policies exist in those accounts.
The critical distinction: SCPs restrict but never grant. An SCP that allows s3:* does not give anyone S3 access. It means S3 actions are permitted if the principal also has an identity-based policy allowing them. An SCP that denies cloudtrail:StopLogging means no one in the affected accounts can stop CloudTrail — not even the account root user.
Here is an SCP that prevents anyone from deleting or disabling CloudTrail:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ProtectCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:PutEventSelectors"
],
"Resource": "*"
}
]
}
SCPs are attached to OUs or accounts. They cascade downward: an SCP on the root OU affects every account in the organization. An SCP on a specific OU affects only accounts in that OU. They do not affect the management account (the root account of the organization), which is why the management account should have almost no workloads running in it.
For GRC engineers, SCPs are the single most powerful compliance mechanism in AWS. They create organization-wide invariants: CloudTrail cannot be disabled, certain regions cannot be used, certain services cannot be accessed. When an auditor asks "how do you ensure logging cannot be tampered with?", the SCP is your evidence.
Permission boundaries define the maximum permissions an identity-based policy can grant to a principal. They do not grant permissions on their own — they set a ceiling.
The use case is delegation. Imagine you want team leads to be able to create IAM roles for their teams, but you need to ensure they cannot create roles with more permissions than you intend. You create a permission boundary that allows only specific services, and you require team leads to attach that boundary to every role they create. Even if a team lead attaches AdministratorAccess to a role, the permission boundary limits the role's effective permissions to the intersection of the boundary and the identity-based policy.
Permission boundaries are an advanced concept that most organizations do not implement. But when they are implemented well, they solve a hard problem: enabling decentralized IAM management without risk of privilege escalation.
┌─────────────────────────────────────────────────────────────┐
│ AWS Organization │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Service Control Policies (SCPs) │ │
│ │ Restrict actions across all accounts │ │
│ │ in the OU. Never grant — only limit. │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌─────────────┴────────────┐ │
│ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────────┐ │
│ │ OU: Security │ │ OU: Production │ │
│ └───────┬────────┘ └────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────────┐ │
│ │ Account: │ │ Account: │ │
│ │ Security-Prod │ │ App-Prod │ │
│ │ │ │ │ │
│ │ ┌───────────┐ │ │ ┌──────────────┐ │ │
│ │ │ IAM Roles │ │ │ │ IAM Roles │ │ │
│ │ │ + Policies│ │ │ │ + Policies │ │ │
│ │ │ + Perm │ │ │ │ + Perm │ │ │
│ │ │ Boundaries│ │ │ │ Boundaries │ │ │
│ │ └───────────┘ │ │ └──────────────┘ │ │
│ └────────────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Effective permissions = Identity-based policy
∩ Permission boundary (if set)
∩ SCP (if in an Organization)
∩ Session policy (if applicable)
Explicit deny in ANY layer → DENIEDAWS IAM hierarchy: Organization-level controls constrain account-level identities and policies
Understanding the building blocks is necessary. Knowing what to check when you audit an AWS environment is what makes you effective. Here is the verification checklist.
GRC Engineer's focus
When assessing an AWS environment's IAM posture, verify these controls:
When an auditor asks you to demonstrate IAM compliance, you need concrete evidence, not architecture diagrams alone.
Audit evidence
Key evidence artifacts for AWS IAM compliance:
iam-root-access-key-check, iam-user-mfa-enabled, iam-user-unused-credentials-check, iam-policy-no-statements-with-admin-access. These run continuously and produce compliance findings.Quick check
You discover an IAM user with two active access keys, one of which was last used 14 months ago. The user has the AdministratorAccess policy attached directly (inline). What are the findings?
AWS IAM is the most detailed identity system you will encounter in GRC work because it has the most layers. Users provide persistent credentials but should be rare. Groups organize policies but cannot be used as principals in resource-based policies. Roles provide temporary credentials and are the preferred mechanism for everything. Policies define permissions as JSON documents with Effect, Action, Resource, and Condition. SCPs create organization-wide guardrails that cannot be overridden. Permission boundaries cap what identity-based policies can grant.
The next lesson dives into exactly how AWS evaluates a request when all of these layers apply simultaneously — the policy evaluation logic that determines whether any given API call is allowed or denied.