CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pulumi--aws

A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources with infrastructure-as-code.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

security-best-practices.mddocs/guides/

Security Best Practices Guide

This guide covers security best practices for AWS infrastructure with Pulumi, including credential management, encryption, IAM policies, network security, secrets management, and compliance.

Table of Contents

  • Credential Management
  • Encryption at Rest and in Transit
  • IAM Least Privilege
  • Network Security
  • Secrets Management
  • Compliance and Auditing

Credential Management

Never Hardcode Credentials

Always use secure credential management:

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

// BAD - Never do this
const badProvider = new aws.Provider("bad", {
    accessKey: "AKIAIOSFODNN7EXAMPLE",
    secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
});

// GOOD - Use environment variables
// Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
const goodProvider = new aws.Provider("good", {
    region: "us-west-2",
});

// GOOD - Use IAM roles (best for AWS resources)
const roleProvider = new aws.Provider("role", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
        sessionName: "pulumi-deployment",
    },
});

Use IAM Roles for EC2 Instances

Always use instance profiles instead of embedding credentials:

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

// Create IAM role for EC2
const instanceRole = new aws.iam.Role("instance-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: { Service: "ec2.amazonaws.com" },
            Action: "sts:AssumeRole",
        }],
    }),
});

// Attach necessary policies
const s3ReadPolicy = new aws.iam.RolePolicy("s3-read", {
    role: instanceRole.id,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: ["s3:GetObject", "s3:ListBucket"],
            Resource: [
                "arn:aws:s3:::my-bucket",
                "arn:aws:s3:::my-bucket/*",
            ],
        }],
    }),
});

// Create instance profile
const instanceProfile = new aws.iam.InstanceProfile("instance-profile", {
    role: instanceRole.name,
});

// Launch instance with profile
const instance = new aws.ec2.Instance("app-instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    iamInstanceProfile: instanceProfile.name,
});

export const instanceId = instance.id;

Rotate Credentials Regularly

Implement automatic credential rotation:

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

// Use temporary credentials with short duration
const provider = new aws.Provider("short-lived", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
        sessionName: "pulumi-deployment",
        duration: "900s", // 15 minutes
    },
});

// Lambda function for credential rotation
const rotationFunction = new aws.lambda.Function("credential-rotation", {
    runtime: "python3.9",
    handler: "index.handler",
    role: "arn:aws:iam::123456789012:role/LambdaRole",
    code: new pulumi.asset.AssetArchive({
        ".": new pulumi.asset.FileArchive("./rotation-function"),
    }),
    environment: {
        variables: {
            SECRET_ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:db-password",
        },
    },
});

// Schedule rotation weekly
const rotationSchedule = new aws.cloudwatch.EventRule("rotation-schedule", {
    scheduleExpression: "rate(7 days)",
});

const rotationTarget = new aws.cloudwatch.EventTarget("rotation-target", {
    rule: rotationSchedule.name,
    arn: rotationFunction.arn,
});

const rotationPermission = new aws.lambda.Permission("rotation-permission", {
    action: "lambda:InvokeFunction",
    function: rotationFunction.name,
    principal: "events.amazonaws.com",
    sourceArn: rotationSchedule.arn,
});

Use Web Identity Federation

Implement OIDC for CI/CD pipelines:

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

// Create OIDC provider for GitHub Actions
const githubOidc = new aws.iam.OpenIdConnectProvider("github-oidc", {
    url: "https://token.actions.githubusercontent.com",
    clientIdLists: ["sts.amazonaws.com"],
    thumbprintLists: ["6938fd4d98bab03faadb97b34396831e3780aea1"],
});

// Create role that trusts GitHub OIDC
const githubRole = new aws.iam.Role("github-actions-role", {
    assumeRolePolicy: pulumi.interpolate`{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {
                "Federated": "${githubOidc.arn}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
                },
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }]
    }`,
});

