/IAM, STS & Security for Developers
Concept
Hard

IAM, STS & Security for Developers

10 min read·IAMSTSSecurityDVA-C02

A comprehensive deep dive into AWS IAM and STS — policies, roles, permission boundaries, credential chain, cross-account access, STS API calls, confused deputy, and security best practices for the DVA-C02 exam.


What is AWS IAM?

AWS Identity and Access Management (IAM) is the global authorization system for AWS. Every API call to any AWS service is evaluated by IAM — it determines whether the caller is authenticated (who are you?) and authorized (are you allowed to do this?).

Core mental model: IAM is a bouncer for every AWS service. Before any action executes, IAM checks: does the identity making this request have a policy that explicitly allows this action on this resource?


IAM Core Components

ComponentDescription
UserA person or application with long-term credentials (access key + secret). Avoid for applications — use roles instead.
GroupCollection of users. Policies attached to groups apply to all members.
RoleAn identity with temporary credentials. Assumed by services, applications, users, or other accounts.
PolicyJSON document defining permissions. Attached to users, groups, or roles.

IAM Policy Structure

Every IAM policy is a JSON document with one or more statements:

json
1{
2  "Version": "2012-10-17",
3  "Statement": [
4    {
5      "Sid": "AllowS3ReadOnMyBucket",
6      "Effect": "Allow",
7      "Action": [
8        "s3:GetObject",
9        "s3:ListBucket"
10      ],
11      "Resource": [
12        "arn:aws:s3:::my-bucket",
13        "arn:aws:s3:::my-bucket/*"
14      ],
15      "Condition": {
16        "StringEquals": {
17          "s3:prefix": ["uploads/"]
18        }
19      }
20    },
21    {
22      "Sid": "DenyDeleteEverywhere",
23      "Effect": "Deny",
24      "Action": "s3:DeleteObject",
25      "Resource": "*"
26    }
27  ]
28}

Policy evaluation logic:

  1. Default is implicit Deny — nothing is allowed unless explicitly permitted
  2. An explicit Allow grants access
  3. An explicit Deny always wins — overrides any Allow, including from other policies
Rendering diagram…

Policy Types

TypeAttached toScopeNotes
Identity-basedUser / Group / RoleWhat the identity can doMost common
Resource-basedS3 bucket, SQS queue, Lambda, KMS key, etc.Who can access this resourceEnables cross-account without role assumption
Permission BoundaryUser or RoleMax permissions ceilingCannot grant more than boundary allows
SCP (Service Control Policy)AWS Organization OU / AccountAccount-level guardrailApplied before identity policies
Session PolicyAssumeRole callFurther restricts sessionCannot expand role permissions
ACLS3, VPCLegacy resource controlDeprecated in favor of bucket policies

Identity-Based vs Resource-Based Policies

Rendering diagram…

Same-account access: Either identity policy OR resource policy is sufficient (logical OR).

Cross-account access: Both identity policy AND resource policy must allow (logical AND) — OR use role assumption.

json
1// S3 bucket policy (resource-based) — allow cross-account read
2{
3  "Version": "2012-10-17",
4  "Statement": [{
5    "Effect": "Allow",
6    "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
7    "Action": ["s3:GetObject", "s3:ListBucket"],
8    "Resource": [
9      "arn:aws:s3:::shared-bucket",
10      "arn:aws:s3:::shared-bucket/*"
11    ]
12  }]
13}

IAM Roles

A role is an IAM identity with no permanent credentials — temporary credentials are issued each time the role is assumed. Roles are the recommended approach for all non-human access.

Trust Policy vs Permission Policy

Every role has two policies:

json
1// Trust Policy — WHO can assume this role
2{
3  "Version": "2012-10-17",
4  "Statement": [{
5    "Effect": "Allow",
6    "Principal": {
7      "Service": "lambda.amazonaws.com"   // Lambda service can assume this role
8    },
9    "Action": "sts:AssumeRole"
10  }]
11}
json
1// Permission Policy — WHAT the role can do
2{
3  "Version": "2012-10-17",
4  "Statement": [{
5    "Effect": "Allow",
6    "Action": [
7      "dynamodb:GetItem",
8      "dynamodb:PutItem",
9      "logs:CreateLogGroup",
10      "logs:PutLogEvents"
11    ],
12    "Resource": "*"
13  }]
14}

Common Role Use Cases

ServiceRole typePurpose
LambdaExecution roleCall DynamoDB, S3, SQS on behalf of your function
EC2Instance profileApplication on EC2 accesses AWS without hardcoded keys
ECS / EKSTask roleContainer-level permissions (not instance-level)
CodePipelineService roleDeploy to S3, ECS, Lambda
Cross-accountRole with trust policyAccount A assumes role in Account B

