Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/com.pulumi/aws@7.16.x

docs

guides

data-pipeline.mdsecurity-best-practices.mdweb-application.md
common-patterns.mdgetting-started.mdindex.mdprovider.md
tile.json

tessl/maven-com-pulumi--aws

tessl install tessl/maven-com-pulumi--aws@7.16.0

Pulumi Java SDK for AWS providing strongly-typed Infrastructure-as-Code for 227 AWS service packages including compute, storage, databases, networking, security, analytics, machine learning, and more.

security-best-practices.mddocs/guides/

AWS Security Best Practices with Pulumi

This comprehensive guide demonstrates security best practices for AWS infrastructure using Pulumi. Implement defense-in-depth with encryption, access controls, monitoring, and compliance.

Security Principles

  1. Principle of Least Privilege - Grant only necessary permissions
  2. Defense in Depth - Multiple layers of security controls
  3. Encryption Everywhere - At rest and in transit
  4. Audit Everything - Comprehensive logging and monitoring
  5. Automate Security - Infrastructure as code with security built-in
  6. Zero Trust - Never trust, always verify

Architecture Overview

┌──────────────────────────────────────────────────────┐
│              Identity & Access Management            │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐    │
│  │ IAM Roles  │  │ IAM Users  │  │ IAM Groups │    │
│  └────────────┘  └────────────┘  └────────────┘    │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────┴───────────────────────────────┐
│                  Encryption Layer                     │
│  ┌────────────┐  ┌─────────────┐  ┌──────────────┐ │
│  │ KMS Keys   │  │   Secrets    │  │     ACM      │ │
│  │            │  │   Manager    │  │ Certificates │ │
│  └────────────┘  └─────────────┘  └──────────────┘ │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────┴───────────────────────────────┐
│              Network Security Layer                   │
│  ┌────────────┐  ┌─────────────┐  ┌──────────────┐ │
│  │  Security  │  │   NACLs     │  │   WAF Rules  │ │
│  │   Groups   │  │             │  │              │ │
│  └────────────┘  └─────────────┘  └──────────────┘ │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────┴───────────────────────────────┐
│                 Audit & Compliance                    │
│  ┌────────────┐  ┌─────────────┐  ┌──────────────┐ │
│  │ CloudTrail │  │  Config     │  │ GuardDuty    │ │
│  │            │  │  Rules      │  │              │ │
│  └────────────┘  └─────────────┘  └──────────────┘ │
└───────────────────────────────────────────────────────┘

Complete Security Implementation

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Configuration
const config = new pulumi.Config();
const projectName = "secure-app";
const environment = pulumi.getStack();

// Tags for all resources
const tags = {
    Environment: environment,
    Project: projectName,
    ManagedBy: "pulumi",
    SecurityLevel: "high",
};

// ============================================================================
// 1. KMS ENCRYPTION KEYS
// ============================================================================

// Create customer managed KMS key for application data
const appKmsKey = new aws.kms.Key("app-key", {
    description: `${projectName} application encryption key`,
    deletionWindowInDays: 30,
    enableKeyRotation: true,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Sid: "Enable IAM User Permissions",
                Effect: "Allow",
                Principal: {
                    AWS: pulumi.interpolate`arn:aws:iam::${aws.getCallerIdentityOutput().accountId}:root`,
                },
                Action: "kms:*",
                Resource: "*",
            },
            {
                Sid: "Allow CloudWatch Logs",
                Effect: "Allow",
                Principal: {
                    Service: `logs.${aws.getRegionOutput().name}.amazonaws.com`,
                },
                Action: [
                    "kms:Encrypt",
                    "kms:Decrypt",
                    "kms:ReEncrypt*",
                    "kms:GenerateDataKey*",
                    "kms:CreateGrant",
                    "kms:DescribeKey",
                ],
                Resource: "*",
                Condition: {
                    ArnLike: {
                        "kms:EncryptionContext:aws:logs:arn": pulumi.interpolate`arn:aws:logs:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:*`,
                    },
                },
            },
        ],
    }),
    tags,
});

// Create alias for easy reference
new aws.kms.Alias("app-key-alias", {
    name: `alias/${projectName}-app`,
    targetKeyId: appKmsKey.id,
});

// Create separate KMS key for S3 data
const s3KmsKey = new aws.kms.Key("s3-key", {
    description: `${projectName} S3 encryption key`,
    deletionWindowInDays: 30,
    enableKeyRotation: true,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Sid: "Enable IAM User Permissions",
                Effect: "Allow",
                Principal: {
                    AWS: pulumi.interpolate`arn:aws:iam::${aws.getCallerIdentityOutput().accountId}:root`,
                },
                Action: "kms:*",
                Resource: "*",
            },
            {
                Sid: "Allow S3 to use the key",
                Effect: "Allow",
                Principal: {
                    Service: "s3.amazonaws.com",
                },
                Action: [
                    "kms:Decrypt",
                    "kms:GenerateDataKey",
                ],
                Resource: "*",
            },
        ],
    }),
    tags,
});