// Grant minimal permissions
const deployPolicy = new aws.iam.RolePolicy("deploy-policy", {
    role: githubRole.id,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: [
                "s3:PutObject",
                "s3:GetObject",
                "cloudfront:CreateInvalidation",
            ],
            Resource: [
                "arn:aws:s3:::deployment-bucket/*",
                "arn:aws:cloudfront::123456789012:distribution/*",
            ],
        }],
    }),
});

export const githubRoleArn = githubRole.arn;

Encryption at Rest and in Transit

S3 Encryption

Enable encryption for all S3 buckets:

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

// Create KMS key for S3 encryption
const s3Key = new aws.kms.Key("s3-key", {
    description: "KMS key for S3 bucket encryption",
    deletionWindowInDays: 30,
    enableKeyRotation: true,
});

const s3KeyAlias = new aws.kms.Alias("s3-key-alias", {
    name: "alias/s3-encryption",
    targetKeyId: s3Key.keyId,
});

// Create encrypted bucket
const bucket = new aws.s3.Bucket("secure-bucket", {
    acl: "private",
    serverSideEncryptionConfiguration: {
        rule: {
            applyServerSideEncryptionByDefault: {
                sseAlgorithm: "aws:kms",
                kmsMasterKeyId: s3Key.arn,
            },
            bucketKeyEnabled: true,
        },
    },
    versioning: {
        enabled: true,
    },
});

// Enforce encryption in bucket policy
const bucketPolicy = new aws.s3.BucketPolicy("secure-bucket-policy", {
    bucket: bucket.id,
    policy: bucket.arn.apply(arn => JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Sid: "DenyUnencryptedObjectUploads",
            Effect: "Deny",
            Principal: "*",
            Action: "s3:PutObject",
            Resource: `${arn}/*`,
            Condition: {
                StringNotEquals: {
                    "s3:x-amz-server-side-encryption": "aws:kms",
                },
            },
        }],
    })),
});

export const bucketName = bucket.bucket;
export const encryptionKeyArn = s3Key.arn;

RDS Encryption

Enable encryption for databases:

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

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

// Create encrypted RDS instance
const database = new aws.rds.Instance("secure-db", {
    engine: "postgres",
    engineVersion: "14.7",
    instanceClass: "db.t3.medium",
    allocatedStorage: 100,
    storageEncrypted: true,
    kmsKeyId: rdsKey.arn,
    username: "admin",
    password: pulumi.secret("secure-password-from-secrets-manager"),
    backupRetentionPeriod: 7,
    deletionProtection: true,
    enabledCloudwatchLogsExports: ["postgresql", "upgrade"],
    tags: {
        Encrypted: "true",
        Compliance: "required",
    },
});

// Enable SSL/TLS for connections
const dbParameterGroup = new aws.rds.ParameterGroup("ssl-parameter-group", {
    family: "postgres14",
    parameters: [{
        name: "rds.force_ssl",
        value: "1",
    }],
});

const secureDb = new aws.rds.Instance("ssl-enforced-db", {
    engine: "postgres",
    instanceClass: "db.t3.medium",
    allocatedStorage: 100,
    storageEncrypted: true,
    kmsKeyId: rdsKey.arn,
    parameterGroupName: dbParameterGroup.name,
    username: "admin",
    password: pulumi.secret("secure-password"),
});

export const dbEndpoint = database.endpoint;

EBS Encryption

Enable default EBS encryption:

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

// Enable EBS encryption by default
const ebsEncryption = new aws.ebs.DefaultKmsKey("default-ebs-key", {
    keyArn: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
});

const ebsEncryptionEnabled = new aws.ebs.EncryptionByDefault("ebs-encryption", {
    enabled: true,
});

// Create encrypted volume
const volume = new aws.ebs.Volume("encrypted-volume", {
    availabilityZone: "us-west-2a",
    size: 100,
    encrypted: true,
    kmsKeyId: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
    tags: {
        Encrypted: "true",
    },
});

// Launch instance with encrypted root volume
const instance = new aws.ec2.Instance("encrypted-instance", {
    instanceType: "t3.micro",
    ami: "ami-0c55b159cbfafe1f0",
    rootBlockDevice: {
        volumeSize: 20,
        encrypted: true,
        kmsKeyId: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
    },
});

