AWS IAM and Security
AWS Identity and Access Management (IAM) controls who (authentication) can do what (authorization) with which AWS resources. Misunderstanding IAM leads to security breaches, overprivileged accounts, and difficult-to-maintain permission structures.
This guide covers IAM fundamentals, policy design, cross-account access, service authentication, and security auditing patterns for production applications.
Misconfigured IAM policies are a leading cause of security breaches. Apply the principle of least privilege and audit permissions regularly to prevent unauthorized access.
Core IAM Concepts
IAM Entities
IAM manages four primary entity types, each serving distinct authentication and authorization purposes:
IAM Users represent individual people or services with long-term credentials (username/password or access keys). Users are strongly discouraged for application authentication - use IAM roles instead. Reserve users for human administrators and emergency access only.
IAM Groups are collections of users that share permission policies. Groups simplify permission management for teams (e.g., developers, operations, security teams) but do not affect service-to-service authentication.
IAM Roles are the preferred authentication mechanism for applications, services, and cross-account access. Unlike users, roles have no long-term credentials - instead, temporary credentials are vended through the AWS Security Token Service (STS) when a principal assumes a role. This credential rotation happens automatically, reducing credential exposure risk.
IAM Policies define permissions as JSON documents. Policies specify what actions are allowed or denied on which resources under what conditions. Policies can be attached to users, groups, or roles.
The diagram illustrates the relationship between IAM entities. Applications running on AWS services (EC2, Lambda, ECS) assume roles to obtain temporary credentials, while external applications can assume roles via federated identity providers like OIDC. This architecture eliminates the need to embed long-term credentials in application code.
IAM Policies
IAM policies are JSON documents that define permissions. Understanding policy structure is essential for implementing least privilege access patterns.
Policy Structure
Every policy consists of one or more statements, each with five core elements:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}
]
}
Version specifies the policy language version. Always use "2012-10-17" (the current and most feature-complete version).
Statement is an array of permission declarations. Each statement grants or denies specific actions on specific resources.
Sid (Statement ID) is an optional human-readable identifier for the statement. Use descriptive Sids to document intent (e.g., "AllowS3ReadAccess").
Effect is either "Allow" or "Deny". By default, all actions are implicitly denied. Use explicit "Deny" statements to override "Allow" statements when implementing security guardrails.
Action specifies which API operations are allowed or denied. Actions use service prefix notation: service:Action (e.g., s3:GetObject, dynamodb:Query). Wildcards are supported (s3:* for all S3 actions, s3:Get* for all read operations).
Resource specifies which resources the actions apply to using Amazon Resource Names (ARNs). ARNs follow the format: arn:partition:service:region:account-id:resource-type/resource-id. Use wildcards carefully - * grants access to all resources of that type.
Condition (optional) specifies circumstances under which the statement applies. Conditions use condition keys (e.g., aws:SourceIp, aws:CurrentTime) and operators (e.g., IpAddress, DateGreaterThan) to implement context-based access controls.
Policy Types
AWS provides multiple policy types for different use cases:
AWS Managed Policies are created and maintained by AWS. These policies provide permissions for common use cases (e.g., AmazonS3ReadOnlyAccess, PowerUserAccess). AWS updates managed policies when new services or features are released. Use managed policies as a starting point, but they often grant broader permissions than needed for specific applications.
Customer Managed Policies are created and maintained by your organization. These policies provide fine-grained control tailored to your specific requirements. Customer managed policies can be attached to multiple roles, users, or groups, enabling permission reuse across your organization.
Inline Policies are embedded directly into a single user, group, or role. Inline policies have a strict one-to-one relationship with their principal. Use inline policies sparingly - they create tight coupling between policy and principal, making policy reuse impossible. Prefer customer managed policies for maintainability.
Service Control Policies (SCPs) are part of AWS Organizations and set permission boundaries for entire AWS accounts. SCPs act as guardrails, defining the maximum permissions available in an account regardless of IAM policies. SCPs never grant permissions - they only limit them. Use SCPs to enforce organization-wide security requirements (e.g., "no account can disable CloudTrail").
The diagram shows the policy hierarchy. SCPs act as account-level guardrails, limiting what any policy can grant. Within an account, roles can have AWS managed policies (broad, maintained by AWS), customer managed policies (specific to your organization), and inline policies (tightly coupled to a single role).
Service Roles and Instance Profiles
Applications running on AWS services obtain credentials by assuming IAM roles. This eliminates the need to embed access keys in code or configuration files.
EC2 Instance Profiles
An instance profile is a container for an IAM role that EC2 instances can assume. When you launch an EC2 instance with an instance profile, the instance automatically obtains temporary credentials from the EC2 Instance Metadata Service (IMDS).
Applications running on the instance access these credentials via the AWS SDK, which automatically retrieves and refreshes credentials from IMDS without developer intervention.
The SDK transparently handles credential retrieval from IMDS. Credentials are cached and automatically refreshed before expiration, ensuring uninterrupted access. This pattern works identically for all AWS SDKs (Java, Node.js, Python, Go, etc.).
Spring Boot with EC2 Instance Profile
Spring Boot applications automatically discover and use EC2 instance profile credentials when the AWS SDK is on the classpath. No explicit credential configuration is required:
// No credential configuration needed - SDK uses instance profile automatically
@Configuration
public class S3Config {
@Bean
public S3Client s3Client() {
return S3Client.builder()
.region(Region.US_EAST_1)
// Credentials automatically loaded from instance profile
.build();
}
}
@Service
public class DocumentService {
private final S3Client s3Client;
public DocumentService(S3Client s3Client) {
this.s3Client = s3Client;
}
public byte[] downloadDocument(String key) {
GetObjectRequest request = GetObjectRequest.builder()
.bucket("my-documents")
.key(key)
.build();
ResponseInputStream<GetObjectResponse> response =
s3Client.getObject(request);
return response.readAllBytes();
}
}
The AWS SDK follows a credential provider chain to locate credentials: environment variables, system properties, instance profile, container credentials, and default credential files. In production, instance profiles should always be the primary credential source for EC2-based applications.
ECS Task Roles
ECS tasks assume IAM roles at the task level, not the container instance level. This provides fine-grained permission control - different tasks running on the same container instance can have different permissions.
ECS Task Role vs Task Execution Role
ECS uses two distinct IAM roles:
Task Role grants permissions to application code running in the container. This is the role your application assumes to access AWS resources (S3, DynamoDB, SQS, etc.). Each task definition specifies a task role via the taskRoleArn parameter.
Task Execution Role grants permissions to the ECS agent to manage the container lifecycle. The execution role pulls container images from ECR, writes logs to CloudWatch, and retrieves secrets from Secrets Manager or Parameter Store. The execution role is specified via executionRoleArn.
{
"family": "my-application",
"taskRoleArn": "arn:aws:iam::123456789012:role/MyApplicationTaskRole",
"executionRoleArn": "arn:aws:iam::123456789012:role/ECSTaskExecutionRole",
"containerDefinitions": [
{
"name": "app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest",
"memory": 512,
"cpu": 256
}
]
}
The task execution role policy typically includes:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/ecs/*"
}
]
}
Applications access task role credentials automatically via the SDK - credentials are served via a local endpoint injected by the ECS agent. See Spring Boot container patterns for deployment details.
Lambda Execution Roles
Lambda functions assume an execution role that grants permissions for the function to access AWS resources and write logs to CloudWatch.
// Lambda function handler - credentials automatically available
public class S3EventHandler implements RequestHandler<S3Event, String> {
private final S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
// Credentials from Lambda execution role
.build();
@Override
public String handleRequest(S3Event event, Context context) {
event.getRecords().forEach(record -> {
String bucket = record.getS3().getBucket().getName();
String key = record.getS3().getObject().getKey();
// Access S3 using execution role permissions
GetObjectRequest request = GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
s3Client.getObject(request);
});
return "Processed";
}
}
Lambda execution role policies must include CloudWatch Logs permissions at minimum:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
Add additional permissions based on resources the function accesses. See AWS Compute Services for Lambda-specific security patterns.
IAM Roles for Service Accounts (IRSA)
In Kubernetes on EKS, traditional EC2 instance profiles apply to all pods on a node - pods share the same IAM role. This violates the principle of least privilege at the pod level.
IAM Roles for Service Accounts (IRSA) provides pod-level IAM roles by mapping Kubernetes service accounts to IAM roles. IRSA leverages OIDC federation to enable pods to assume IAM roles without sharing credentials.
IRSA Architecture
IRSA works through a trust relationship between EKS's OIDC provider and IAM:
The pod's service account token (a JWT) is signed by the EKS cluster's OIDC provider. When the pod calls AWS APIs, the AWS SDK exchanges this token for temporary AWS credentials via AssumeRoleWithWebIdentity. STS validates the token against the EKS OIDC provider before issuing credentials.
Implementing IRSA
- Create IAM role with trust policy for EKS OIDC provider:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:my-service-account",
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}
The Condition restricts the role to a specific Kubernetes namespace and service account, preventing other pods from assuming the role.
- Annotate Kubernetes service account with IAM role ARN:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/MyPodRole
- Reference service account in pod specification:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
serviceAccountName: my-service-account
containers:
- name: app
image: my-app:latest
# AWS SDK automatically uses IRSA credentials
The AWS SDK in the container automatically detects the service account token and exchanges it for temporary credentials. No code changes are required - credential resolution is transparent.
See AWS EKS documentation for cluster setup and Kubernetes best practices for service account security patterns.
Cross-Account Access
Organizations often run multiple AWS accounts for environment isolation (dev, staging, production) or organizational boundaries (teams, business units). Cross-account access enables resources in one account to access resources in another while maintaining security boundaries.
Cross-Account IAM Roles
The recommended pattern for cross-account access is role assumption. Account A creates an IAM role that trusts Account B, and principals in Account B assume the role to access resources in Account A.
Implementing Cross-Account Role Assumption
- In Account A (resource account), create role with trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-12345"
}
}
}
]
}
The trust policy allows principals from Account B (222222222222) to assume the role. The ExternalId condition provides protection against the confused deputy problem - it ensures that only authorized parties with knowledge of the external ID can assume the role. Without external IDs, third parties can trick your account into performing unauthorized actions on their behalf (the confused deputy attack).
- Attach permission policy to role in Account A:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::production-data",
"arn:aws:s3:::production-data/*"
]
}
]
}
- In Account B (assuming account), grant permission to assume the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/ProductionAccessRole"
}
]
}
Attach this policy to the user or role in Account B that needs cross-account access.
- Application code assumes the role:
@Service
public class CrossAccountS3Service {
private final StsClient stsClient;
public CrossAccountS3Service() {
this.stsClient = StsClient.builder()
.region(Region.US_EAST_1)
.build();
}
public S3Client getCrossAccountS3Client() {
// Assume role in Account A
AssumeRoleRequest roleRequest = AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::111111111111:role/ProductionAccessRole")
.roleSessionName("cross-account-session")
.externalId("unique-external-id-12345")
.durationSeconds(3600) // 1 hour
.build();
AssumeRoleResponse response = stsClient.assumeRole(roleRequest);
Credentials credentials = response.credentials();
// Create S3 client with temporary credentials
return S3Client.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsSessionCredentials.create(
credentials.accessKeyId(),
credentials.secretAccessKey(),
credentials.sessionToken()
)
))
.build();
}
public String readProductionData(String key) {
S3Client s3Client = getCrossAccountS3Client();
GetObjectRequest request = GetObjectRequest.builder()
.bucket("production-data")
.key(key)
.build();
ResponseInputStream<GetObjectResponse> response = s3Client.getObject(request);
return new String(response.readAllBytes(), StandardCharsets.UTF_8);
}
}
The temporary credentials returned by AssumeRole expire after the specified duration (1-12 hours). Applications must refresh credentials before expiration by calling AssumeRole again.
For credential caching and automatic refresh, use the AWS SDK's StsAssumeRoleCredentialsProvider:
@Bean
public S3Client crossAccountS3Client(StsClient stsClient) {
StsAssumeRoleCredentialsProvider credentialsProvider =
StsAssumeRoleCredentialsProvider.builder()
.stsClient(stsClient)
.refreshRequest(AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::111111111111:role/ProductionAccessRole")
.roleSessionName("cross-account-session")
.externalId("unique-external-id-12345")
.build())
.build();
return S3Client.builder()
.region(Region.US_EAST_1)
.credentialsProvider(credentialsProvider)
.build();
}
The credentials provider automatically refreshes credentials before expiration, eliminating manual refresh logic.
Resource-Based Policies for Cross-Account Access
Some AWS services support resource-based policies as an alternative to role assumption. Resource-based policies are attached directly to resources (e.g., S3 buckets, SQS queues, Lambda functions) and grant permissions to principals in other accounts.
Example S3 bucket policy granting cross-account read access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/DevApplicationRole"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::production-data",
"arn:aws:s3:::production-data/*"
]
}
]
}
Resource-based policies are simpler than role assumption (no AssumeRole call needed), but they're less flexible. Role assumption allows centralized permission management in IAM roles, while resource-based policies scatter permissions across individual resources. Prefer role assumption for multi-resource access patterns.
See cross-account architecture patterns for account organization strategies.
Least Privilege and Policy Best Practices
Least privilege means granting only the minimum permissions necessary to perform a task. Overly permissive policies increase blast radius when credentials are compromised.
Permission Boundaries
Permission boundaries are advanced IAM features that set the maximum permissions an IAM entity can have, regardless of what policies grant. Permission boundaries act as guardrails for delegated administration.
Use case: Allow a team to create IAM roles for their applications without granting them full IAM admin access. The permission boundary ensures that even if the team creates overly permissive roles, those roles cannot exceed the boundary.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*",
"sqs:*"
],
"Resource": "*"
}
]
}
Attach this as a permission boundary to roles created by the team. Even if the team attaches a policy granting iam:* to their role, the permission boundary blocks IAM actions.
Service Control Policies (SCPs)
Service Control Policies are applied at the AWS Organizations level and limit permissions for all principals in an account, including the root user. SCPs define the perimeter of allowed actions - any action not explicitly allowed in an SCP is denied.
Example SCP preventing account from disabling security monitoring:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"config:DeleteConfigurationRecorder",
"config:StopConfigurationRecorder",
"guardduty:DeleteDetector"
],
"Resource": "*"
}
]
}
This SCP prevents any principal in the account from disabling CloudTrail, AWS Config, or GuardDuty - even if their IAM policies allow it. Use SCPs to enforce organization-wide security baselines.
Policy Design Patterns
Start with AWS managed policies for common use cases, then refine with customer managed policies. AWS managed policies are updated by AWS when new services launch, but they often grant broader permissions than needed.
Use specific actions instead of wildcards wherever possible. s3:GetObject is more secure than s3:*. Wildcards are convenient but increase risk.
Scope resources narrowly. Instead of "Resource": "*", specify exact ARNs: "Resource": "arn:aws:s3:::my-specific-bucket/*". Use variables for dynamic scoping:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/${aws:PrincipalTag/team}/*"
}
]
}
This policy grants access only to objects in a prefix matching the principal's team tag, enabling multi-tenant access control with a single policy.
Use conditions for context-aware access control. Common condition keys:
aws:SourceIp: Restrict access to specific IP rangesaws:SecureTransport: Require HTTPS/TLS connectionsaws:CurrentTime: Time-based access restrictionsaws:PrincipalTag/*: Tag-based access controlaws:RequestedRegion: Restrict operations to specific regions
Example requiring encryption in transit for S3 operations:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
This policy denies all S3 operations unless the connection is encrypted (HTTPS). Apply this as a bucket policy to enforce encryption in transit.
See authorization patterns for application-level policy design and Spring Boot security for integrating AWS IAM with application authorization.
Multi-Factor Authentication (MFA)
MFA adds a second factor (time-based one-time password or hardware token) to authentication, significantly reducing account compromise risk.
Enforcing MFA with IAM Policies
Use IAM policy conditions to require MFA for sensitive operations:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
This policy grants all actions but denies them if MFA is not present. Attach this to administrative users to prevent privileged operations without MFA.
For programmatic access (AWS CLI, SDKs), MFA requires generating temporary session credentials:
aws sts get-session-token \
--serial-number arn:aws:iam::123456789012:mfa/user-name \
--token-code 123456 \
--duration-seconds 3600
The returned temporary credentials include an MFA session attribute that satisfies MFA-required policy conditions.
MFA for Sensitive Operations
Apply MFA requirements selectively to high-risk operations rather than all operations. Example: require MFA for deleting S3 buckets but not for read operations:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "s3:DeleteBucket",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
This pattern balances security (MFA for destructive operations) with usability (no MFA for routine operations).
Identity Federation and Single Sign-On
Identity federation enables users to authenticate with external identity providers (corporate Active Directory, Okta, Google Workspace) and receive temporary AWS credentials via IAM roles.
SAML 2.0 Federation
SAML 2.0 federation integrates enterprise identity providers with AWS:
The SAML assertion contains user attributes (group memberships, email) that map to IAM roles. IAM role trust policies specify which SAML provider and attributes are trusted:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:saml-provider/MyCorpIdP"
},
"Action": "sts:AssumeRoleWithSAML",
"Condition": {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}
}
]
}
OIDC Federation for Web Identity
OIDC federation enables applications to authenticate users via public identity providers (Google, Facebook, Amazon) or private OIDC providers:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "accounts.google.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"accounts.google.com:aud": "my-app-client-id"
}
}
}
]
}
Applications exchange OIDC ID tokens for temporary AWS credentials, enabling user-specific resource access without managing AWS credentials in the application.
AWS IAM Identity Center (formerly AWS SSO)
IAM Identity Center is AWS's managed single sign-on solution, providing centralized access management across multiple AWS accounts and applications. Identity Center integrates with corporate identity providers via SAML 2.0 or SCIM.
Benefits over manual SAML federation:
- Centralized user and permission management
- Automatic account provisioning and deprovisioning
- Built-in permission sets (reusable IAM policies)
- Integration with AWS Organizations for multi-account access
IAM Identity Center is recommended for organizations with multiple AWS accounts and existing identity providers. See AWS Organizations for multi-account architecture patterns.
Security Auditing and Monitoring
Continuous monitoring and auditing of IAM activity is critical for detecting unauthorized access, privilege escalation, and policy violations.
CloudTrail for IAM Activity Logging
AWS CloudTrail logs all IAM API calls, providing a comprehensive audit trail for security analysis. Enable CloudTrail in all accounts and regions:
Key CloudTrail events for IAM monitoring:
AssumeRole: Track role assumption, identify unusual cross-account accessCreateAccessKey: Detect new access key creation (potential credential leakage)PutUserPolicy,AttachRolePolicy: Monitor permission changesConsoleLogin: Track user logins, identify unusual access patternsiam:PassRole: Detect privilege escalation attempts
Query CloudTrail logs with CloudWatch Logs Insights:
fields @timestamp, userIdentity.arn, eventName, sourceIPAddress, errorCode
| filter eventSource = "iam.amazonaws.com"
| filter eventName like /^(Create|Delete|Put|Attach)/
| sort @timestamp desc
| limit 100
This query identifies recent IAM permission changes, useful for investigating unauthorized modifications.
IAM Access Analyzer
IAM Access Analyzer continuously scans resource policies (S3 buckets, IAM roles, KMS keys, Lambda functions, SQS queues, Secrets Manager secrets) to identify resources shared with external principals.
Access Analyzer uses automated reasoning to analyze policies and report:
- Public access (resource accessible by any AWS principal)
- Cross-account access (resource accessible by specific external accounts)
- Cross-organization access (resource accessible by accounts outside your AWS Organization)
Enable Access Analyzer in every account and review findings regularly. Findings categorized as "resolved" are suppressed, allowing teams to acknowledge intentional external sharing while alerting on unintended exposure.
IAM Credential Reports
Credential reports provide a CSV export of all IAM users in an account with credential metadata:
- Access key age and last used date
- Password age and last used date
- MFA device status
- Last activity date
Generate reports regularly to identify:
- Unused credentials (users without recent activity)
- Aging access keys (keys not rotated in 90+ days)
- Users without MFA enabled
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d > credential-report.csv
Automate credential report analysis with Lambda functions to alert on policy violations (e.g., access keys older than 90 days).
IAM Policy Simulator
The IAM Policy Simulator is a testing tool for evaluating whether policies grant or deny specific actions without making actual API calls. Use the simulator to:
- Test new policies before deployment
- Troubleshoot permission issues
- Validate least privilege configurations
Access the simulator via AWS Console or API:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/MyRole \
--action-names s3:GetObject s3:PutObject \
--resource-arns arn:aws:s3:::my-bucket/*
The simulator returns whether each action would be allowed or denied based on the role's policies.
For detailed auditing and compliance patterns, see security testing strategies and observability.
Common IAM Anti-Patterns
Avoid these IAM mistakes that undermine security:
Using root account credentials for daily operations. The root account has unrestricted access and cannot be limited by IAM policies or SCPs. Create IAM users or roles for all operational work and secure root credentials with MFA and restricted access.
Embedding access keys in code or configuration files. Long-term credentials in source code or Docker images are easily leaked. Use IAM roles for applications running on AWS services and federated identity for external applications.
Granting *:* permissions. Policies with "Action": "*" and "Resource": "*" grant full access to all AWS services and resources. Always scope actions and resources narrowly.
Not rotating access keys. Access keys have no expiration by default. Rotate keys every 90 days or, preferably, eliminate long-term keys entirely by using IAM roles.
Ignoring IAM Access Analyzer findings. Unreviewed findings often indicate unintended public or cross-account access. Review findings promptly and archive resolved findings to maintain visibility into new exposure.
Overly permissive trust policies. Trust policies specifying "Principal": {"AWS": "arn:aws:iam::123456789012:root"} allow any principal in the account to assume the role. Specify exact user/role ARNs instead.
Not using MFA for privileged users. Privileged users (administrators, security roles) without MFA are vulnerable to credential compromise. Enforce MFA via IAM policy conditions.
Creating inline policies instead of managed policies. Inline policies are tightly coupled to principals, making reuse and auditing difficult. Use customer managed policies for reusable, auditable permissions.
Failing to test policies before deployment. Untested policies may grant unintended permissions or block legitimate access. Use the IAM Policy Simulator to validate policies before attaching them.
Sharing IAM users across people or applications. Shared credentials obscure audit trails and complicate revocation. Create individual IAM users or roles for each person and application.
Further Reading
IAM is a deep topic with extensive AWS documentation and community resources:
- AWS IAM Best Practices - AWS official best practices guide
- IAM Policy Reference - Comprehensive policy language documentation
- AWS Security Blog - Security announcements and deep dives
- IAM Roles Anywhere - IAM roles for workloads outside AWS (on-premises, other clouds)
For organization-wide security patterns, see AWS account strategy, networking security, and secrets management.
For application integration patterns, see Spring Boot AWS SDK integration, EKS IRSA configuration, and Lambda execution roles.