Permission Boundaries

A permission boundary sets the maximum permissions an identity can have. Even if an identity policy grants more, the boundary caps it.

Effective permissions = Identity Policy ∩ Permission Boundary ∩ SCP
json
1// Permission boundary — dev role can only use S3 and DynamoDB
2{
3  "Version": "2012-10-17",
4  "Statement": [{
5    "Effect": "Allow",
6    "Action": ["s3:*", "dynamodb:*"],
7    "Resource": "*"
8  }]
9}

Use case: Allow developers to create IAM roles for their Lambda functions, but prevent them from creating roles with Admin access.

Rendering diagram…

AWS STS — Security Token Service

STS issues temporary security credentials (Access Key ID + Secret + Session Token) with a configurable expiry. All role assumptions go through STS.

STS API Calls

APIWho calls itUse case
AssumeRoleIAM user, role, or serviceCross-account, service federation, temporary escalation
AssumeRoleWithWebIdentityApp with OIDC tokenMobile/web app with Google, Apple, Cognito Identity Pool
AssumeRoleWithSAMLEnterprise SSO userSAML 2.0 corporate federation (Active Directory)
GetSessionTokenIAM userMFA-protected API calls
GetFederationTokenBroker applicationLegacy federation; prefer AssumeRole

AssumeRole in Practice

javascript
1import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts';
2import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';
3
4const sts = new STSClient({ region: 'us-east-1' });
5
6// Assume a role in another account
7const { Credentials } = await sts.send(new AssumeRoleCommand({
8  RoleArn: 'arn:aws:iam::999988887777:role/DataAccessRole',
9  RoleSessionName: 'MyAppSession',     // appears in CloudTrail logs
10  DurationSeconds: 3600,               // 15 min (900s) to 12 hours (43200s)
11  ExternalId: 'unique-external-id',    // prevents confused deputy
12  // Policy: JSON.stringify(sessionPolicy)  // optional session policy to restrict further
13}));
14
15// Use temporary credentials
16const s3 = new S3Client({
17  region: 'us-east-1',
18  credentials: {
19    accessKeyId:     Credentials.AccessKeyId,
20    secretAccessKey: Credentials.SecretAccessKey,
21    sessionToken:    Credentials.SessionToken,
22  },
23});
24
25const objects = await s3.send(new ListObjectsV2Command({ Bucket: 'cross-account-bucket' }));

Credential Duration