export const volumeId = volume.id;

Application Load Balancer SSL/TLS

Configure secure load balancer listeners:

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

// Request ACM certificate
const certificate = new aws.acm.Certificate("cert", {
    domainName: "example.com",
    validationMethod: "DNS",
    subjectAlternativeNames: ["*.example.com"],
});

// Create ALB
const alb = new aws.lb.LoadBalancer("app-alb", {
    loadBalancerType: "application",
    subnets: ["subnet-1", "subnet-2"],
    securityGroups: ["sg-12345"],
});

// HTTPS listener with strong security policy
const httpsListener = new aws.lb.Listener("https-listener", {
    loadBalancerArn: alb.arn,
    port: 443,
    protocol: "HTTPS",
    sslPolicy: "ELBSecurityPolicy-TLS-1-2-2017-01",
    certificateArn: certificate.arn,
    defaultActions: [{
        type: "forward",
        targetGroupArn: "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/app/1234567890",
    }],
});

// HTTP listener redirects to HTTPS
const httpListener = new aws.lb.Listener("http-listener", {
    loadBalancerArn: alb.arn,
    port: 80,
    protocol: "HTTP",
    defaultActions: [{
        type: "redirect",
        redirect: {
            port: "443",
            protocol: "HTTPS",
            statusCode: "HTTP_301",
        },
    }],
});

export const albDnsName = alb.dnsName;

IAM Least Privilege

Minimal IAM Policies

Grant only necessary permissions:

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

// BAD - Overly permissive
const badPolicy = new aws.iam.Policy("bad-policy", {
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: "*",
            Resource: "*",
        }],
    }),
});

// GOOD - Specific permissions
const goodPolicy = new aws.iam.Policy("good-policy", {
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: [
                "s3:GetObject",
                "s3:PutObject",
            ],
            Resource: "arn:aws:s3:::specific-bucket/*",
        }, {
            Effect: "Allow",
            Action: "s3:ListBucket",
            Resource: "arn:aws:s3:::specific-bucket",
        }],
    }),
});

// Create role with minimal permissions
const appRole = new aws.iam.Role("app-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: { Service: "lambda.amazonaws.com" },
            Action: "sts:AssumeRole",
        }],
    }),
});

// Attach specific policies
const policyAttachment = new aws.iam.RolePolicyAttachment("attachment", {
    role: appRole.name,
    policyArn: goodPolicy.arn,
});

export const roleArn = appRole.arn;

Resource-Based Policies

Use resource-based policies for fine-grained access:

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

// S3 bucket with restrictive policy
const bucket = new aws.s3.Bucket("restricted-bucket", {
    acl: "private",
});

const bucketPolicy = new aws.s3.BucketPolicy("bucket-policy", {
    bucket: bucket.id,
    policy: pulumi.all([bucket.arn]).apply(([arn]) => JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Sid: "AllowSpecificRole",
            Effect: "Allow",
            Principal: {
                AWS: "arn:aws:iam::123456789012:role/SpecificRole",
            },
            Action: ["s3:GetObject", "s3:PutObject"],
            Resource: `${arn}/*`,
        }, {
            Sid: "DenyInsecureTransport",
            Effect: "Deny",
            Principal: "*",
            Action: "s3:*",
            Resource: [arn, `${arn}/*`],
            Condition: {
                Bool: {
                    "aws:SecureTransport": "false",
                },
            },
        }],
    })),
});

// KMS key with restricted access
const key = new aws.kms.Key("restricted-key", {
    description: "Restricted KMS key",
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Sid: "Enable IAM User Permissions",
            Effect: "Allow",
            Principal: {
                AWS: "arn:aws:iam::123456789012:root",
            },
            Action: "kms:*",
            Resource: "*",
        }, {
            Sid: "Allow specific role to use key",
            Effect: "Allow",
            Principal: {
                AWS: "arn:aws:iam::123456789012:role/AppRole",
            },
            Action: [
                "kms:Decrypt",
                "kms:DescribeKey",
            ],
            Resource: "*",
        }],
    }),
});