new aws.kms.Alias("s3-key-alias", {
    name: `alias/${projectName}-s3`,
    targetKeyId: s3KmsKey.id,
});

// Create KMS key for RDS
const rdsKmsKey = new aws.kms.Key("rds-key", {
    description: `${projectName} RDS encryption key`,
    deletionWindowInDays: 30,
    enableKeyRotation: true,
    tags,
});

new aws.kms.Alias("rds-key-alias", {
    name: `alias/${projectName}-rds`,
    targetKeyId: rdsKmsKey.id,
});

// ============================================================================
// 2. SECRETS MANAGER
// ============================================================================

// Store database credentials in Secrets Manager
const dbSecret = new aws.secretsmanager.Secret("db-credentials", {
    name: `${projectName}/db/credentials`,
    description: "Database credentials",
    kmsKeyId: appKmsKey.id,
    recoveryWindowInDays: 30,
    tags,
});

// Generate random password
const randomPassword = new aws.secretsmanager.SecretVersion("db-password-version", {
    secretId: dbSecret.id,
    secretString: JSON.stringify({
        username: "dbadmin",
        password: config.requireSecret("dbPassword"),
        engine: "postgres",
        host: "", // Will be updated after RDS creation
        port: 5432,
        dbname: "appdb",
    }),
});

// Store API keys
const apiKeySecret = new aws.secretsmanager.Secret("api-keys", {
    name: `${projectName}/api/keys`,
    description: "Third-party API keys",
    kmsKeyId: appKmsKey.id,
    recoveryWindowInDays: 30,
    tags,
});

new aws.secretsmanager.SecretVersion("api-keys-version", {
    secretId: apiKeySecret.id,
    secretString: JSON.stringify({
        stripe: config.getSecret("stripeApiKey") || "placeholder",
        sendgrid: config.getSecret("sendgridApiKey") || "placeholder",
    }),
});

// Enable automatic rotation for database credentials
const rotationLambdaRole = new aws.iam.Role("rotation-lambda-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Effect: "Allow",
            Principal: {
                Service: "lambda.amazonaws.com",
            },
        }],
    }),
    tags,
});

// ============================================================================
// 3. IAM ROLES AND POLICIES
// ============================================================================

// Application role for EC2/ECS with least privilege
const appRole = new aws.iam.Role("app-role", {
    name: `${projectName}-app-role`,
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Action: "sts:AssumeRole",
                Effect: "Allow",
                Principal: {
                    Service: ["ec2.amazonaws.com", "ecs-tasks.amazonaws.com"],
                },
            },
        ],
    }),
    maxSessionDuration: 3600, // 1 hour
    tags,
});

// Custom policy with specific permissions
const appPolicy = new aws.iam.Policy("app-policy", {
    name: `${projectName}-app-policy`,
    description: "Application permissions with least privilege",
    policy: pulumi.all([appKmsKey.arn, s3KmsKey.arn, dbSecret.arn, apiKeySecret.arn]).apply(
        ([appKeyArn, s3KeyArn, dbSecretArn, apiSecretArn]) =>
            JSON.stringify({
                Version: "2012-10-17",
                Statement: [
                    {
                        Sid: "ReadSecretsManager",
                        Effect: "Allow",
                        Action: [
                            "secretsmanager:GetSecretValue",
                            "secretsmanager:DescribeSecret",
                        ],
                        Resource: [dbSecretArn, apiSecretArn],
                    },
                    {
                        Sid: "DecryptSecrets",
                        Effect: "Allow",
                        Action: [
                            "kms:Decrypt",
                            "kms:DescribeKey",
                        ],
                        Resource: [appKeyArn],
                        Condition: {
                            StringEquals: {
                                "kms:ViaService": `secretsmanager.${aws.getRegionOutput().name}.amazonaws.com`,
                            },
                        },
                    },
                    {
                        Sid: "WriteApplicationLogs",
                        Effect: "Allow",
                        Action: [
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        Resource: pulumi.interpolate`arn:aws:logs:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:log-group:/app/${projectName}:*`,
                    },
                    {
                        Sid: "ReadWriteS3Data",
                        Effect: "Allow",
                        Action: [
                            "s3:GetObject",
                            "s3:PutObject",
                            "s3:DeleteObject",
                        ],
                        Resource: pulumi.interpolate`arn:aws:s3:::${projectName}-data-*/*`,
                    },
                    {
                        Sid: "EncryptS3Objects",
                        Effect: "Allow",
                        Action: [
                            "kms:Decrypt",
                            "kms:Encrypt",
                            "kms:GenerateDataKey",
                        ],
                        Resource: [s3KeyArn],
                        Condition: {
                            StringEquals: {
                                "kms:ViaService": `s3.${aws.getRegionOutput().name}.amazonaws.com`,
                            },
                        },
                    },
                ],
            })
    ),
    tags,
});

// Attach policy to role
new aws.iam.RolePolicyAttachment("app-policy-attachment", {
    role: appRole.name,
    policyArn: appPolicy.arn,
});

// Create instance profile for EC2
const appInstanceProfile = new aws.iam.InstanceProfile("app-instance-profile", {
    name: `${projectName}-instance-profile`,
    role: appRole.name,
});

// Lambda execution role with restricted permissions
const lambdaRole = new aws.iam.Role("lambda-role", {
    name: `${projectName}-lambda-role`,
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Effect: "Allow",
            Principal: {
                Service: "lambda.amazonaws.com",
            },
        }],
    }),
    tags,
});

