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
Guide to selecting and using AWS security services with Pulumi.
Control who can do what - Authentication and authorization
Protect data at rest and in transit
Identify security threats
Protect network boundaries
Audit and compliance
Start: What do you need to secure?
┌─ Access control
│ ├─ AWS resource access → IAM (users, roles, policies)
│ ├─ Application users → Cognito (user pools)
│ ├─ Cross-account access → IAM roles + trust policies
│ ├─ Temporary credentials → STS (via IAM roles)
│ └─ Centralized SSO → IAM Identity Center
│
┌─ Data encryption
│ ├─ Encryption keys → KMS
│ ├─ Application secrets → Secrets Manager
│ ├─ Configuration values → Parameter Store
│ ├─ SSL/TLS certificates → ACM
│ └─ Client-side encryption → KMS + client libraries
│
┌─ Threat detection
│ ├─ General threats → GuardDuty (always enable)
│ ├─ Centralized view → Security Hub
│ ├─ Vulnerabilities → Inspector
│ ├─ Sensitive data → Macie
│ └─ Investigation → Detective
│
┌─ Network protection
│ ├─ Web application → WAF (with CloudFront or ALB)
│ ├─ DDoS protection → Shield (Standard free, Advanced paid)
│ ├─ Network filtering → Network Firewall
│ └─ Centralized management → Firewall Manager
│
└─ Audit & compliance
├─ API logging → CloudTrail (always enable)
├─ Resource compliance → Config
├─ Compliance reports → Audit Manager
└─ Security standards → Security Hub standardsIAM best practices:
Key concepts:
Key types:
Common use cases:
Cost considerations:
Secrets Manager vs Parameter Store:
Secrets Manager:
Parameter Store:
ACM features:
Validation methods:
GuardDuty analyzes:
Finding types:
Cost:
Security Hub aggregates:
Security standards:
WAF rules:
Common rule groups:
CloudTrail features:
Best practices:
Service Role with Minimal Permissions
import * as aws from "@pulumi/aws";
// IAM role for Lambda function
const lambdaRole = new aws.iam.Role("lambda-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: "sts:AssumeRole",
}],
}),
tags: { Purpose: "Lambda execution" },
});
// Attach basic Lambda execution policy
const lambdaBasicPolicy = new aws.iam.RolePolicyAttachment("lambda-basic", {
role: lambdaRole.name,
policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
});
// Custom inline policy for DynamoDB access
const dynamoPolicy = new aws.iam.RolePolicy("dynamo-access", {
role: lambdaRole.name,
policy: pulumi.interpolate`{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:Query"
],
"Resource": "${table.arn}"
}]
}`,
});
// KMS decrypt permission
const kmsPolicy = new aws.iam.RolePolicy("kms-access", {
role: lambdaRole.name,
policy: pulumi.interpolate`{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "${kmsKey.arn}"
}]
}`,
});
export const roleArn = lambdaRole.arn;Use when: Creating service roles, Lambda functions, ECS tasks
Secrets Manager + KMS + Rotation
// KMS key for encrypting secrets
const secretsKey = new aws.kms.Key("secrets-key", {
description: "KMS key for Secrets Manager",
enableKeyRotation: true,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "Enable IAM policies",
Effect: "Allow",
Principal: { AWS: pulumi.interpolate`arn:aws:iam::${accountId}:root` },
Action: "kms:*",
Resource: "*",
},
{
Sid: "Allow Secrets Manager",
Effect: "Allow",
Principal: { Service: "secretsmanager.amazonaws.com" },
Action: ["kms:Decrypt", "kms:GenerateDataKey"],
Resource: "*",
},
],
}),
tags: { Purpose: "Secrets encryption" },
});
const secretsKeyAlias = new aws.kms.Alias("secrets-key-alias", {
name: "alias/secrets-key",
targetKeyId: secretsKey.id,
});
// Secret for database credentials
const dbSecret = new aws.secretsmanager.Secret("db-credentials", {
description: "Database credentials with auto-rotation",
kmsKeyId: secretsKey.id,
recoveryWindowInDays: 7,
tags: { Purpose: "RDS credentials" },
});
// Secret version
const dbSecretVersion = new aws.secretsmanager.SecretVersion("db-creds-version", {
secretId: dbSecret.id,
secretString: JSON.stringify({
username: "admin",
password: randomPassword.result,
engine: "postgres",
host: dbInstance.endpoint,
port: 5432,
dbname: "myapp",
}),
});
// Rotation Lambda role
const rotationRole = new aws.iam.Role("rotation-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: { Service: "lambda.amazonaws.com" },
Action: "sts:AssumeRole",
}],
}),
});
// Rotation function (provided by AWS)
const rotationLambda = new aws.lambda.Function("rotation", {
runtime: "python3.11",
handler: "lambda_function.lambda_handler",
role: rotationRole.arn,
code: new pulumi.asset.FileArchive("./rotation-lambda"), // AWS provided
environment: {
variables: {
SECRETS_MANAGER_ENDPOINT: pulumi.interpolate`https://secretsmanager.${region}.amazonaws.com`,
},
},
});
// Enable automatic rotation
const rotation = new aws.secretsmanager.SecretRotation("db-rotation", {
secretId: dbSecret.id,
rotationLambdaArn: rotationLambda.arn,
rotationRules: {
automaticallyAfterDays: 30,
},
});
// Lambda permission for Secrets Manager
const rotationPermission = new aws.lambda.Permission("rotation-permission", {
action: "lambda:InvokeFunction",
function: rotationLambda.name,
principal: "secretsmanager.amazonaws.com",
});
export const secretArn = dbSecret.arn;Use when: Storing database credentials, API keys, secure configuration
Multi-Layer Security (WAF + GuardDuty + Security Hub)
// Enable GuardDuty
const guardduty = new aws.guardduty.Detector("main", {
enable: true,
findingPublishingFrequency: "FIFTEEN_MINUTES",
datasources: {
s3Logs: { enable: true },
kubernetes: { auditLogs: { enable: true } },
},
tags: { Name: "GuardDuty detector" },
});
// Enable Security Hub
const securityHub = new aws.securityhub.Account("main", {
enableDefaultStandards: true,
controlFindingGenerator: "SECURITY_CONTROL",
});
// Enable security standards
const cisStandard = new aws.securityhub.StandardsSubscription("cis", {
standardsArn: pulumi.interpolate`arn:aws:securityhub:${region}::standards/cis-aws-foundations-benchmark/v/1.4.0`,
});
const foundationalStandard = new aws.securityhub.StandardsSubscription("foundational", {
standardsArn: pulumi.interpolate`arn:aws:securityhub:${region}::standards/aws-foundational-security-best-practices/v/1.0.0`,
});
// CloudTrail for audit logging
const trailBucket = new aws.s3.BucketV2("cloudtrail-logs", {
bucket: "cloudtrail-logs-" + accountId,
});
const trailBucketPolicy = new aws.s3.BucketPolicy("cloudtrail-policy", {
bucket: trailBucket.id,
policy: pulumi.all([trailBucket.arn, accountId, region]).apply(([arn, account, reg]) =>
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" },
},
}],
})
),
});
const trail = new aws.cloudtrail.Trail("main-trail", {
s3BucketName: trailBucket.id,
includeGlobalServiceEvents: true,
isMultiRegionTrail: true,
enableLogFileValidation: true,
kmsKeyId: kmsKey.id,
eventSelectors: [{
readWriteType: "All",
includeManagementEvents: true,
}],
tags: { Name: "Organization trail" },
});
// WAF for CloudFront/ALB
const wafAcl = new aws.wafv2.WebAcl("web-acl", {
scope: "REGIONAL", // Use "CLOUDFRONT" for CloudFront
defaultAction: { allow: {} },
rules: [
{
name: "AWSManagedRulesCommonRuleSet",
priority: 1,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesCommonRuleSet",
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "CommonRuleSet",
sampledRequestsEnabled: true,
},
},
{
name: "RateLimitRule",
priority: 2,
action: { block: {} },
statement: {
rateBasedStatement: {
limit: 2000,
aggregateKeyType: "IP",
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "RateLimit",
sampledRequestsEnabled: true,
},
},
],
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "WebACL",
sampledRequestsEnabled: true,
},
});
// Associate WAF with ALB
const wafAssociation = new aws.wafv2.WebAclAssociation("alb-waf", {
resourceArn: alb.arn,
webAclArn: wafAcl.arn,
});
export const guardDutyId = guardduty.id;
export const securityHubArn = securityHub.arn;
export const wafAclArn = wafAcl.arn;Use when: Production environments, compliance requirements, high-security needs
IAM Roles with Trust Relationships
// Role in account A to be assumed by account B
const crossAccountRole = new aws.iam.Role("cross-account-role", {
name: "CrossAccountAccessRole",
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: {
AWS: `arn:aws:iam::${accountBId}:root`, // Account B
},
Action: "sts:AssumeRole",
Condition: {
StringEquals: {
"sts:ExternalId": "unique-external-id-12345",
},
},
}],
}),
tags: { Purpose: "Cross-account S3 access" },
});
// Policy granting S3 read access
const s3ReadPolicy = new aws.iam.RolePolicy("s3-read", {
role: crossAccountRole.name,
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: [
"s3:GetObject",
"s3:ListBucket",
],
Resource: [
bucket.arn,
pulumi.interpolate`${bucket.arn}/*`,
],
}],
}),
});
// In Account B, create user/role with permission to assume the role
const assumeRolePolicy = new aws.iam.Policy("assume-cross-account", {
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: "sts:AssumeRole",
Resource: crossAccountRole.arn,
}],
}),
});
export const roleArn = crossAccountRole.arn;
// Usage example (in Account B):
// aws sts assume-role \
// --role-arn arn:aws:iam::ACCOUNT_A_ID:role/CrossAccountAccessRole \
// --role-session-name CrossAccountSession \
// --external-id unique-external-id-12345Use when: Multi-account architectures, centralized logging, shared resources
KMS Encryption for Multiple Services
// Customer managed KMS key
const dataKey = new aws.kms.Key("data-encryption-key", {
description: "Key for encrypting data at rest",
enableKeyRotation: true, // Automatic annual rotation
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "Enable IAM policies",
Effect: "Allow",
Principal: { AWS: pulumi.interpolate`arn:aws:iam::${accountId}:root` },
Action: "kms:*",
Resource: "*",
},
{
Sid: "Allow services to use key",
Effect: "Allow",
Principal: { Service: [
"s3.amazonaws.com",
"rds.amazonaws.com",
"dynamodb.amazonaws.com",
"logs.amazonaws.com",
] },
Action: [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:CreateGrant",
],
Resource: "*",
},
],
}),
tags: { Purpose: "Data encryption" },
});
const keyAlias = new aws.kms.Alias("data-key-alias", {
name: "alias/data-encryption",
targetKeyId: dataKey.id,
});
// S3 bucket with KMS encryption
const bucket = new aws.s3.BucketV2("encrypted-bucket", {
bucket: "encrypted-data-bucket",
});
const bucketEncryption = new aws.s3.BucketServerSideEncryptionConfigurationV2("encryption", {
bucket: bucket.id,
rules: [{
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms",
kmsMasterKeyId: dataKey.id,
},
bucketKeyEnabled: true, // Reduce KMS costs
}],
});
// EBS volume with KMS encryption
const volume = new aws.ebs.Volume("encrypted-volume", {
availabilityZone: "us-east-1a",
size: 100,
type: "gp3",
encrypted: true,
kmsKeyId: dataKey.id,
tags: { Name: "Encrypted volume" },
});
// RDS with KMS encryption
const db = new aws.rds.Instance("encrypted-db", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
storageEncrypted: true,
kmsKeyId: dataKey.id,
username: "admin",
password: dbPassword.result,
});
// DynamoDB with KMS encryption
const table = new aws.dynamodb.Table("encrypted-table", {
attributes: [{ name: "id", type: "S" }],
hashKey: "id",
billingMode: "PAY_PER_REQUEST",
serverSideEncryption: {
enabled: true,
kmsKeyArn: dataKey.arn,
},
});
export const kmsKeyId = dataKey.id;
export const kmsKeyArn = dataKey.arn;Use when: Compliance requirements, protecting sensitive data, enterprise security
Implement multiple layers of security:
Network Layer
Identity Layer
Data Layer
Monitoring Layer
Install with Tessl CLI
npx tessl i tessl/npm-pulumi--aws