Service Control Policies (SCPs)

Implement organization-level controls:

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

// Deny policy for organization
const denyPublicS3 = new aws.organizations.Policy("deny-public-s3", {
    content: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Deny",
            Action: [
                "s3:PutBucketPublicAccessBlock",
                "s3:PutAccountPublicAccessBlock",
            ],
            Resource: "*",
            Condition: {
                StringNotEquals: {
                    "s3:PublicAccessBlockConfiguration/BlockPublicAcls": "true",
                    "s3:PublicAccessBlockConfiguration/BlockPublicPolicy": "true",
                    "s3:PublicAccessBlockConfiguration/IgnorePublicAcls": "true",
                    "s3:PublicAccessBlockConfiguration/RestrictPublicBuckets": "true",
                },
            },
        }],
    }),
    description: "Deny making S3 buckets public",
});

// Require encryption
const requireEncryption = new aws.organizations.Policy("require-encryption", {
    content: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Deny",
            Action: [
                "s3:PutObject",
            ],
            Resource: "*",
            Condition: {
                StringNotEquals: {
                    "s3:x-amz-server-side-encryption": ["AES256", "aws:kms"],
                },
            },
        }],
    }),
    description: "Require S3 encryption",
});

IAM Conditions

Use conditions for context-aware access:

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

// Policy with IP restriction
const ipRestrictedPolicy = new aws.iam.Policy("ip-restricted", {
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: "s3:*",
            Resource: "*",
            Condition: {
                IpAddress: {
                    "aws:SourceIp": ["203.0.113.0/24", "198.51.100.0/24"],
                },
            },
        }],
    }),
});

// Policy with MFA requirement
const mfaPolicy = new aws.iam.Policy("mfa-required", {
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: "*",
            Resource: "*",
            Condition: {
                Bool: {
                    "aws:MultiFactorAuthPresent": "true",
                },
            },
        }],
    }),
});

// Policy with time-based access
const timeBasedPolicy = new aws.iam.Policy("time-based", {
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: "ec2:*",
            Resource: "*",
            Condition: {
                DateGreaterThan: {
                    "aws:CurrentTime": "2024-01-01T00:00:00Z",
                },
                DateLessThan: {
                    "aws:CurrentTime": "2024-12-31T23:59:59Z",
                },
            },
        }],
    }),
});

Network Security

VPC with Security Groups

Implement network segmentation:

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

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

// Create private subnets
const privateSubnet1 = new aws.ec2.Subnet("private-subnet-1", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
    availabilityZone: "us-west-2a",
    mapPublicIpOnLaunch: false,
    tags: { Name: "private-subnet-1", Type: "private" },
});

const privateSubnet2 = new aws.ec2.Subnet("private-subnet-2", {
    vpcId: vpc.id,
    cidrBlock: "10.0.2.0/24",
    availabilityZone: "us-west-2b",
    mapPublicIpOnLaunch: false,
    tags: { Name: "private-subnet-2", Type: "private" },
});

// Create public subnets
const publicSubnet1 = new aws.ec2.Subnet("public-subnet-1", {
    vpcId: vpc.id,
    cidrBlock: "10.0.101.0/24",
    availabilityZone: "us-west-2a",
    mapPublicIpOnLaunch: true,
    tags: { Name: "public-subnet-1", Type: "public" },
});

// Application security group (restrictive)
const appSecurityGroup = new aws.ec2.SecurityGroup("app-sg", {
    vpcId: vpc.id,
    description: "Application security group",
    ingress: [{
        protocol: "tcp",
        fromPort: 8080,
        toPort: 8080,
        securityGroups: ["sg-alb-id"], // Only from ALB
    }],
    egress: [{
        protocol: "tcp",
        fromPort: 5432,
        toPort: 5432,
        securityGroups: ["sg-db-id"], // Only to database
    }],
    tags: { Name: "app-sg" },
});