// Attach managed policy for Lambda execution
new aws.iam.RolePolicyAttachment("lambda-basic-execution", {
    role: lambdaRole.name,
    policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
});

// Cross-account access role (for external services)
const crossAccountRole = new aws.iam.Role("cross-account-role", {
    name: `${projectName}-cross-account`,
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: {
                AWS: config.require("trustedAccountId"),
            },
            Action: "sts:AssumeRole",
            Condition: {
                StringEquals: {
                    "sts:ExternalId": config.requireSecret("externalId"),
                },
            },
        }],
    }),
    tags,
});

// ============================================================================
// 4. SECURITY GROUPS WITH LEAST PRIVILEGE
// ============================================================================

// VPC for secure networking
const vpc = new aws.ec2.Vpc("vpc", {
    cidrBlock: "10.0.0.0/16",
    enableDnsHostnames: true,
    enableDnsSupport: true,
    tags: { ...tags, Name: `${projectName}-vpc` },
});

// Enable VPC Flow Logs
const flowLogsRole = new aws.iam.Role("flow-logs-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Effect: "Allow",
            Principal: {
                Service: "vpc-flow-logs.amazonaws.com",
            },
        }],
    }),
    tags,
});

const flowLogsPolicy = new aws.iam.RolePolicy("flow-logs-policy", {
    role: flowLogsRole.id,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams",
            ],
            Resource: "*",
        }],
    }),
});

const flowLogsGroup = new aws.cloudwatch.LogGroup("vpc-flow-logs", {
    name: `/aws/vpc/${projectName}`,
    retentionInDays: 90,
    kmsKeyId: appKmsKey.arn,
    tags,
});

const vpcFlowLog = new aws.ec2.FlowLog("vpc-flow-log", {
    vpcId: vpc.id,
    trafficType: "ALL",
    logDestinationType: "cloud-watch-logs",
    logDestination: flowLogsGroup.arn,
    iamRoleArn: flowLogsRole.arn,
    tags,
}, { dependsOn: [flowLogsPolicy] });

// Application Load Balancer Security Group
const albSg = new aws.ec2.SecurityGroup("alb-sg", {
    name: `${projectName}-alb-sg`,
    description: "Security group for Application Load Balancer",
    vpcId: vpc.id,
    tags: { ...tags, Name: `${projectName}-alb-sg`, Layer: "public" },
});

// Allow HTTPS only from internet
new aws.ec2.SecurityGroupRule("alb-ingress-https", {
    type: "ingress",
    securityGroupId: albSg.id,
    protocol: "tcp",
    fromPort: 443,
    toPort: 443,
    cidrBlocks: ["0.0.0.0/0"],
    description: "Allow HTTPS from internet",
});

// Redirect HTTP to HTTPS
new aws.ec2.SecurityGroupRule("alb-ingress-http", {
    type: "ingress",
    securityGroupId: albSg.id,
    protocol: "tcp",
    fromPort: 80,
    toPort: 80,
    cidrBlocks: ["0.0.0.0/0"],
    description: "Allow HTTP for redirect to HTTPS",
});

// Egress to application tier only
new aws.ec2.SecurityGroupRule("alb-egress", {
    type: "egress",
    securityGroupId: albSg.id,
    protocol: "tcp",
    fromPort: 8080,
    toPort: 8080,
    sourceSecurityGroupId: "", // Will reference app SG
    description: "Allow traffic to application tier",
});

// Application Security Group
const appSg = new aws.ec2.SecurityGroup("app-sg", {
    name: `${projectName}-app-sg`,
    description: "Security group for application tier",
    vpcId: vpc.id,
    tags: { ...tags, Name: `${projectName}-app-sg`, Layer: "application" },
});

// Allow traffic from ALB only
new aws.ec2.SecurityGroupRule("app-ingress-alb", {
    type: "ingress",
    securityGroupId: appSg.id,
    protocol: "tcp",
    fromPort: 8080,
    toPort: 8080,
    sourceSecurityGroupId: albSg.id,
    description: "Allow traffic from ALB only",
});

