AW
IAM & Security
Aws security v1.0.0
AWS IAM and Security
Overview
This skill covers AWS Identity and Access Management (IAM) including policies, roles, service control policies, and security best practices. Proper IAM configuration is the foundation of AWS security.
Key Concepts
IAM Policy Evaluation
┌─────────────────────────────────────────────────────────────┐
│ IAM Policy Evaluation │
├─────────────────────────────────────────────────────────────┤
│ │
│ Request → ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Explicit Deny? ──YES──▶ DENY │ │
│ │ │ │ │
│ │ NO │ │
│ │ ▼ │ │
│ │ 2. SCP Allow? ──NO───▶ DENY │ │
│ │ │ │ │
│ │ YES │ │
│ │ ▼ │ │
│ │ 3. Resource Policy Allow? │ │
│ │ (for cross-account) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 4. Identity Policy Allow? │ │
│ │ │ │ │
│ │ YES │ │
│ │ ▼ │ │
│ │ 5. Permission Boundary Allow? │ │
│ │ │ │ │
│ │ YES │ │
│ │ ▼ │ │
│ │ ALLOW │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Policy Types: │
│ • Identity-based: Attached to users/roles/groups │
│ • Resource-based: Attached to resources (S3, SQS, etc.) │
│ • Permission boundaries: Maximum permissions for users │
│ • SCPs: Organization-wide guardrails │
│ • Session policies: Temporary credential limits │
│ │
└─────────────────────────────────────────────────────────────┘
Best Practices
1. Follow Least Privilege
Grant only the permissions required to perform a task.
2. Use IAM Roles for Applications
Never use long-term credentials in applications.
3. Implement Permission Boundaries
Set maximum permissions for users and roles.
4. Enable MFA for Privileged Access
Require MFA for sensitive operations.
5. Use AWS Organizations SCPs
Apply guardrails across all accounts.
Code Examples
Example 1: IAM Role with Least Privilege
import * as iam from 'aws-cdk-lib/aws-iam';
export class IamStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Lambda execution role with least privilege
const orderProcessorRole = new iam.Role(this, 'OrderProcessorRole', {
roleName: 'order-processor-role',
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
description: 'Role for order processing Lambda function',
maxSessionDuration: cdk.Duration.hours(1),
});
// Basic Lambda execution
orderProcessorRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
'service-role/AWSLambdaBasicExecutionRole'
)
);
// Specific DynamoDB permissions
orderProcessorRole.addToPolicy(new iam.PolicyStatement({
sid: 'DynamoDBOrderAccess',
effect: iam.Effect.ALLOW,
actions: [
'dynamodb:GetItem',
'dynamodb:PutItem',
'dynamodb:UpdateItem',
'dynamodb:Query',
],
resources: [
orderTable.tableArn,
`${orderTable.tableArn}/index/*`,
],
conditions: {
'ForAllValues:StringEquals': {
'dynamodb:LeadingKeys': ['${aws:PrincipalTag/tenant-id}'],
},
},
}));
// Specific S3 permissions
orderProcessorRole.addToPolicy(new iam.PolicyStatement({
sid: 'S3OrderDocuments',
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject',
's3:PutObject',
],
resources: [
`${documentBucket.bucketArn}/orders/*`,
],
conditions: {
'StringEquals': {
's3:x-amz-server-side-encryption': 'aws:kms',
},
},
}));
// Secrets Manager for database credentials
orderProcessorRole.addToPolicy(new iam.PolicyStatement({
sid: 'SecretsAccess',
effect: iam.Effect.ALLOW,
actions: [
'secretsmanager:GetSecretValue',
],
resources: [
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:order-service/*`,
],
}));
// KMS for encryption
orderProcessorRole.addToPolicy(new iam.PolicyStatement({
sid: 'KmsDecrypt',
effect: iam.Effect.ALLOW,
actions: [
'kms:Decrypt',
'kms:GenerateDataKey',
],
resources: [encryptionKey.keyArn],
}));
// X-Ray tracing
orderProcessorRole.addToPolicy(new iam.PolicyStatement({
sid: 'XRayTracing',
effect: iam.Effect.ALLOW,
actions: [
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
],
resources: ['*'],
}));
}
}
Example 2: Cross-Account Access
export class CrossAccountStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Role that can be assumed from another account
const crossAccountRole = new iam.Role(this, 'CrossAccountRole', {
roleName: 'cross-account-data-reader',
assumedBy: new iam.CompositePrincipal(
new iam.AccountPrincipal('123456789012'),
new iam.AccountPrincipal('234567890123'),
),
externalIds: ['shared-secret-id'], // Prevent confused deputy
maxSessionDuration: cdk.Duration.hours(1),
});
// Condition for specific roles only
crossAccountRole.assumeRolePolicy?.addStatements(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.AccountPrincipal('123456789012')],
actions: ['sts:AssumeRole'],
conditions: {
'StringEquals': {
'sts:ExternalId': 'shared-secret-id',
'aws:PrincipalArn': [
'arn:aws:iam::123456789012:role/trusted-role',
],
},
'Bool': {
'aws:MultiFactorAuthPresent': 'true',
},
},
})
);
// Read-only access to shared data
crossAccountRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject',
's3:ListBucket',
],
resources: [
sharedBucket.bucketArn,
`${sharedBucket.bucketArn}/*`,
],
}));
// S3 bucket policy for cross-account access
sharedBucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [crossAccountRole],
actions: ['s3:GetObject', 's3:ListBucket'],
resources: [
sharedBucket.bucketArn,
`${sharedBucket.bucketArn}/*`,
],
}));
}
}
Example 3: Permission Boundaries
export class PermissionBoundaryStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Permission boundary for developers
const developerBoundary = new iam.ManagedPolicy(this, 'DeveloperBoundary', {
managedPolicyName: 'developer-permission-boundary',
description: 'Maximum permissions for developer roles',
statements: [
// Allow most actions
new iam.PolicyStatement({
sid: 'AllowMostServices',
effect: iam.Effect.ALLOW,
actions: [
'lambda:*',
'dynamodb:*',
's3:*',
'sqs:*',
'sns:*',
'events:*',
'logs:*',
'cloudwatch:*',
'xray:*',
'secretsmanager:GetSecretValue',
],
resources: ['*'],
}),
// Deny privileged actions
new iam.PolicyStatement({
sid: 'DenyPrivilegedActions',
effect: iam.Effect.DENY,
actions: [
'iam:CreateUser',
'iam:CreateAccessKey',
'iam:AttachUserPolicy',
'iam:PutUserPolicy',
'organizations:*',
'account:*',
],
resources: ['*'],
}),
// Deny modifying security resources
new iam.PolicyStatement({
sid: 'DenySecurityModification',
effect: iam.Effect.DENY,
actions: [
'iam:DeleteRole',
'iam:DeleteRolePolicy',
'iam:DeleteRolePermissionsBoundary',
],
resources: [
'arn:aws:iam::*:role/security-*',
'arn:aws:iam::*:role/admin-*',
],
}),
// Deny leaving organization
new iam.PolicyStatement({
sid: 'DenyLeaveOrganization',
effect: iam.Effect.DENY,
actions: ['organizations:LeaveOrganization'],
resources: ['*'],
}),
// Require resource tagging
new iam.PolicyStatement({
sid: 'RequireResourceTags',
effect: iam.Effect.DENY,
actions: [
'lambda:CreateFunction',
'dynamodb:CreateTable',
's3:CreateBucket',
],
resources: ['*'],
conditions: {
'Null': {
'aws:RequestTag/Environment': 'true',
'aws:RequestTag/Owner': 'true',
},
},
}),
],
});
// Apply boundary to developer role
const developerRole = new iam.Role(this, 'DeveloperRole', {
roleName: 'developer-role',
assumedBy: new iam.FederatedPrincipal(
`arn:aws:iam::${this.account}:saml-provider/corporate-idp`,
{
'StringEquals': {
'SAML:aud': 'https://signin.aws.amazon.com/saml',
},
},
'sts:AssumeRoleWithSAML'
),
permissionsBoundary: developerBoundary,
});
}
}
Example 4: Service Control Policies
// SCP for production OU
const productionScp = {
Version: '2012-10-17',
Statement: [
{
Sid: 'DenyRootUser',
Effect: 'Deny',
Action: '*',
Resource: '*',
Condition: {
StringLike: {
'aws:PrincipalArn': 'arn:aws:iam::*:root',
},
},
},
{
Sid: 'RequireIMDSv2',
Effect: 'Deny',
Action: 'ec2:RunInstances',
Resource: 'arn:aws:ec2:*:*:instance/*',
Condition: {
StringNotEquals: {
'ec2:MetadataHttpTokens': 'required',
},
},
},
{
Sid: 'DenyPublicS3',
Effect: 'Deny',
Action: [
's3:PutBucketPublicAccessBlock',
's3:DeletePublicAccessBlock',
],
Resource: '*',
Condition: {
'Bool': {
's3:PublicAccessBlockConfiguration.BlockPublicAcls': 'false',
},
},
},
{
Sid: 'RequireEncryption',
Effect: 'Deny',
Action: [
's3:PutObject',
],
Resource: '*',
Condition: {
'Null': {
's3:x-amz-server-side-encryption': 'true',
},
},
},
{
Sid: 'DenyLeavingOrg',
Effect: 'Deny',
Action: 'organizations:LeaveOrganization',
Resource: '*',
},
{
Sid: 'ProtectCloudTrail',
Effect: 'Deny',
Action: [
'cloudtrail:DeleteTrail',
'cloudtrail:StopLogging',
'cloudtrail:UpdateTrail',
],
Resource: 'arn:aws:cloudtrail:*:*:trail/organization-trail',
},
{
Sid: 'RestrictRegions',
Effect: 'Deny',
Action: '*',
Resource: '*',
Condition: {
StringNotEquals: {
'aws:RequestedRegion': [
'us-east-1',
'us-west-2',
'eu-west-1',
],
},
'ForAnyValue:StringNotLike': {
'aws:PrincipalArn': [
'arn:aws:iam::*:role/OrganizationAccountAccessRole',
],
},
},
},
],
};
Example 5: Resource-Based Policies
export class ResourcePoliciesStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// KMS Key Policy
const encryptionKey = new kms.Key(this, 'DataKey', {
alias: 'alias/data-encryption-key',
enableKeyRotation: true,
policy: new iam.PolicyDocument({
statements: [
// Allow key administration
new iam.PolicyStatement({
sid: 'AllowKeyAdministration',
effect: iam.Effect.ALLOW,
principals: [new iam.AccountRootPrincipal()],
actions: ['kms:*'],
resources: ['*'],
}),
// Allow use by specific roles
new iam.PolicyStatement({
sid: 'AllowKeyUsage',
effect: iam.Effect.ALLOW,
principals: [
new iam.ArnPrincipal(orderProcessorRole.roleArn),
new iam.ServicePrincipal('lambda.amazonaws.com'),
],
actions: [
'kms:Encrypt',
'kms:Decrypt',
'kms:GenerateDataKey*',
],
resources: ['*'],
conditions: {
StringEquals: {
'kms:ViaService': `lambda.${this.region}.amazonaws.com`,
'kms:CallerAccount': this.account,
},
},
}),
// Allow grants for AWS services
new iam.PolicyStatement({
sid: 'AllowServiceGrants',
effect: iam.Effect.ALLOW,
principals: [new iam.AnyPrincipal()],
actions: ['kms:CreateGrant'],
resources: ['*'],
conditions: {
Bool: {
'kms:GrantIsForAWSResource': 'true',
},
},
}),
],
}),
});
// SQS Queue Policy
const orderQueue = new sqs.Queue(this, 'OrderQueue', {
queueName: 'order-queue',
});
orderQueue.addToResourcePolicy(new iam.PolicyStatement({
sid: 'AllowSNSPublish',
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal('sns.amazonaws.com')],
actions: ['sqs:SendMessage'],
resources: [orderQueue.queueArn],
conditions: {
ArnEquals: {
'aws:SourceArn': orderTopic.topicArn,
},
},
}));
// Secrets Manager resource policy
const dbSecret = new secretsmanager.Secret(this, 'DbSecret', {
secretName: 'db-credentials',
});
dbSecret.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [orderProcessorRole],
actions: ['secretsmanager:GetSecretValue'],
resources: ['*'],
conditions: {
StringEquals: {
'secretsmanager:VersionStage': 'AWSCURRENT',
},
},
}));
// Deny access to old versions
dbSecret.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['secretsmanager:GetSecretValue'],
resources: ['*'],
conditions: {
StringNotEquals: {
'secretsmanager:VersionStage': 'AWSCURRENT',
},
ArnNotEquals: {
'aws:PrincipalArn': [
adminRole.roleArn,
],
},
},
}));
}
}
Anti-Patterns
❌ Overly Permissive Policies
// WRONG - full admin access
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
// ✅ CORRECT - specific permissions
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:*:*:table/orders"
}
❌ Long-Term Credentials
// WRONG - hardcoded credentials
const client = new S3Client({
credentials: {
accessKeyId: 'AKIA...',
secretAccessKey: '...',
},
});
// ✅ CORRECT - use IAM roles
const client = new S3Client({}); // Uses instance profile or ECS task role