// Database security group
const dbSecurityGroup = new aws.ec2.SecurityGroup("db-sg", {
    vpcId: vpc.id,
    description: "Database security group",
    ingress: [{
        protocol: "tcp",
        fromPort: 5432,
        toPort: 5432,
        securityGroups: [appSecurityGroup.id], // Only from app
    }],
    egress: [], // No outbound
    tags: { Name: "db-sg" },
});

// ALB security group
const albSecurityGroup = new aws.ec2.SecurityGroup("alb-sg", {
    vpcId: vpc.id,
    description: "ALB security group",
    ingress: [
        {
            protocol: "tcp",
            fromPort: 443,
            toPort: 443,
            cidrBlocks: ["0.0.0.0/0"],
        },
        {
            protocol: "tcp",
            fromPort: 80,
            toPort: 80,
            cidrBlocks: ["0.0.0.0/0"],
        },
    ],
    egress: [{
        protocol: "tcp",
        fromPort: 8080,
        toPort: 8080,
        securityGroups: [appSecurityGroup.id],
    }],
    tags: { Name: "alb-sg" },
});

export const vpcId = vpc.id;
export const privateSubnetIds = [privateSubnet1.id, privateSubnet2.id];

Network ACLs

Implement network-level controls:

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

const vpc = new aws.ec2.Vpc("vpc", {
    cidrBlock: "10.0.0.0/16",
});

const subnet = new aws.ec2.Subnet("private-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
});

// Restrictive NACL
const privateNacl = new aws.ec2.NetworkAcl("private-nacl", {
    vpcId: vpc.id,
    ingress: [
        // Allow HTTPS from internet
        {
            protocol: "tcp",
            ruleNo: 100,
            action: "allow",
            cidrBlock: "0.0.0.0/0",
            fromPort: 443,
            toPort: 443,
        },
        // Allow responses to outbound requests
        {
            protocol: "tcp",
            ruleNo: 110,
            action: "allow",
            cidrBlock: "0.0.0.0/0",
            fromPort: 1024,
            toPort: 65535,
        },
    ],
    egress: [
        // Allow HTTPS to internet
        {
            protocol: "tcp",
            ruleNo: 100,
            action: "allow",
            cidrBlock: "0.0.0.0/0",
            fromPort: 443,
            toPort: 443,
        },
        // Allow responses
        {
            protocol: "tcp",
            ruleNo: 110,
            action: "allow",
            cidrBlock: "0.0.0.0/0",
            fromPort: 1024,
            toPort: 65535,
        },
    ],
    tags: { Name: "private-nacl" },
});

const naclAssociation = new aws.ec2.NetworkAclAssociation("nacl-assoc", {
    networkAclId: privateNacl.id,
    subnetId: subnet.id,
});

VPC Flow Logs

Enable flow logs for monitoring:

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

const vpc = new aws.ec2.Vpc("monitored-vpc", {
    cidrBlock: "10.0.0.0/16",
});

// Create CloudWatch log group
const flowLogGroup = new aws.cloudwatch.LogGroup("vpc-flow-logs", {
    retentionInDays: 30,
});

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

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

// Enable flow logs
const flowLog = new aws.ec2.FlowLog("vpc-flow-log", {
    vpcId: vpc.id,
    trafficType: "ALL",
    logDestinationType: "cloud-watch-logs",
    logDestination: flowLogGroup.arn,
    iamRoleArn: flowLogRole.arn,
    tags: { Name: "vpc-flow-log" },
});

export const flowLogId = flowLog.id;

AWS WAF

Protect web applications:

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