// Allow HTTPS for external API calls
new aws.ec2.SecurityGroupRule("app-egress-https", {
    type: "egress",
    securityGroupId: appSg.id,
    protocol: "tcp",
    fromPort: 443,
    toPort: 443,
    cidrBlocks: ["0.0.0.0/0"],
    description: "Allow HTTPS for external APIs",
});

// Database Security Group
const dbSg = new aws.ec2.SecurityGroup("db-sg", {
    name: `${projectName}-db-sg`,
    description: "Security group for database tier",
    vpcId: vpc.id,
    tags: { ...tags, Name: `${projectName}-db-sg`, Layer: "database" },
});

// Allow PostgreSQL from application tier only
new aws.ec2.SecurityGroupRule("db-ingress-app", {
    type: "ingress",
    securityGroupId: dbSg.id,
    protocol: "tcp",
    fromPort: 5432,
    toPort: 5432,
    sourceSecurityGroupId: appSg.id,
    description: "Allow PostgreSQL from application tier only",
});

// No egress rules (database doesn't need outbound)
new aws.ec2.SecurityGroupRule("db-no-egress", {
    type: "egress",
    securityGroupId: dbSg.id,
    protocol: "-1",
    fromPort: 0,
    toPort: 0,
    cidrBlocks: ["127.0.0.1/32"],
    description: "Deny all egress",
});

// ============================================================================
// 5. S3 BUCKET SECURITY
// ============================================================================

// Secure S3 bucket with encryption and access controls
const secureBucket = new aws.s3.BucketV2("secure-bucket", {
    bucket: `${projectName}-data-${aws.getCallerIdentityOutput().accountId}`,
    tags,
});

// Enable versioning
new aws.s3.BucketVersioningV2("secure-versioning", {
    bucket: secureBucket.id,
    versioningConfiguration: {
        status: "Enabled",
    },
});

// Block all public access
new aws.s3.BucketPublicAccessBlock("secure-public-access-block", {
    bucket: secureBucket.id,
    blockPublicAcls: true,
    blockPublicPolicy: true,
    ignorePublicAcls: true,
    restrictPublicBuckets: true,
});

// Enable encryption with KMS
new aws.s3.BucketServerSideEncryptionConfigurationV2("secure-encryption", {
    bucket: secureBucket.id,
    rules: [{
        applyServerSideEncryptionByDefault: {
            sseAlgorithm: "aws:kms",
            kmsMasterKeyId: s3KmsKey.id,
        },
        bucketKeyEnabled: true,
    }],
});

// Enable logging
const logBucket = new aws.s3.BucketV2("log-bucket", {
    bucket: `${projectName}-logs-${aws.getCallerIdentityOutput().accountId}`,
    tags,
});

new aws.s3.BucketPublicAccessBlock("log-public-access-block", {
    bucket: logBucket.id,
    blockPublicAcls: true,
    blockPublicPolicy: true,
    ignorePublicAcls: true,
    restrictPublicBuckets: true,
});

new aws.s3.BucketLoggingV2("secure-logging", {
    bucket: secureBucket.id,
    targetBucket: logBucket.id,
    targetPrefix: "s3-access-logs/",
});

// Bucket policy with strict access controls
const secureBucketPolicy = new aws.s3.BucketPolicy("secure-bucket-policy", {
    bucket: secureBucket.id,
    policy: pulumi.all([secureBucket.arn, appRole.arn]).apply(([bucketArn, roleArn]) =>
        JSON.stringify({
            Version: "2012-10-17",
            Statement: [
                {
                    Sid: "DenyInsecureTransport",
                    Effect: "Deny",
                    Principal: "*",
                    Action: "s3:*",
                    Resource: [bucketArn, `${bucketArn}/*`],
                    Condition: {
                        Bool: {
                            "aws:SecureTransport": "false",
                        },
                    },
                },
                {
                    Sid: "DenyUnencryptedObjectUploads",
                    Effect: "Deny",
                    Principal: "*",
                    Action: "s3:PutObject",
                    Resource: `${bucketArn}/*`,
                    Condition: {
                        StringNotEquals: {
                            "s3:x-amz-server-side-encryption": "aws:kms",
                        },
                    },
                },
                {
                    Sid: "AllowApplicationRole",
                    Effect: "Allow",
                    Principal: {
                        AWS: roleArn,
                    },
                    Action: [
                        "s3:GetObject",
                        "s3:PutObject",
                        "s3:DeleteObject",
                    ],
                    Resource: `${bucketArn}/*`,
                },
            ],
        })
    ),
});

// Object lock for compliance
new aws.s3.BucketObjectLockConfigurationV2("secure-object-lock", {
    bucket: secureBucket.id,
    objectLockEnabled: "Enabled",
    rule: {
        defaultRetention: {
            mode: "GOVERNANCE",
            days: 30,
        },
    },
});

