A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources with infrastructure-as-code.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
This guide covers security best practices for AWS infrastructure with Pulumi, including credential management, encryption, IAM policies, network security, secrets management, and compliance.
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",
},
});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;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,
});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;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;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;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;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;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;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: "*",
}],
}),
});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",
});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",
},
},
}],
}),
});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];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,
});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;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;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;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;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;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;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