// Create WAF Web ACL
const webAcl = new aws.wafv2.WebAcl("web-acl", {
    scope: "REGIONAL",
    defaultAction: { allow: {} },
    rules: [
        // Rate limiting
        {
            name: "rate-limit",
            priority: 1,
            statement: {
                rateBasedStatement: {
                    limit: 2000,
                    aggregateKeyType: "IP",
                },
            },
            action: { block: {} },
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudwatchMetricsEnabled: true,
                metricName: "rate-limit",
            },
        },
        // SQL injection protection
        {
            name: "sql-injection",
            priority: 2,
            statement: {
                sqliMatchStatement: {
                    fieldToMatch: { body: {} },
                    textTransformations: [{
                        priority: 0,
                        type: "URL_DECODE",
                    }],
                },
            },
            action: { block: {} },
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudwatchMetricsEnabled: true,
                metricName: "sql-injection",
            },
        },
        // XSS protection
        {
            name: "xss-protection",
            priority: 3,
            statement: {
                xssMatchStatement: {
                    fieldToMatch: { body: {} },
                    textTransformations: [{
                        priority: 0,
                        type: "URL_DECODE",
                    }],
                },
            },
            action: { block: {} },
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudwatchMetricsEnabled: true,
                metricName: "xss-protection",
            },
        },
        // Geo blocking
        {
            name: "geo-block",
            priority: 4,
            statement: {
                geoMatchStatement: {
                    countryCodes: ["CN", "RU"], // Block specific countries
                },
            },
            action: { block: {} },
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudwatchMetricsEnabled: true,
                metricName: "geo-block",
            },
        },
    ],
    visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudwatchMetricsEnabled: true,
        metricName: "web-acl",
    },
});

// Associate with ALB
const wafAssociation = new aws.wafv2.WebAclAssociation("waf-alb-assoc", {
    resourceArn: "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-alb/1234567890",
    webAclArn: webAcl.arn,
});

export const webAclArn = webAcl.arn;

Secrets Management

AWS Secrets Manager

Store and rotate secrets securely:

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

// Create secret
const dbPassword = new aws.secretsmanager.Secret("db-password", {
    description: "Database password",
    recoveryWindowInDays: 30,
});

// Store secret value
const dbPasswordVersion = new aws.secretsmanager.SecretVersion("db-password-version", {
    secretId: dbPassword.id,
    secretString: pulumi.secret(JSON.stringify({
        username: "admin",
        password: "super-secure-password-12345",
    })),
});

// Automatic rotation
const rotationLambda = new aws.lambda.Function("rotation-lambda", {
    runtime: "python3.9",
    handler: "lambda_function.lambda_handler",
    role: "arn:aws:iam::123456789012:role/LambdaRotationRole",
    code: new pulumi.asset.AssetArchive({
        ".": new pulumi.asset.FileArchive("./rotation-lambda"),
    }),
});

const secretRotation = new aws.secretsmanager.SecretRotation("db-rotation", {
    secretId: dbPassword.id,
    rotationLambdaArn: rotationLambda.arn,
    rotationRules: {
        automaticallyAfterDays: 30,
    },
});

// Use secret in RDS
const database = new aws.rds.Instance("secure-db", {
    engine: "postgres",
    instanceClass: "db.t3.medium",
    allocatedStorage: 100,
    username: dbPasswordVersion.secretString.apply(s => JSON.parse(s).username),
    password: dbPasswordVersion.secretString.apply(s => JSON.parse(s).password),
});

export const secretArn = dbPassword.arn;

Parameter Store

Use Systems Manager Parameter Store:

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

// Store encrypted parameter
const apiKey = new aws.ssm.Parameter("api-key", {
    name: "/app/production/api-key",
    type: "SecureString",
    value: pulumi.secret("my-secret-api-key"),
    description: "API key for external service",
    keyId: "alias/aws/ssm", // Use custom KMS key
    tags: {
        Environment: "production",
        Sensitive: "true",
    },
});

// Use in Lambda
const lambda = new aws.lambda.Function("app-function", {
    runtime: "python3.9",
    handler: "index.handler",
    role: "arn:aws:iam::123456789012:role/LambdaRole",
    code: new pulumi.asset.AssetArchive({
        ".": new pulumi.asset.FileArchive("./function"),
    }),
    environment: {
        variables: {
            API_KEY_PARAM: apiKey.name,
        },
    },
});