// ============================================================================
// 6. CLOUDTRAIL AUDIT LOGGING
// ============================================================================

// S3 bucket for CloudTrail logs
const cloudtrailBucket = new aws.s3.BucketV2("cloudtrail-bucket", {
    bucket: `${projectName}-cloudtrail-${aws.getCallerIdentityOutput().accountId}`,
    tags,
});

new aws.s3.BucketPublicAccessBlock("cloudtrail-public-access-block", {
    bucket: cloudtrailBucket.id,
    blockPublicAcls: true,
    blockPublicPolicy: true,
    ignorePublicAcls: true,
    restrictPublicBuckets: true,
});

// Bucket policy for CloudTrail
const cloudtrailBucketPolicy = new aws.s3.BucketPolicy("cloudtrail-bucket-policy", {
    bucket: cloudtrailBucket.id,
    policy: pulumi.all([cloudtrailBucket.arn]).apply(([bucketArn]) =>
        JSON.stringify({
            Version: "2012-10-17",
            Statement: [
                {
                    Sid: "AWSCloudTrailAclCheck",
                    Effect: "Allow",
                    Principal: {
                        Service: "cloudtrail.amazonaws.com",
                    },
                    Action: "s3:GetBucketAcl",
                    Resource: bucketArn,
                },
                {
                    Sid: "AWSCloudTrailWrite",
                    Effect: "Allow",
                    Principal: {
                        Service: "cloudtrail.amazonaws.com",
                    },
                    Action: "s3:PutObject",
                    Resource: `${bucketArn}/*`,
                    Condition: {
                        StringEquals: {
                            "s3:x-amz-acl": "bucket-owner-full-control",
                        },
                    },
                },
            ],
        })
    ),
});

// CloudWatch Log Group for CloudTrail
const cloudtrailLogGroup = new aws.cloudwatch.LogGroup("cloudtrail-logs", {
    name: `/aws/cloudtrail/${projectName}`,
    retentionInDays: 365,
    kmsKeyId: appKmsKey.arn,
    tags,
});

// IAM role for CloudTrail to write to CloudWatch Logs
const cloudtrailRole = new aws.iam.Role("cloudtrail-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Effect: "Allow",
            Principal: {
                Service: "cloudtrail.amazonaws.com",
            },
        }],
    }),
    tags,
});

new aws.iam.RolePolicy("cloudtrail-policy", {
    role: cloudtrailRole.id,
    policy: pulumi.all([cloudtrailLogGroup.arn]).apply(([logGroupArn]) =>
        JSON.stringify({
            Version: "2012-10-17",
            Statement: [{
                Effect: "Allow",
                Action: [
                    "logs:CreateLogStream",
                    "logs:PutLogEvents",
                ],
                Resource: `${logGroupArn}:*`,
            }],
        })
    ),
});

// Create CloudTrail
const trail = new aws.cloudtrail.Trail("trail", {
    name: `${projectName}-trail`,
    s3BucketName: cloudtrailBucket.bucket,
    includeGlobalServiceEvents: true,
    isMultiRegionTrail: true,
    enableLogFileValidation: true,
    cloudWatchLogsGroupArn: cloudtrailLogGroup.arn,
    cloudWatchLogsRoleArn: cloudtrailRole.arn,
    kmsKeyId: appKmsKey.id,
    eventSelectors: [{
        readWriteType: "All",
        includeManagementEvents: true,
        dataResources: [
            {
                type: "AWS::S3::Object",
                values: [pulumi.interpolate`${secureBucket.arn}/`],
            },
            {
                type: "AWS::Lambda::Function",
                values: ["arn:aws:lambda"],
            },
        ],
    }],
    tags,
}, { dependsOn: [cloudtrailBucketPolicy] });

// ============================================================================
// 7. AWS CONFIG FOR COMPLIANCE
// ============================================================================

// S3 bucket for Config
const configBucket = new aws.s3.BucketV2("config-bucket", {
    bucket: `${projectName}-config-${aws.getCallerIdentityOutput().accountId}`,
    tags,
});

new aws.s3.BucketPublicAccessBlock("config-public-access-block", {
    bucket: configBucket.id,
    blockPublicAcls: true,
    blockPublicPolicy: true,
    ignorePublicAcls: true,
    restrictPublicBuckets: true,
});

// IAM role for Config
const configRole = new aws.iam.Role("config-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Effect: "Allow",
            Principal: {
                Service: "config.amazonaws.com",
            },
        }],
    }),
    managedPolicyArns: [
        "arn:aws:iam::aws:policy/service-role/ConfigRole",
    ],
    tags,
});

// Config recorder
const configRecorder = new aws.cfg.Recorder("config-recorder", {
    name: `${projectName}-recorder`,
    roleArn: configRole.arn,
    recordingGroup: {
        allSupported: true,
        includeGlobalResourceTypes: true,
    },
});

