Skip to content
Home / Skills / Aws / IAM & Security
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

References