// IAM policy to read parameter
const parameterPolicy = new aws.iam.Policy("parameter-read", {
    policy: apiKey.name.apply(name => JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: ["ssm:GetParameter", "ssm:GetParameters"],
            Resource: `arn:aws:ssm:us-west-2:123456789012:parameter${name}`,
        }, {
            Effect: "Allow",
            Action: "kms:Decrypt",
            Resource: "arn:aws:kms:us-west-2:123456789012:key/*",
        }],
    })),
});

export const parameterName = apiKey.name;

Compliance and Auditing

AWS Config

Enable configuration tracking:

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

// S3 bucket for config logs
const configBucket = new aws.s3.Bucket("config-bucket", {
    acl: "private",
    versioning: { enabled: true },
    serverSideEncryptionConfiguration: {
        rule: {
            applyServerSideEncryptionByDefault: {
                sseAlgorithm: "AES256",
            },
        },
    },
});

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

const configRolePolicy = new aws.iam.RolePolicyAttachment("config-policy", {
    role: configRole.name,
    policyArn: "arn:aws:iam::aws:policy/service-role/ConfigRole",
});

// Configuration recorder
const recorder = new aws.cfg.Recorder("config-recorder", {
    roleArn: configRole.arn,
    recordingGroup: {
        allSupported: true,
        includeGlobalResourceTypes: true,
    },
});

// Delivery channel
const deliveryChannel = new aws.cfg.DeliveryChannel("config-channel", {
    s3BucketName: configBucket.bucket,
    dependsOn: [recorder],
});

// Config rules
const s3BucketEncryptionRule = new aws.cfg.Rule("s3-encryption-rule", {
    source: {
        owner: "AWS",
        sourceIdentifier: "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED",
    },
    dependsOn: [recorder, deliveryChannel],
});

const iamPasswordPolicyRule = new aws.cfg.Rule("iam-password-policy", {
    source: {
        owner: "AWS",
        sourceIdentifier: "IAM_PASSWORD_POLICY",
    },
    inputParameters: JSON.stringify({
        RequireUppercaseCharacters: "true",
        RequireLowercaseCharacters: "true",
        RequireNumbers: "true",
        MinimumPasswordLength: "14",
    }),
    dependsOn: [recorder, deliveryChannel],
});

export const configBucketName = configBucket.bucket;

CloudTrail

Enable comprehensive audit logging:

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

// S3 bucket for CloudTrail logs
const trailBucket = new aws.s3.Bucket("cloudtrail-bucket", {
    acl: "private",
    versioning: { enabled: true },
    lifecycleRules: [{
        enabled: true,
        transitions: [{
            days: 90,
            storageClass: "GLACIER",
        }],
    }],
});

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

// CloudTrail
const trail = new aws.cloudtrail.Trail("organization-trail", {
    s3BucketName: trailBucket.bucket,
    includeGlobalServiceEvents: true,
    isMultiRegionTrail: true,
    enableLogFileValidation: true,
    eventSelectors: [{
        readWriteType: "All",
        includeManagementEvents: true,
        dataResources: [{
            type: "AWS::S3::Object",
            values: ["arn:aws:s3:::*/"],
        }],
    }],
    insightSelectors: [{
        insightType: "ApiCallRateInsight",
    }],
});

export const trailArn = trail.arn;

GuardDuty

Enable threat detection:

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

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

// SNS topic for findings
const findingsTopic = new aws.sns.Topic("guardduty-findings");

// CloudWatch Event Rule for findings
const findingsRule = new aws.cloudwatch.EventRule("guardduty-findings-rule", {
    description: "GuardDuty findings",
    eventPattern: JSON.stringify({
        source: ["aws.guardduty"],
        detailType: ["GuardDuty Finding"],
    }),
});

const findingsTarget = new aws.cloudwatch.EventTarget("findings-target", {
    rule: findingsRule.name,
    arn: findingsTopic.arn,
});

export const detectorId = detector.id;
export const findingsTopicArn = findingsTopic.arn;

Install with Tessl CLI

npx tessl i tessl/npm-pulumi--aws@7.16.0

docs

index.md

quickstart.md

README.md

tile.json