// Config delivery channel
const configDeliveryChannel = new aws.cfg.DeliveryChannel("config-delivery", {
    name: `${projectName}-delivery`,
    s3BucketName: configBucket.bucket,
    snapshotDeliveryProperties: {
        deliveryFrequency: "TwentyFour_Hours",
    },
}, { dependsOn: [configRecorder] });

// Start Config recorder
new aws.cfg.RecorderStatus("config-recorder-status", {
    name: configRecorder.name,
    isEnabled: true,
}, { dependsOn: [configDeliveryChannel] });

// Config rules for compliance
new aws.cfg.Rule("encrypted-volumes", {
    name: "encrypted-volumes",
    description: "Check that EBS volumes are encrypted",
    source: {
        owner: "AWS",
        sourceIdentifier: "ENCRYPTED_VOLUMES",
    },
}, { dependsOn: [configRecorder] });

new aws.cfg.Rule("s3-bucket-public-read-prohibited", {
    name: "s3-bucket-public-read-prohibited",
    description: "Check that S3 buckets do not allow public read access",
    source: {
        owner: "AWS",
        sourceIdentifier: "S3_BUCKET_PUBLIC_READ_PROHIBITED",
    },
}, { dependsOn: [configRecorder] });

new aws.cfg.Rule("rds-storage-encrypted", {
    name: "rds-storage-encrypted",
    description: "Check that RDS instances are encrypted",
    source: {
        owner: "AWS",
        sourceIdentifier: "RDS_STORAGE_ENCRYPTED",
    },
}, { dependsOn: [configRecorder] });

// ============================================================================
// 8. GUARDDUTY THREAT DETECTION
// ============================================================================

// Enable GuardDuty
const guardduty = new aws.guardduty.Detector("guardduty", {
    enable: true,
    findingPublishingFrequency: "FIFTEEN_MINUTES",
    tags,
});

// SNS topic for GuardDuty findings
const securityAlertTopic = new aws.sns.Topic("security-alerts", {
    name: `${projectName}-security-alerts`,
    displayName: "Security Alerts",
    kmsMasterKeyId: appKmsKey.id,
    tags,
});

// Email subscription for security alerts
new aws.sns.TopicSubscription("security-email", {
    topic: securityAlertTopic.arn,
    protocol: "email",
    endpoint: config.require("securityEmail"),
});

// EventBridge rule for high severity findings
const guarddutyRule = new aws.cloudwatch.EventRule("guardduty-findings", {
    name: `${projectName}-guardduty-high`,
    description: "Alert on high severity GuardDuty findings",
    eventPattern: JSON.stringify({
        source: ["aws.guardduty"],
        "detail-type": ["GuardDuty Finding"],
        detail: {
            severity: [7, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9],
        },
    }),
});

new aws.cloudwatch.EventTarget("guardduty-sns", {
    rule: guarddutyRule.name,
    arn: securityAlertTopic.arn,
});

// ============================================================================
// 9. SECURITY HUB
// ============================================================================

// Enable Security Hub
const securityHub = new aws.securityhub.Account("security-hub", {
    enableDefaultStandards: true,
});

// Enable AWS Foundational Security Best Practices standard
new aws.securityhub.StandardsSubscription("fsbp-standard", {
    standardsArn: pulumi.interpolate`arn:aws:securityhub:${aws.getRegionOutput().name}::standards/aws-foundational-security-best-practices/v/1.0.0`,
}, { dependsOn: [securityHub] });

// Enable CIS AWS Foundations Benchmark
new aws.securityhub.StandardsSubscription("cis-standard", {
    standardsArn: pulumi.interpolate`arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0`,
}, { dependsOn: [securityHub] });

// ============================================================================
// 10. CLOUDWATCH ALARMS FOR SECURITY EVENTS
// ============================================================================

// Metric filter for unauthorized API calls
const unauthorizedApiFilter = new aws.cloudwatch.LogMetricFilter("unauthorized-api-calls", {
    name: "UnauthorizedAPICalls",
    logGroupName: cloudtrailLogGroup.name,
    pattern: '{ ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") }',
    metricTransformation: {
        name: "UnauthorizedAPICalls",
        namespace: "CloudTrailMetrics",
        value: "1",
        defaultValue: "0",
    },
});

new aws.cloudwatch.MetricAlarm("unauthorized-api-alarm", {
    name: `${projectName}-unauthorized-api`,
    comparisonOperator: "GreaterThanThreshold",
    evaluationPeriods: 1,
    metricName: "UnauthorizedAPICalls",
    namespace: "CloudTrailMetrics",
    period: 300,
    statistic: "Sum",
    threshold: 5,
    alarmDescription: "Alert on unauthorized API calls",
    alarmActions: [securityAlertTopic.arn],
    tags,
}, { dependsOn: [unauthorizedApiFilter] });