APIMinMax
AssumeRole15 minutes12 hours (role's MaxSessionDuration)
AssumeRoleWithWebIdentity15 minutes12 hours
AssumeRoleWithSAML15 minutes12 hours
GetSessionToken15 minutes36 hours

AWS SDK Credential Chain

When your application uses the AWS SDK, credentials are resolved in this exact order — the first match wins:

PrioritySourceHow it works
1Explicit in codenew S3Client({ credentials: { accessKeyId, secretAccessKey } })
2Environment variablesAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
3AWS SSO~/.aws/sso/cache via aws configure sso
4Shared credentials file~/.aws/credentials[default] or named profile
5AWS config file~/.aws/config
6Container credentialsECS task role via metadata endpoint
7EC2 instance profileInstance metadata service (IMDS): http://169.254.169.254/...

Best practices: Never hardcode credentials (priority 1). In production, always use roles (priority 6 for ECS, priority 7 for EC2, automatic for Lambda).

bash
1# Lambda, ECS, EC2 automatically get credentials via the role — no code needed
2# The SDK picks them up from the environment automatically (priority 6 or 7)
3
4# For local development, use named profiles
5export AWS_PROFILE=my-dev-profile
6# Or assume a role via CLI
7aws sts assume-role --role-arn arn:aws:iam::123:role/DevRole --role-session-name local

Cross-Account Access Patterns

Pattern 1: Role Assumption (Most Common)

Rendering diagram…

Setup:

  1. In Account B: create role with trust policy allowing Account A
  2. In Account A: attach policy allowing sts:AssumeRole on that role ARN
  3. Application assumes the role → gets temporary credentials → accesses Account B resources

Pattern 2: Resource-Based Policy (No Role Assumption)

json
1// Account B's S3 bucket policy grants Account A directly
2{
3  "Principal": { "AWS": "arn:aws:iam::111122223333:role/AppRole" },
4  "Action": "s3:GetObject",
5  "Effect": "Allow",
6  "Resource": "arn:aws:s3:::account-b-bucket/*"
7}

Key difference: With resource policy, Account A's identity retains its original permissions alongside the cross-account access. With role assumption, the caller gives up its original permissions and takes on only the assumed role's permissions.


Confused Deputy Attack & ExternalId

When a third-party service (Vendor) assumes your role, any of their customers could trick them into using their AWS privileges to access your account. The ExternalId prevents this.

Rendering diagram…
json
1// Trust policy with ExternalId condition
2{
3  "Effect": "Allow",
4  "Principal": { "AWS": "arn:aws:iam::VENDOR_ACCOUNT:root" },
5  "Action": "sts:AssumeRole",
6  "Condition": {
7    "StringEquals": {
8      "sts:ExternalId": "your-unique-external-id-abc123"
9    }
10  }
11}

The ExternalId should be unique per customer at the vendor — a UUID or your account ID works well.


IAM Policy Conditions

Conditions add fine-grained control to policies:

json
1{
2  "Effect": "Allow",
3  "Action": "s3:*",
4  "Resource": "*",
5  "Condition": {
6    "Bool":          { "aws:MultiFactorAuthPresent": "true" },
7    "IpAddress":     { "aws:SourceIp": ["203.0.113.0/24", "198.51.100.0/24"] },
8    "StringEquals":  { "aws:RequestedRegion": "us-east-1" },
9    "DateGreaterThan": { "aws:CurrentTime": "2024-01-01T00:00:00Z" },
10    "StringLike":    { "s3:prefix": "home/${aws:username}/*" }
11  }
12}

Common condition keys:

KeyPurpose
aws:MultiFactorAuthPresentRequire MFA for sensitive operations
aws:SourceIp / aws:VpcSourceIpRestrict to IP range or VPC
aws:RequestedRegionRestrict to specific regions
aws:PrincipalTag/keyAttribute-based access control (ABAC)
aws:ResourceTag/keyRestrict to tagged resources
aws:CalledViaAllow action only when called by a specific service
sts:ExternalIdConfused deputy prevention

IAM Best Practices for Developers

  1. Never use root account for day-to-day operations — enable MFA and lock it away
  2. Never hardcode credentials — use roles, environment variables, or secrets manager
  3. Least privilege — start with minimal permissions, add as needed
  4. Use roles for EC2/Lambda/ECS — not access keys in environment variables
  5. Rotate access keys — if you must use them, rotate every 90 days
  6. Enable MFA for all human users, especially for console access
  7. Use permission boundaries when delegating role creation to developers
  8. Audit with IAM Access Analyzer — finds unused permissions and external access

DVA-C02 Quick Reference

TopicKey Fact
Explicit DenyAlways overrides any Allow
Default permissionImplicit Deny (nothing allowed unless explicitly permitted)
Same-account: identity OR resource policyEither is sufficient
Cross-account: identity AND resource policyBoth required (or use role assumption)
Role assumption vs resource policyRole: give up original permissions; Resource policy: keep original permissions
STS AssumeRole duration15 min – 12 hours
STS GetSessionToken duration15 min – 36 hours
Confused deputy fixExternalId in trust policy condition
Permission boundary effectCaps maximum permissions — cannot grant more
Session policy effectFurther restricts — cannot expand role permissions
SDK credential chain firstExplicit in code (avoid!)
SDK credential chain lastEC2 Instance Profile / ECS Task Role
AssumeRoleWithWebIdentityOIDC providers: Google, Apple, Cognito Identity Pool
AssumeRoleWithSAMLEnterprise Active Directory / SAML 2.0 federation
CloudTrail identityRoleSessionName appears as the principal in logs

Practice Questions5

easy

Q1. A Lambda function needs to read from an S3 bucket. The developer attaches an IAM user's access keys to the function. A security reviewer flags this as incorrect. What is the recommended approach?


Select one answer before revealing.

medium

Q2. A developer wants to grant an external AWS account temporary access to upload objects to an S3 bucket. The access should expire after 2 hours. Which approach should be used?


Select one answer before revealing.

medium

Q3. A Lambda function uses STS AssumeRole to assume a role in another account. The function fails with "AccessDenied: User is not authorized to perform sts:AssumeRole." What must be configured?


Select one answer before revealing.

hard

Q4. A developer is implementing attribute-based access control (ABAC) in AWS. They want to allow engineers to start/stop only EC2 instances tagged with the same "team" tag as the engineer's IAM user. Which IAM feature enables this?


Select one answer before revealing.

hard

Q5. A developer needs to generate temporary credentials for a web application that authenticates users via Facebook. Users should get AWS credentials scoped to their own data. Which AWS service and API should be used?


Select one answer before revealing.