tessl install github:giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-securitygithub.com/giuseppe-trisciuoglio/developer-kit
AWS CloudFormation patterns for infrastructure security, secrets management, encryption, and secure data handling. Use when creating secure CloudFormation templates with AWS Secrets Manager, KMS encryption, secure parameters, IAM policies, VPC security groups, TLS/SSL certificates, and encrypted traffic configurations. Covers template structure, parameter best practices, cross-stack references, and defense-in-depth strategies.
Review Score
80%
Validation Score
11/16
Implementation Score
65%
Activation Score
100%
Create secure AWS infrastructure using CloudFormation templates with security best practices. This skill covers encryption with AWS KMS, secrets management with Secrets Manager, secure parameters, IAM least privilege, security groups, TLS/SSL certificates, and defense-in-depth strategies.
Use this skill when:
AWSTemplateFormatVersion: 2010-09-09
Description: Secure infrastructure template with encryption and secrets management
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Encryption Settings
Parameters:
- EncryptionKeyArn
- SecretsKmsKeyId
- Label:
default: Security Configuration
Parameters:
- SecurityLevel
- EnableVPCPeering
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
EncryptionKeyArn:
Type: AWS::KMS::Key::Arn
Description: KMS key ARN for encryption
SecretsKmsKeyId:
Type: String
Description: KMS key ID for secrets encryption
Mappings:
SecurityConfig:
dev:
EnableDetailedMonitoring: false
RequireMultiAZ: false
staging:
EnableDetailedMonitoring: true
RequireMultiAZ: false
production:
EnableDetailedMonitoring: true
RequireMultiAZ: true
Conditions:
IsProduction: !Equals [!Ref Environment, production]
EnableEnhancedMonitoring: !Equals [!Ref Environment, production]
Resources:
# Resources will be defined here
Outputs:
SecurityConfigurationOutput:
Description: Security configuration applied
Value: !Ref EnvironmentResources:
# Master KMS Key for application
ApplicationKmsKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for application encryption"
KeyPolicy:
Version: "2012-10-17"
Id: "application-key-policy"
Statement:
# Allow key management to administrators
- Sid: "EnableIAMPolicies"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action:
- kms:Create*
- kms:Describe*
- kms:Enable*
- kms:List*
- kms:Put*
- kms:Update*
- kms:Revoke*
- kms:Disable*
- kms:Get*
- kms:Delete*
- kms:TagResource
- kms:UntagResource
Resource: "*"
Condition:
StringEquals:
aws:PrincipalOrgID: !Ref OrganizationId
# Allow encryption/decryption for application roles
- Sid: "AllowCryptographicOperations"
Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${AWS::AccountId}:role/LambdaExecutionRole"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/ECSTaskRole"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
- kms:ReEncrypt*
Resource: "*"
# Allow key usage for specific services
- Sid: "AllowKeyUsageForSpecificServices"
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- ecs.amazonaws.com
- rds.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
KeyUsage: ENCRYPT_DECRYPT
EnableKeyRotation: true
PendingWindowInDays: 30
# Alias for the key
ApplicationKmsKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/application-${Environment}"
TargetKeyId: !Ref ApplicationKmsKey
# KMS Key for S3 bucket encryption
S3KmsKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for S3 bucket encryption"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowS3Encryption"
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
# KMS Key for RDS encryption
RdsKmsKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for RDS database encryption"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowRDSEncryption"
Effect: Allow
Principal:
Service: rds.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"Resources:
EncryptedS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "secure-bucket-${AWS::AccountId}-${AWS::Region}"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !Ref S3KmsKey
BucketKeyEnabled: true
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: ArchiveOldVersions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 90
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Encrypted
Value: "true"Resources:
# Database credentials secret
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/database/credentials"
Description: "Database credentials with automatic rotation"
SecretString: !Sub |
{
"username": "${DBUsername}",
"password": "${DBPassword}",
"host": "${DBHost}",
"port": "${DBPort}",
"dbname": "${DBName}",
"engine": "postgresql"
}
KmsKeyId: !Ref SecretsKmsKeyId
# Enable automatic rotation
RotationRules:
AutomaticallyAfterDays: 30
# Rotation Lambda configuration
RotationLambdaARN: !GetAtt SecretRotationFunction.Arn
Tags:
- Key: Environment
Value: !Ref Environment
- Key: ManagedBy
Value: CloudFormation
- Key: RotationEnabled
Value: "true"
# Secret with resource-based policy
ApiSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/api/keys"
Description: "API keys for external service authentication"
SecretString: !Sub |
{
"api_key": "${ExternalApiKey}",
"api_secret": "${ExternalApiSecret}",
"endpoint": "https://api.example.com"
}
KmsKeyId: !Ref SecretsKmsKeyId
# Resource-based policy for access control
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowLambdaAccess"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/LambdaExecutionRole"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Environment: !Ref Environment
- Sid: "DenyUnencryptedAccess"
Effect: Deny
Principal: "*"
Action:
- secretsmanager:GetSecretValue
Resource: "*"
Condition:
StringEquals:
kms:ViaService: !Sub "secretsmanager.${AWS::Region}.amazonaws.com"
StringNotEquals:
kms:EncryptContext: !Sub "secretsmanager:${AWS::StackName}"
# Secret with cross-account access
SharedSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/shared/credentials"
Description: "Secret shared across accounts"
SecretString: !Sub |
{
"shared_key": "${SharedKey}",
"shared_value": "${SharedValue}"
}
KmsKeyId: !Ref SecretsKmsKeyId
# Cross-account access policy
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowCrossAccountRead"
Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${ProductionAccountId}:role/SharedSecretReader"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: "*"Parameters:
# SSM Parameter for database connection
DBCredentialsParam:
Type: AWS::SSM::Parameter::Value<SecureString>
NoEcho: true
Description: Database credentials from SSM Parameter Store
Value: !Sub "/${Environment}/database/credentials"
# SSM Parameter with specific path
ApiKeyParam:
Type: AWS::SSM::Parameter::Value<SecureString>
NoEcho: true
Description: API key for external service
Value: !Sub "/${Environment}/external-api/key"
Resources:
# Lambda function using SSM parameters
SecureLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-secure-function"
Runtime: python3.11
Handler: handler.handler
Code:
S3Bucket: !Ref CodeBucket
S3Key: lambda/secure-function.zip
Role: !GetAtt LambdaExecutionRole.Arn
Environment:
Variables:
DB_CREDENTIALS_SSM_PATH: !Sub "/${Environment}/database/credentials"
API_KEY_SSM_PATH: !Sub "/${Environment}/external-api/key"Resources:
# Lambda Execution Role with minimal permissions
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-lambda-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
lambda:SourceFunctionArn: !Ref SecureLambdaFunctionArn
# Permissions boundary for enhanced security
PermissionsBoundary: !Ref PermissionsBoundaryPolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
# Policy for specific secrets access
- PolicyName: SecretsAccessPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: !Ref DatabaseSecretArn
Condition:
StringEquals:
secretsmanager:SecretTarget: !Sub "${DatabaseSecretArn}:${DatabaseSecret}"
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref ApiSecretArn
# Policy for specific S3 access
- PolicyName: S3AccessPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource:
- !Sub "${DataBucket.Arn}/*"
- !Sub "${DataBucket.Arn}"
Condition:
StringEquals:
s3:ResourceAccount: !Ref AWS::AccountId
- Effect: Deny
Action:
- s3:DeleteObject*
Resource:
- !Sub "${DataBucket.Arn}/*"
Condition:
Bool:
aws:MultiFactorAuthPresent: true
# Policy for CloudWatch Logs
- PolicyName: CloudWatchLogsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "${LogGroup.Arn}:*"
Tags:
- Key: Environment
Value: !Ref Environment
- Key: LeastPrivilege
Value: "true"
# Permissions Boundary Policy
PermissionsBoundaryPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "Permissions boundary for Lambda execution role"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "DenyAccessToAllExceptSpecified"
Effect: Deny
Action:
- "*"
Resource: "*"
Condition:
StringNotEqualsIfExists:
aws:RequestedRegion:
- !Ref AWS::Region
ArnNotEqualsIfExists:
aws:SourceArn: !Ref AllowedResourceArnsResources:
# Role for cross-account access
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-cross-account-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${ProductionAccountId}:root"
- !Sub "arn:aws:iam::${StagingAccountId}:role/CrossAccountAccessRole"
Action: sts:AssumeRole
Condition:
StringEquals:
aws:PrincipalAccount: !Ref ProductionAccountId
Bool:
aws:MultiFactorAuthPresent: true
Policies:
- PolicyName: CrossAccountReadOnlyPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject*
- s3:List*
Resource:
- !Sub "${SharedBucket.Arn}"
- !Sub "${SharedBucket.Arn}/*"
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
Resource:
- !Sub "${SharedTable.Arn}"
- !Sub "${SharedTable.Arn}/index/*"
- Effect: Deny
Action:
- s3:DeleteObject*
- s3:PutObject*
Resource:
- !Sub "${SharedBucket.Arn}/*"Resources:
# Security Group for application
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-app-sg"
GroupDescription: "Security group for application tier"
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-app-sg"
- Key: Environment
Value: !Ref Environment
# Inbound rules - only necessary traffic
SecurityGroupIngress:
# HTTP from ALB
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: "HTTP from ALB"
# HTTPS from ALB
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: "HTTPS from ALB"
# SSH from bastion only (if needed)
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "SSH access from bastion"
# Custom TCP for internal services
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref InternalSecurityGroup
Description: "Internal service communication"
# Outbound rules - limited
SecurityGroupEgress:
# HTTPS outbound for API calls
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS outbound"
# DNS outbound
- IpProtocol: udp
FromPort: 53
ToPort: 53
CidrIp: 10.0.0.0/16
Description: "DNS outbound for VPC"
# Security Group for database
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-db-sg"
GroupDescription: "Security group for database tier"
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-db-sg"
# Inbound - only from application security group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "PostgreSQL from application tier"
# Outbound - minimum required
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS for updates and patches"
# Security Group for ALB
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-alb-sg"
GroupDescription: "Security group for ALB"
VpcId: !Ref VPCId
SecurityGroupIngress:
# HTTP from internet
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: "HTTP from internet"
# HTTPS from internet
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS from internet"
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "Forward to application"
# VPC Endpoint for Secrets Manager
SecretsManagerVPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: !Sub "com.amazonaws.${AWS::Region}.secretsmanager"
VpcEndpointType: Interface
Subnets:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
SecurityGroups:
- !Ref ApplicationSecurityGroup
PrivateDnsEnabled: trueResources:
# SSL Certificate for domain
SSLCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
SubjectAlternativeNames:
- !Sub "*.${DomainName}"
- !Ref AdditionalDomainName
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Ref DomainName
Route53HostedZoneId: !Ref HostedZoneId
Options:
CertificateTransparencyLoggingPreference: ENABLED
Tags:
- Key: Environment
Value: !Ref Environment
- Key: ManagedBy
Value: CloudFormation
# Certificate for regional API Gateway
RegionalCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Sub "${Environment}.${DomainName}"
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Sub "${Environment}.${DomainName}"
Route53HostedZoneId: !Ref HostedZoneId
# API Gateway with TLS 1.2+
SecureApiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub "${AWS::StackName}-secure-api"
Description: "Secure REST API with TLS enforcement"
EndpointConfiguration:
Types:
- REGIONAL
MinimumCompressionSize: 1024
# Policy to enforce HTTPS
Policy:
Version: "2012-10-17"
Statement:
- Effect: Deny
Principal: "*"
Action: execute-api:Invoke
Resource: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${SecureApiGateway}/*"
Condition:
Bool:
aws:SecureTransport: "false"
# Custom Domain per API Gateway
ApiGatewayDomain:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: !Sub "api.${DomainName}"
RegionalCertificateArn: !Ref RegionalCertificate
EndpointConfiguration:
Types:
- REGIONAL
# Route 53 record per dominio API
ApiGatewayDNSRecord:
Type: AWS::Route53::RecordSet
Properties:
Name: !Sub "api.${DomainName}."
Type: A
AliasTarget:
DNSName: !GetAtt ApiGatewayRegionalHostname.RegionalHostname
HostedZoneId: !GetAtt ApiGatewayRegionalHostname.RegionalHostedZoneId
EvaluateTargetHealth: false
HostedZoneId: !Ref HostedZoneId
# Lambda Function URL con AuthType AWS_IAM
SecureLambdaUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: AWS_IAM
TargetFunctionArn: !GetAtt SecureLambdaFunction.Arn
Cors:
AllowCredentials: true
AllowHeaders:
- Authorization
- Content-Type
AllowMethods:
- GET
- POST
AllowOrigins:
- !Ref AllowedOrigin
MaxAge: 86400
InvokeMode: BUFFEREDParameters:
# AWS-specific types for automatic validation
VPCId:
Type: AWS::EC2::VPC::Id
Description: VPC ID for deployment
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Subnet IDs for private subnets
SecurityGroupIds:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Security group IDs
DatabaseInstanceIdentifier:
Type: AWS::RDS::DBInstance::Identifier
Description: RDS instance identifier
KMSKeyArn:
Type: AWS::KMS::Key::Arn
Description: KMS key ARN for encryption
SecretArn:
Type: AWS::SecretsManager::Secret::Arn
Description: Secrets Manager secret ARN
LambdaFunctionArn:
Type: AWS::Lambda::Function::Arn
Description: Lambda function ARN
# SSM Parameter with secure string
DatabasePassword:
Type: AWS::SSM::Parameter::Value<SecureString>
NoEcho: true
Description: Database password from SSM
# Custom parameters with constraints
DBUsername:
Type: String
Description: Database username
Default: appuser
MinLength: 1
MaxLength: 63
AllowedPattern: "[a-zA-Z][a-zA-Z0-9_]*"
ConstraintDescription: Must start with letter, alphanumeric and underscores only
DBPort:
Type: Number
Description: Database port
Default: 5432
MinValue: 1024
MaxValue: 65535
MaxConnections:
Type: Number
Description: Maximum database connections
Default: 100
MinValue: 10
MaxValue: 65535
EnvironmentName:
Type: String
Description: Deployment environment
Default: dev
AllowedValues:
- dev
- staging
- production
ConstraintDescription: Must be dev, staging, or productionOutputs:
# Export for cross-stack references
VPCIdExport:
Description: VPC ID for network stack
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPCId"
ApplicationSecurityGroupIdExport:
Description: Application security group ID
Value: !Ref ApplicationSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-AppSecurityGroupId"
DatabaseSecurityGroupIdExport:
Description: Database security group ID
Value: !Ref DatabaseSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-DBSecurityGroupId"
KMSKeyArnExport:
Description: KMS key ARN for encryption
Value: !GetAtt ApplicationKmsKey.Arn
Export:
Name: !Sub "${AWS::StackName}-KMSKeyArn"
DatabaseSecretArnExport:
Description: Database secret ARN
Value: !Ref DatabaseSecret
Export:
Name: !Sub "${AWS::StackName}-DatabaseSecretArn"
SSLCertificateArnExport:
Description: SSL certificate ARN
Value: !Ref SSLCertificate
Export:
Name: !Sub "${AWS::StackName}-SSLCertificateArn"Parameters:
NetworkStackName:
Type: String
Description: Name of the network stack
Resources:
# Import values from network stack
VPCId:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Select [0, !Split [",", !ImportValue !Sub "${NetworkStackName}-VPCcidrs"]]
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-app-sg"
VpcId: !ImportValue !Sub "${NetworkStackName}-VPCId"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !ImportValue !Sub "${NetworkStackName}-ALBSecurityGroupId"Resources:
# Encrypted CloudWatch Log Group
EncryptedLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${AWS::StackName}-function"
RetentionInDays: 30
KmsKeyId: !Ref ApplicationKmsKey
# Data protection policy
LogGroupClass: STANDARD
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Encrypted
Value: "true"
# Metric Filter for security events
SecurityEventMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !Ref EncryptedLogGroup
FilterPattern: '[ERROR, WARNING, "Access Denied", "Unauthorized"]'
MetricTransformations:
- MetricValue: "1"
MetricNamespace: !Sub "${AWS::StackName}/Security"
MetricName: SecurityEvents
# Alarm for security errors
SecurityAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-security-errors"
AlarmDescription: Alert on security-related errors
MetricName: SecurityEvents
Namespace: !Sub "${AWS::StackName}/Security"
Statistic: Sum
Period: 60
EvaluationPeriods: 5
Threshold: 1
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref SecurityAlertTopic
# SNS Topic for security alerts
SecurityAlertTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${AWS::StackName}-security-alerts"AWSTemplateFormatVersion: 2010-09-09
Description: Defense in depth security architecture
Resources:
# Layer 1: Network Security - Security Groups
WebTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Web tier security group"
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS from internet"
AppTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "App tier security group"
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref WebTierSecurityGroup
# Layer 2: Encryption - KMS
DataEncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: "Data encryption key"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "EnableIAMPoliciesForKeyManagement"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowEncryptionOperations"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AppRole"
Action:
- kms:Encrypt
- kms:Decrypt
Resource: "*"
# Layer 3: Secrets Management
ApplicationSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/application/credentials"
SecretString: "{}"
KmsKeyId: !Ref DataEncryptionKey
# Layer 4: IAM Least Privilege
ApplicationRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-app-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ecs.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: MinimalSecretsAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref ApplicationSecret
# Layer 5: Logging and Monitoring
AuditLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/${AWS::StackName}/audit"
RetentionInDays: 365
KmsKeyId: !Ref DataEncryptionKey
# Layer 6: WAF for API protection
WebACL:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub "${AWS::StackName}-waf"
Scope: REGIONAL
DefaultAction:
Allow:
CustomRequestHandling:
InsertHeaders:
- Name: X-Frame-Options
Value: DENY
Rules:
- Name: BlockSQLInjection
Priority: 1
Statement:
SqliMatchStatement:
FieldToMatch:
Body:
OversizeHandling: CONTINUE
SensitivityLevel: HIGH
Action:
Block:
CustomResponse:
ResponseCode: 403
ResponseBody: "Request blocked due to SQL injection"
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: BlockSQLInjection
- Name: BlockXSS
Priority: 2
Statement:
XssMatchStatement:
FieldToMatch:
QueryString:
OversizeHandling: CONTINUE
Action:
Block:
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: BlockXSS
- Name: RateLimit
Priority: 3
Statement:
RateBasedStatement:
Limit: 2000
EvaluationWindowSec: 60
Action:
Block:
CustomResponse:
ResponseCode: 429
ResponseBody: "Too many requests"
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: RateLimit
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${AWS::StackName}-WebACL"
SampledRequestsEnabled: trueFor complete details on resources and their properties, see:
Stack Policies prevent accidental updates to critical infrastructure resources. Use them to protect production resources from unintended modifications.
Resources:
ProductionStackPolicy:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/production-stack.yaml"
StackPolicyBody:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: Update:*
Principal: "*"
Resource: "*"
- Effect: Deny
Action:
- Update:Replace
- Update:Delete
Principal: "*"
Resource:
- LogicalResourceId/ProductionDatabase
- LogicalResourceId/ProductionKmsKey
Condition:
StringEquals:
aws:RequestedRegion:
- us-east-1
- us-west-2
# Inline stack policy for sensitive resources
SensitiveResourcesPolicy:
Type: AWS::CloudFormation::StackPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Deny
Action: Update:*
Principal: "*"
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Environment: production
Not:
StringEquals:
aws:username: security-adminEnable termination protection to prevent accidental deletion of production stacks. This adds a safety layer for critical infrastructure.
Resources:
# Production stack with termination protection
ProductionDatabaseStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/database.yaml"
TerminationProtection: true
Parameters:
Environment: production
InstanceClass: db.r6g.xlarge
MultiAZ: true
# Stack with conditional termination protection
ApplicationStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/application.yaml"
TerminationProtection: !If [IsProduction, true, false]
Parameters:
Environment: !Ref EnvironmentDetect configuration drift in your CloudFormation stacks to identify unauthorized or unexpected changes.
Resources:
# Custom resource for drift detection
DriftDetectionFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-drift-detector"
Runtime: python3.11
Handler: drift_detector.handler
Role: !GetAtt DriftDetectionRole.Arn
Code:
S3Bucket: !Ref CodeBucket
S3Key: lambda/drift-detector.zip
Environment:
Variables:
STACK_NAME: !Ref StackName
SNS_TOPIC_ARN: !Ref DriftAlertTopic
Timeout: 300
# Scheduled drift detection
DriftDetectionSchedule:
Type: AWS::Events::Rule
Properties:
Name: !Sub "${AWS::StackName}-drift-schedule"
ScheduleExpression: rate(1 day)
State: ENABLED
Targets:
- Arn: !GetAtt DriftDetectionFunction.Arn
Id: DriftDetectionFunction
# Permission for EventBridge to invoke Lambda
DriftDetectionPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref DriftDetectionFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt DriftDetectionSchedule.Arn
# SNS topic for drift alerts
DriftAlertTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${AWS::StackName}-drift-alerts"import boto3
import json
def handler(event, context):
cloudformation = boto3.client('cloudformation')
sns = boto3.client('sns')
stack_name = event.get('STACK_NAME', 'my-production-stack')
topic_arn = event.get('SNS_TOPIC_ARN')
# Detect drift
response = cloudformation.detect_stack_drift(StackName=stack_name)
# Wait for drift detection to complete
import time
time.sleep(60)
# Get drift status
drift_status = cloudformation.describe_stack-drift-detection-status(
StackName=stack_name,
DetectionId=response['StackDriftDetectionId']
)
# Get resources with drift
resources = []
paginator = cloudformation.get_paginator('list_stack_resources')
for page in paginator.paginate(StackName=stack_name):
for resource in page['StackResourceSummaries']:
if resource['DriftStatus'] != 'IN_SYNC':
resources.append({
'LogicalId': resource['LogicalResourceId'],
'PhysicalId': resource['PhysicalResourceId'],
'DriftStatus': resource['DriftStatus'],
'Expected': resource.get('ExpectedResourceType'),
'Actual': resource.get('ActualResourceType')
})
# Send alert if drift detected
if resources:
message = f"Drift detected on stack {stack_name}:\n"
for r in resources:
message += f"- {r['LogicalId']}: {r['DriftStatus']}\n"
sns.publish(
TopicArn=topic_arn,
Subject=f"CloudFormation Drift Alert: {stack_name}",
Message=message
)
return {
'statusCode': 200,
'body': json.dumps({
'drift_status': drift_status['StackDriftStatus'],
'resources_with_drift': len(resources)
})
}Use Change Sets to preview and review changes before applying them to production stacks.
Resources:
# Change set for stack update
ChangeSet:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/updated-template.yaml"
ChangeSetName: !Sub "${AWS::StackName}-update-changeset"
ChangeSetType: UPDATE
Parameters:
Environment: !Ref Environment
InstanceType: !Ref NewInstanceType
Capabilities:
- CAPABILITY_IAM
- CAPABILITY_NAMED_IAM
# Nested change set for review
ReviewChangeSet:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/review-template.yaml"
ChangeSetName: !Sub "${AWS::StackName}-review-changeset"
ChangeSetType: UPDATE
Parameters:
Environment: !Ref Environment
Tags:
- Key: ChangeSetType
Value: review
- Key: CreatedBy
Value: CloudFormation#!/bin/bash
# Create a change set for review
aws cloudformation create-change-set \
--stack-name my-production-stack \
--change-set-name production-update-changeset \
--template-url https://my-bucket.s3.amazonaws.com/updated-template.yaml \
--parameters ParameterKey=Environment,ParameterValue=production \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
# Wait for change set creation
aws cloudformation wait change-set-create-complete \
--stack-name my-production-stack \
--change-set-name production-update-changeset
# Describe change set to see what will change
aws cloudformation describe-change-set \
--stack-name my-production-stack \
--change-set-name production-update-changeset
# Execute change set if changes look good
aws cloudformation execute-change-set \
--stack-name my-production-stack \
--change-set-name production-update-changesetResources:
# Production stack with rollback configuration
ProductionStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/production.yaml"
TimeoutInMinutes: 60
RollbackConfiguration:
RollbackTriggers:
- Arn: !Sub "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:ProductionCPUHigh"
Type: AWS::CloudWatch::Alarm
- Arn: !Sub "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:ProductionLatencyHigh"
Type: AWS::CloudWatch::Alarm
MonitoringTimeInMinutes: 15
NotificationARNs:
- !Ref UpdateNotificationTopic
# CloudWatch alarms for rollback
ProductionCPUHigh:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-CPU-High"
AlarmDescription: Trigger rollback if CPU exceeds 80%
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 60
EvaluationPeriods: 5
Threshold: 80
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref UpdateNotificationTopic
# SNS topic for notifications
UpdateNotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${AWS::StackName}-update-notifications"Enable Termination Protection
Use Stack Policies
Implement Drift Detection
Use Change Sets
Configure Rollback Triggers
Implement Change Management
Use Stack Sets for Multi-Account