// Metric filter for root account usage
const rootAccountFilter = new aws.cloudwatch.LogMetricFilter("root-account-usage", {
    name: "RootAccountUsage",
    logGroupName: cloudtrailLogGroup.name,
    pattern: '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }',
    metricTransformation: {
        name: "RootAccountUsage",
        namespace: "CloudTrailMetrics",
        value: "1",
        defaultValue: "0",
    },
});

new aws.cloudwatch.MetricAlarm("root-account-alarm", {
    name: `${projectName}-root-account`,
    comparisonOperator: "GreaterThanThreshold",
    evaluationPeriods: 1,
    metricName: "RootAccountUsage",
    namespace: "CloudTrailMetrics",
    period: 60,
    statistic: "Sum",
    threshold: 0,
    alarmDescription: "Alert on root account usage",
    alarmActions: [securityAlertTopic.arn],
    tags,
}, { dependsOn: [rootAccountFilter] });

// Metric filter for IAM policy changes
const iamPolicyFilter = new aws.cloudwatch.LogMetricFilter("iam-policy-changes", {
    name: "IAMPolicyChanges",
    logGroupName: cloudtrailLogGroup.name,
    pattern: '{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}',
    metricTransformation: {
        name: "IAMPolicyChanges",
        namespace: "CloudTrailMetrics",
        value: "1",
        defaultValue: "0",
    },
});

new aws.cloudwatch.MetricAlarm("iam-policy-alarm", {
    name: `${projectName}-iam-changes`,
    comparisonOperator: "GreaterThanThreshold",
    evaluationPeriods: 1,
    metricName: "IAMPolicyChanges",
    namespace: "CloudTrailMetrics",
    period: 300,
    statistic: "Sum",
    threshold: 0,
    alarmDescription: "Alert on IAM policy changes",
    alarmActions: [securityAlertTopic.arn],
    tags,
}, { dependsOn: [iamPolicyFilter] });

// ============================================================================
// OUTPUTS
// ============================================================================

export const appKmsKeyId = appKmsKey.id;
export const appKmsKeyArn = appKmsKey.arn;
export const s3KmsKeyId = s3KmsKey.id;
export const dbSecretArn = dbSecret.arn;
export const appRoleArn = appRole.arn;
export const secureBucketName = secureBucket.bucket;
export const cloudtrailBucketName = cloudtrailBucket.bucket;
export const securityAlertTopicArn = securityAlertTopic.arn;
export const vpcId = vpc.id;
export const appSecurityGroupId = appSg.id;

Security Checklist

Identity and Access Management

  • ✓ IAM roles with least privilege
  • ✓ No hardcoded credentials
  • ✓ MFA enabled for console access
  • ✓ Regular access key rotation
  • ✓ Cross-account roles with external ID
  • ✓ Service control policies (SCPs)

Encryption

  • ✓ KMS keys with rotation enabled
  • ✓ S3 encryption at rest
  • ✓ RDS encryption at rest
  • ✓ EBS encryption enabled
  • ✓ Secrets Manager for credentials
  • ✓ SSL/TLS for data in transit

Network Security

  • ✓ VPC with private subnets
  • ✓ Security groups with specific rules
  • ✓ No default security group usage
  • ✓ VPC Flow Logs enabled
  • ✓ Network ACLs for additional layer
  • ✓ No public databases or resources

Monitoring and Logging

  • ✓ CloudTrail enabled globally
  • ✓ CloudWatch Logs with retention
  • ✓ GuardDuty threat detection
  • ✓ Config compliance tracking
  • ✓ Security Hub enabled
  • ✓ Alarms for security events

Data Protection

  • ✓ S3 versioning enabled
  • ✓ S3 Object Lock for compliance
  • ✓ Backup retention policies
  • ✓ Database automated backups
  • ✓ Cross-region replication
  • ✓ Access logging enabled

Compliance

  • ✓ AWS Config rules
  • ✓ Security Hub standards
  • ✓ Resource tagging
  • ✓ Cost allocation tags
  • ✓ Audit trail preservation
  • ✓ Regular compliance reports

Common Security Patterns

Secure Lambda Function

const secureLambda = new aws.lambda.Function("secure-function", {
    runtime: aws.lambda.Runtime.Python3d11,
    handler: "index.handler",
    role: lambdaRole.arn,
    timeout: 30,
    reservedConcurrentExecutions: 10,
    deadLetterConfig: {
        targetArn: dlqQueue.arn,
    },
    environment: {
        variables: {
            SECRET_ARN: dbSecret.arn,
        },
    },
    vpcConfig: {
        subnetIds: privateSubnets.map(s => s.id),
        securityGroupIds: [lambdaSg.id],
    },
    tracingConfig: {
        mode: "Active",
    },
    kmsKeyArn: appKmsKey.arn,
    code: new pulumi.asset.AssetArchive({
        "index.py": new pulumi.asset.StringAsset(`
import boto3
import json
import os

secrets_client = boto3.client('secretsmanager')

def handler(event, context):
    # Retrieve secrets securely
    secret_arn = os.environ['SECRET_ARN']
    secret = secrets_client.get_secret_value(SecretId=secret_arn)
    credentials = json.loads(secret['SecretString'])

    # Use credentials securely
    # Never log sensitive data

    return {
        'statusCode': 200,
        'body': json.dumps('Success')
    }
`),
    }),
    tags,
});

Secure RDS Database

const secureDb = new aws.rds.Instance("secure-db", {
    identifier: `${projectName}-db`,
    engine: "postgres",
    engineVersion: "15.5",
    instanceClass: "db.t3.small",
    allocatedStorage: 20,
    storageEncrypted: true,
    kmsKeyId: rdsKmsKey.id,
    username: "dbadmin",
    password: dbPassword,
    dbSubnetGroupName: dbSubnetGroup.name,
    vpcSecurityGroupIds: [dbSg.id],
    publiclyAccessible: false,
    multiAz: true,
    backupRetentionPeriod: 30,
    backupWindow: "03:00-04:00",
    maintenanceWindow: "mon:04:00-mon:05:00",
    enabledCloudwatchLogsExports: ["postgresql", "upgrade"],
    performanceInsightsEnabled: true,
    performanceInsightsKmsKeyId: rdsKmsKey.id,
    deletionProtection: true,
    copyTagsToSnapshot: true,
    iamDatabaseAuthenticationEnabled: true,
    tags,
});

Secure ECS Task

const secureTaskDefinition = new aws.ecs.TaskDefinition("secure-task", {
    family: `${projectName}-task`,
    networkMode: "awsvpc",
    requiresCompatibilities: ["FARGATE"],
    cpu: "512",
    memory: "1024",
    executionRoleArn: taskExecutionRole.arn,
    taskRoleArn: taskRole.arn,
    containerDefinitions: JSON.stringify([{
        name: "app",
        image: "myapp:latest",
        essential: true,
        readonlyRootFilesystem: true,
        linuxParameters: {
            capabilities: {
                drop: ["ALL"],
            },
        },
        secrets: [
            {
                name: "DB_PASSWORD",
                valueFrom: dbSecret.arn,
            },
        ],
        logConfiguration: {
            logDriver: "awslogs",
            options: {
                "awslogs-group": logGroup.name,
                "awslogs-region": aws.getRegionOutput().name,
                "awslogs-stream-prefix": "app",
            },
        },
    }]),
    tags,
});

Deployment

# Configure security email for alerts
pulumi config set securityEmail ops@example.com

# Set database password
pulumi config set --secret dbPassword YourSecurePassword123!

# Set trusted account ID for cross-account access
pulumi config set trustedAccountId 123456789012

# Set external ID
pulumi config set --secret externalId unique-external-id-12345

# Deploy security infrastructure
pulumi up

# Verify security controls
aws cloudtrail describe-trails
aws guardduty list-detectors
aws securityhub describe-hub

Compliance and Audit

Generate Compliance Reports

# Get Config compliance summary
aws configservice describe-compliance-by-config-rule

# Get Security Hub findings
aws securityhub get-findings --filters '{"SeverityLabel": [{"Value": "CRITICAL", "Comparison": "EQUALS"}]}'

# Export CloudTrail logs
aws s3 sync s3://$(pulumi stack output cloudtrailBucketName) ./cloudtrail-logs/

Regular Security Reviews

  1. Review IAM permissions quarterly
  2. Rotate access keys every 90 days
  3. Audit CloudTrail logs monthly
  4. Review Security Hub findings weekly
  5. Update security patches immediately
  6. Conduct penetration testing annually

Incident Response

Security Event Response

# Investigate suspicious activity
aws cloudtrail lookup-events --lookup-attributes AttributeKey=Username,AttributeValue=suspicious-user

# Review GuardDuty findings
aws guardduty list-findings --detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)

# Disable compromised access key
aws iam update-access-key --access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive --user-name compromised-user

# Rotate secrets
aws secretsmanager rotate-secret --secret-id $(pulumi stack output dbSecretArn)

Cost Optimization

  • Use AWS Config Rules selectively
  • Set CloudWatch Logs retention appropriately
  • Disable GuardDuty in non-prod environments
  • Use S3 lifecycle policies for logs
  • Review KMS key usage

Cleanup

# Disable protection before cleanup
# aws rds modify-db-instance --db-instance-identifier secure-app-db --no-deletion-protection

# Destroy infrastructure
pulumi destroy

# Remove logs and backups manually if needed

Resources

  • AWS Security Best Practices
  • CIS AWS Foundations Benchmark
  • AWS Well-Architected Framework - Security Pillar
  • AWS Security Hub