
Aws Cloudformation Security
- 72 installs
- 318 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit
Author secure AWS CloudFormation templates using KMS encryption, Secrets Manager, secure parameters, and least-privilege IAM.
About
Provides CloudFormation patterns for infrastructure security including KMS encryption, Secrets Manager, SSM secure parameters, security groups, and TLS/SSL. A developer uses it to build encrypted, defense-in-depth infrastructure as code.
- Encryption at-rest and in-transit with KMS and Secrets Manager
- Least-privilege IAM, secure parameters, and TLS/SSL certificates
Aws Cloudformation Security by the numbers
- 72 all-time installs (skills.sh)
- Ranked #1,163 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-securityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 72 |
|---|---|
| repo stars | ★ 318 |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit ↗ |
What it does
Author secure AWS CloudFormation templates using KMS encryption, Secrets Manager, secure parameters, and least-privilege IAM.
Files
AWS CloudFormation Security
Overview
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.
When to Use
Use this skill when:
- Creating CloudFormation templates with encryption at-rest and in-transit
- Managing secrets and credentials with AWS Secrets Manager
- Configuring AWS KMS for encryption keys
- Implementing secure parameters with SSM Parameter Store
- Creating IAM policies with least privilege
- Configuring security groups and network security
- Implementing secure cross-stack references
- Configuring TLS/SSL for AWS services
- Applying defense-in-depth for infrastructure
Instructions
Follow these steps to create secure CloudFormation infrastructure:
1. Define Encryption Keys: Create KMS keys for data encryption 2. Set Up Secrets: Use Secrets Manager for credentials and API keys 3. Configure Secure Parameters: Use SSM Parameter Store with encryption 4. Implement IAM Policies: Apply least privilege principles 5. Create Security Groups: Configure network access controls 6. Set Up TLS Certificates: Use ACM for SSL/TLS certificates 7. Enable Encryption: Configure encryption for storage and transit 8. Implement Monitoring: Enable CloudTrail and security logging
For complete examples, see the EXAMPLES.md file.
Examples
The following examples demonstrate common security patterns:
Example 1: KMS Key for Encryption
KmsKey:
Type: AWS::KMS::Key
Properties:
KeyPolicy:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: kms:*
Resource: "*"
- Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
Resource: "*"
KmsAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/${AWS::StackName}-key"
TargetKeyId: !Ref KmsKeyExample 2: Secrets Manager Secret
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/database-credentials"
Description: Database credentials for application
SecretString: !Sub |
{
"username": "${DBUsername}",
"password": "${DBPassword}",
"host": "${DBInstance.Endpoint.Address}",
"port": "${DBInstance.Endpoint.Port}"
}Example 3: Secure Parameter
SecureParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub "/${AWS::StackName}/api-key"
Type: SecureString
Value: !Ref ApiKeyValue
Description: Secure API key for external serviceFor complete production-ready examples, see EXAMPLES.md.
CloudFormation Template Structure
Base Template with Security Section
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 EnvironmentAWS KMS - Encryption
Complete KMS Key with Full Policy
Resources:
# 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: "*"S3 Bucket with KMS Encryption
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"AWS Secrets Manager
Secrets Manager with Automatic Rotation
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: "*"SSM Parameter Store with SecureString
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"IAM Security - Least Privilege
IAM Role with Granular Policies
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 AllowedResourceArnsIAM Policy for Cross-Account Access
Resources:
# 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}/*"VPC Security
Security Groups with Restrictive Rules
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: trueTLS/SSL Certificates with ACM
Certificate Manager for API Gateway
Resources:
# 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: BUFFEREDParameter Security Best Practices
AWS-Specific Parameter Types with Validation
Parameters:
# 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 and Secure Cross-Stack References
Export with Naming Convention
Outputs:
# 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"Import from Network Stack
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"CloudWatch Logs Encryption
Log Group with KMS Encryption
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"Defense in Depth
Stack with Multiple Security Layers
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: trueBest Practices
Encryption
- Always use KMS with customer-managed keys for sensitive data
- Enable automatic key rotation (max 365 days)
- Use S3 bucket keys to reduce KMS costs
- Encrypt CloudWatch Logs with KMS
- Implement envelope encryption for large data
Secrets Management
- Use Secrets Manager for automatic rotation
- Reference secrets via ARN, not hard-coded
- Use resource-based policies for granular access
- Implement encryption context for auditing
- Limit access with IAM conditions
IAM Security
- Apply least privilege in all policies
- Use permissions boundaries to limit escalation
- Enable MFA for administrative roles
- Implement condition keys for region/endpoint
- Regular audit with IAM Access Analyzer
Network Security
- Security groups with minimal rules
- Deny default outbound where possible
- Use VPC endpoints for AWS services
- Implement private subnets for backend tiers
- Use Network ACLs as additional layer
TLS/SSL
- Use ACM for managed certificates
- Enforce HTTPS with resource policies
- Configure minimum TLS 1.2
- Use HSTS headers
- Renew certificates before expiration
Monitoring
- Enable CloudTrail for audit trail
- Create metrics for security events
- Configure alarms for suspicious activity
- Appropriate log retention (min 90 days)
- Use GuardDuty for threat detection
Related Resources
Additional Files
For complete details on resources and their properties, see:
- REFERENCE.md - Detailed reference guide for all CloudFormation security resources
- EXAMPLES.md - Complete production-ready examples for security scenarios
CloudFormation Stack Management Best Practices
Stack Policies
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-adminTermination Protection
Enable 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 EnvironmentDrift Detection
Detect 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"Drift Detection Python Handler
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)
})
}Change Sets Usage
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: CloudFormationChange Set Generation Script
#!/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-changesetStack Update with Rollback Triggers
Resources:
# 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"Best Practices for Stack Management
1. Enable Termination Protection
- Always enable for production stacks
- Use as a safety mechanism against accidental deletion
- Requires manual disabling before deletion
2. Use Stack Policies
- Protect critical resources from unintended updates
- Use Deny statements for production databases, KMS keys, and IAM roles
- Apply conditions based on region, user, or tags
3. Implement Drift Detection
- Run drift detection regularly (daily for production)
- Alert on any drift detection
- Investigate and remediate drift immediately
4. Use Change Sets
- Always use Change Sets for production updates
- Review changes before execution
- Use descriptive change set names
5. Configure Rollback Triggers
- Set up CloudWatch alarms for critical metrics
- Configure monitoring time to allow stabilization
- Test rollback triggers in non-production first
6. Implement Change Management
- Require approval for production changes
- Use CodePipeline with manual approval gates
- Document all changes in change log
7. Use Stack Sets for Multi-Account
- Deploy infrastructure consistently across accounts
- Use StackSets for organization-wide policies
- Implement drift detection at organization level
Constraints and Warnings
Resource Limits
- Security Group Rules: Maximum 60 inbound and 60 outbound rules per security group
- Security Groups: Maximum 500 security groups per VPC
- NACL Rules: Maximum 20 inbound and 20 outbound rules per NACL per subnet
- VPC Limits: Maximum 5 VPCs per region (soft limit, can be increased)
Security Constraints
- Default Security Groups: Default security groups cannot be deleted
- Security Group References: Security group references cannot span VPC peering in some cases
- NACL Stateless: NACLs are stateless; return traffic must be explicitly allowed
- Flow Logs: VPC Flow Logs generate significant CloudWatch Logs costs
Operational Constraints
- CIDR Overlap: VPC CIDR blocks cannot overlap with peered VPCs or on-prem networks
- ENI Limits: Each instance type has maximum ENI limits; affects scaling
- Elastic IP Limits: Each account has limited number of Elastic IPs
- NAT Gateway Limits: Each AZ can have only one NAT gateway per subnet
Network Constraints
- Transit Gateway: Transit Gateway attachments have per-AZ and per-account limits
- VPN Connections: VPN connections have bandwidth limitations
- Direct Connect: Direct Connect requires physical infrastructure and lead time
- PrivateLink: VPC Endpoint services have availability constraints
Cost Considerations
- NAT Gateway: NAT gateways incur hourly costs plus data processing costs
- Traffic Mirroring: Traffic mirroring doubles data transfer costs
- Flow Logs: Flow logs storage and analysis add significant costs
- PrivateLink: Interface VPC endpoints incur hourly and data processing costs
Access Control Constraints
- IAM vs Resource Policies: Some services require both IAM and resource-based policies
- SCP Limits: Service Control Policies have character limits and complexity constraints
- Permission Boundaries: Permission boundaries do not limit service actions
- Session Policies: Session policies cannot grant more permissions than IAM policies
Additional Files
AWS CloudFormation Security - Examples
This file contains comprehensive examples for AWS CloudFormation security patterns with production-ready configurations.
Example 1: Complete KMS Security Stack
Complete KMS key setup with encryption, key rotation, and proper access policies.
AWSTemplateFormatVersion: 2010-09-09
Description: Complete KMS security configuration with key rotation and policies
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
OrganizationId:
Type: String
Description: AWS Organization ID for condition
Mappings:
KeyPolicyConfig:
dev:
EnableKeyRotation: true
PendingWindowDays: 7
staging:
EnableKeyRotation: true
PendingWindowDays: 14
production:
EnableKeyRotation: true
PendingWindowDays: 30
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
# Master KMS Key per applicazione
ApplicationKmsKey:
Type: AWS::KMS::Key
Properties:
Description: !Sub "Master encryption key for ${Environment} environment"
KeyPolicy:
Version: "2012-10-17"
Id: !Sub "${AWS::StackName}-key-policy"
Statement:
# Enable IAM policies for key administration
- 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
- kms:CancelKeyDeletion
- kms:ScheduleKeyDeletion
Resource: "*"
Condition:
StringEquals:
aws:PrincipalOrgID: !Ref OrganizationId
# Allow cryptographic operations 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"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/RDSInstanceRole"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
- kms:ReEncrypt*
- kms:DescribeKey
Resource: "*"
# Allow S3 to use key for bucket encryption
- Sid: "AllowS3Encryption"
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
# Allow RDS to use key for database encryption
- Sid: "AllowRDSEncryption"
Effect: Allow
Principal:
Service: rds.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
# Deny access to all principals not in the organization
- Sid: "DenyOutsideOrganization"
Effect: Deny
Principal: "*"
Action: kms:*
Resource: "*"
Condition:
StringNotEquals:
aws:PrincipalOrgID: !Ref OrganizationId
KeyUsage: ENCRYPT_DECRYPT
EnableKeyRotation: !FindInMap [KeyPolicyConfig, !Ref Environment, EnableKeyRotation]
PendingWindowInDays: !FindInMap [KeyPolicyConfig, !Ref Environment, PendingWindowDays]
MultiRegion: !Ref IsProduction
# Alias per la chiave
ApplicationKmsKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/application-${Environment}"
TargetKeyId: !Ref ApplicationKmsKey
# S3 Bucket Encryption Key
S3EncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for S3 bucket encryption"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "EnableIAMAdmin"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowS3Service"
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
EnableKeyRotation: true
# RDS Encryption Key
RdsEncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for RDS database encryption"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "EnableIAMAdmin"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowRDSService"
Effect: Allow
Principal:
Service: rds.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
# Secrets Manager Encryption Key
SecretsEncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for Secrets Manager encryption"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "EnableIAMAdmin"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowSecretsManager"
Effect: Allow
Principal:
Service: secretsmanager.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
Outputs:
ApplicationKeyArn:
Description: ARN of the application KMS key
Value: !GetAtt ApplicationKmsKey.Arn
Export:
Name: !Sub "${AWS::StackName}-AppKeyArn"
S3KeyArn:
Description: ARN of the S3 encryption KMS key
Value: !GetAtt S3EncryptionKey.Arn
Export:
Name: !Sub "${AWS::StackName}-S3KeyArn"
RdsKeyArn:
Description: ARN of the RDS encryption KMS key
Value: !GetAtt RdsEncryptionKey.Arn
Export:
Name: !Sub "${AWS::StackName}-RdsKeyArn"
SecretsKeyArn:
Description: ARN of the Secrets Manager encryption KMS key
Value: !GetAtt SecretsEncryptionKey.Arn
Export:
Name: !Sub "${AWS::StackName}-SecretsKeyArn"Example 2: Secrets Manager con Rotazione Automatica
Complete secrets configuration with automatic rotation and resource policies.
AWSTemplateFormatVersion: 2010-09-09
Description: Secrets Manager configuration with automatic rotation
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
DBUsername:
Type: String
Description: Database username
Default: appuser
DBHost:
Type: String
Description: Database host
DBName:
Type: String
Description: Database name
Resources:
# Database credentials secret
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/database/credentials"
Description: "Database credentials for ${Environment} environment"
SecretString: !Sub |
{
"username": "${DBUsername}",
"password": "${DBPassword}",
"host": "${DBHost}",
"port": "5432",
"dbname": "${DBName}",
"engine": "postgresql"
}
KmsKeyId: !GetAtt SecretsEncryptionKey.Arn
RotationRules:
AutomaticallyAfterDays: 30
ResourcePolicy:
Version: "2012-10-17"
Statement:
# Allow application roles to read secret
- Sid: "AllowAppReadAccess"
Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${AWS::AccountId}:role/LambdaExecutionRole"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/ECSTaskRole"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Environment: !Ref Environment
# Allow rotation lambda to manage secret
- Sid: "AllowRotationAccess"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/SecretRotationRole"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:PutSecretValue
- secretsmanager:DescribeSecret
Resource: "*"
# Deny access without encryption context
- Sid: "DenyUnencryptedAccess"
Effect: Deny
Principal: "*"
Action:
- secretsmanager:GetSecretValue
Resource: "*"
Condition:
StringNotEquals:
kms:ViaService: !Sub "secretsmanager.${AWS::Region}.amazonaws.com"
StringNotEquals:
kms:EncryptContext: !Sub "secret:${AWS::StackName}"
Tags:
- Key: Environment
Value: !Ref Environment
- Key: ManagedBy
Value: CloudFormation
- Key: RotationEnabled
Value: "true"
# API credentials secret
ApiCredentialsSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/api/credentials"
Description: "External API credentials"
SecretString: !Sub |
{
"api_key": "${ExternalApiKey}",
"api_secret": "${ExternalApiSecret}",
"api_endpoint": "https://api.example.com"
}
KmsKeyId: !GetAtt SecretsEncryptionKey.Arn
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowAppAccess"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/LambdaExecutionRole"
Action:
- secretsmanager:GetSecretValue
Resource: "*"
# Generated secret for service accounts
ServiceAccountSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/service/account"
Description: "Generated service account credentials"
GenerateSecretString:
SecretStringTemplate: '{"username": "service_account"}'
GenerateSecretKey: "password"
PasswordLength: 64
ExcludeCharacters: '"@/\\'
KmsKeyId: !GetAtt SecretsEncryptionKey.Arn
RotationRules:
AutomaticallyAfterDays: 90
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowServiceAccess"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/ServiceRole"
Action:
- secretsmanager:GetSecretValue
Resource: "*"
# Cross-account shared secret
SharedSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/shared/cross-account"
Description: "Secret shared with partner account"
SecretString: "{}"
KmsKeyId: !GetAtt SecretsEncryptionKey.Arn
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowPartnerAccess"
Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${PartnerAccountId}:role/PartnerSecretReader"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: "*"
Condition:
Bool:
aws:MultiFactorAuthPresent: true
# Secrets Manager VPC Endpoint
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 AppSecurityGroup
PrivateDnsEnabled: true
Outputs:
DatabaseSecretArn:
Description: ARN of the database secret
Value: !Ref DatabaseSecret
ApiSecretArn:
Description: ARN of the API credentials secret
Value: !Ref ApiCredentialsSecretExample 3: IAM Security con Least Privilege
Complete IAM configuration with granular permissions, permissions boundaries, and conditions.
AWSTemplateFormatVersion: 2010-09-09
Description: IAM security configuration with least privilege
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
VPCId:
Type: AWS::EC2::VPC::Id
Description: VPC ID
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
# Permissions Boundary Policy
PermissionsBoundaryPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: "Permissions boundary for application roles"
PolicyDocument:
Version: "2012-10-17"
Statement:
# Deny all actions not explicitly allowed
- Sid: "DenyAllOutsideSpecifiedResources"
Effect: Deny
Action:
- "*"
Resource: "*"
Condition:
StringNotEqualsIfExists:
aws:ResourceTag/Environment: !Ref Environment
ArnNotEqualsIfExists:
aws:SourceArn: !Sub "arn:aws:s3:::${DataBucketName}/*"
# Deny modification of security-critical resources
- Sid: "DenySecurityModification"
Effect: Deny
Action:
- iam:DeleteUserPolicy
- iam:DeleteRolePolicy
- iam:DeleteGroupPolicy
- iam:PutUserPolicy
- iam:PutRolePolicy
- iam:PutGroupPolicy
- iam:AttachUserPolicy
- iam:AttachRolePolicy
- iam:AttachGroupPolicy
- iam:DetachUserPolicy
- iam:DetachRolePolicy
- iam:DetachGroupPolicy
Resource: "*"
Condition:
Bool:
aws:MultiFactorAuthPresent: false
# Lambda Execution Role con permessi minimi
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-lambda-role"
Description: "Lambda execution role with least privilege"
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: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}-*"
PermissionsBoundary: !Ref PermissionsBoundaryPolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
# Secrets access policy
- PolicyName: SecretsAccessPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${AWS::StackName}/*"
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${Environment}/*"
Condition:
StringEquals:
secretsmanager:SecretTarget: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${AWS::StackName}/database/*"
# S3 access policy
- PolicyName: S3AccessPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource:
- !Sub "${DataBucket.Arn}/*"
Condition:
StringEquals:
s3:ResourceAccount: !Ref AWS::AccountId
IpAddress:
aws:SourceIp: !If [IsProduction, !Ref AllowedIPRange, "0.0.0.0/0"]
- Effect: Deny
Action:
- s3:DeleteObject*
Resource:
- !Sub "${DataBucket.Arn}/*"
Condition:
Bool:
aws:MultiFactorAuthPresent: false
# CloudWatch Logs policy
- PolicyName: CloudWatchLogsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogStreams
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-*"
Condition:
StringEquals:
aws:ResourceTag/Environment: !Ref Environment
# DynamoDB access policy
- PolicyName: DynamoDBPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
Resource:
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${AWS::StackName}-table"
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${AWS::StackName}-table/index/*"
Condition:
ForAllValue:StringEquals:
dynamodb:Attributes: !Ref AllowedAttributes
ForAnyValue:StringLike:
dynamodb:Select: SPECIFIC_ATTRIBUTES
Tags:
- Key: Environment
Value: !Ref Environment
- Key: LeastPrivilege
Value: "true"
# Cross-account access role
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-cross-account-role"
Description: "Role for cross-account access with MFA requirement"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::${ProductionAccountId}:root"
Action: sts:AssumeRole
Condition:
StringEquals:
aws:PrincipalAccount: !Ref ProductionAccountId
Bool:
aws:MultiFactorAuthPresent: true
MaxSessionDuration: 3600
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*
- dynamodb:DeleteItem*
- dynamodb:PutItem*
- dynamodb:UpdateItem*
Resource:
- !Sub "${SharedBucket.Arn}/*"
- !Sub "${SharedTable.Arn}"
# IAM User with access keys
ServiceUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${AWS::StackName}-service-user"
Groups:
- !Ref ServiceUserGroup
ServiceUserGroup:
Type: AWS::IAM::Group
Properties:
GroupName: !Sub "${AWS::StackName}-service-group"
Policies:
- PolicyName: ServiceUserPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
Resource:
- !Sub "${UploadBucket.Arn}/*"
Outputs:
LambdaRoleArn:
Description: ARN of the Lambda execution role
Value: !GetAtt LambdaExecutionRole.Arn
CrossAccountRoleArn:
Description: ARN of the cross-account role
Value: !GetAtt CrossAccountRole.Arn
ServiceUserArn:
Description: ARN of the service user
Value: !GetAtt ServiceUser.ArnExample 4: VPC Security Groups Configuration
Complete security group configuration with layered security.
AWSTemplateFormatVersion: 2010-09-09
Description: VPC security groups with defense in depth
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
VPCId:
Type: AWS::EC2::VPC::Id
Description: VPC ID
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
# Security Group per ALB (front-end)
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-alb-sg"
GroupDescription: "Security group for application load balancer"
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-alb-sg"
- Key: Environment
Value: !Ref Environment
- Key: Tier
Value: "front-end"
SecurityGroupIngress:
# HTTP from internet
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: "HTTP from internet (redirect to HTTPS)"
# HTTPS from internet
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS from internet"
SecurityGroupEgress:
# Only to application security group
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "HTTP to application tier"
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "HTTPS to application tier"
# Security Group per Application tier
AppSecurityGroup:
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
- Key: Tier
Value: "application"
SecurityGroupIngress:
# From ALB only
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: "Application port from ALB"
# From bastion for SSH (if needed)
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "SSH from bastion host"
# ICMP for health checks
- IpProtocol: icmp
FromPort: -1
ToPort: -1
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "ICMP from bastion"
SecurityGroupEgress:
# HTTPS for external API calls
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS for external APIs"
# DNS for name resolution
- IpProtocol: udp
FromPort: 53
ToPort: 53
CidrIp: 10.0.0.0/16
Description: "DNS for VPC resolution"
# To database tier
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref DatabaseSecurityGroup
Description: "PostgreSQL to database tier"
# Security Group per Database tier
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"
- Key: Environment
Value: !Ref Environment
- Key: Tier
Value: "database"
SecurityGroupIngress:
# From application tier only
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "PostgreSQL from application tier"
# From bastion for administration (with MFA)
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "PostgreSQL from bastion for admin"
SecurityGroupEgress:
# Minimal outbound - only for security updates
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS for security patches"
# Security Group per Cache tier (Redis/Memcached)
CacheSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-cache-sg"
GroupDescription: "Security group for cache tier"
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-cache-sg"
- Key: Environment
Value: !Ref Environment
- Key: Tier
Value: "cache"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "Redis from application tier"
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS for updates"
# Bastion Security Group
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-bastion-sg"
GroupDescription: "Security group for bastion host"
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-bastion-sg"
- Key: Environment
Value: !Ref Environment
- Key: Tier
Value: "bastion"
SecurityGroupIngress:
# SSH from corporate VPN or specific IPs
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref AllowedSSHIP
Description: "SSH from allowed IP range"
SecurityGroupEgress:
# To all internal tiers
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "SSH to application"
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref DatabaseSecurityGroup
Description: "SSH to database for tunneling"
Outputs:
ALBSecurityGroupId:
Description: ID of the ALB security group
Value: !Ref ALBSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-ALBSGId"
AppSecurityGroupId:
Description: ID of the application security group
Value: !Ref AppSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-AppSGId"
DatabaseSecurityGroupId:
Description: ID of the database security group
Value: !Ref DatabaseSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-DBSGId"Example 5: TLS/SSL con ACM e API Gateway
Complete SSL certificate setup with API Gateway and custom domain.
AWSTemplateFormatVersion: 2010-09-09
Description: SSL certificates and API Gateway with TLS enforcement
Parameters:
DomainName:
Type: String
Description: Primary domain name
HostedZoneId:
Type: AWS::Route53::HostedZone::Id
Description: Route 53 hosted zone ID
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
Resources:
# SSL Certificate with DNS validation
SSLCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
SubjectAlternativeNames:
- !Sub "*.${DomainName}"
- !Sub "api.${DomainName}"
- !Sub "${Environment}.${DomainName}"
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Ref DomainName
Route53HostedZoneId: !Ref HostedZoneId
- DomainName: !Sub "*.${DomainName}"
Route53HostedZoneId: !Ref HostedZoneId
Options:
CertificateTransparencyLoggingPreference: ENABLED
Tags:
- Key: Environment
Value: !Ref Environment
- Key: ManagedBy
Value: CloudFormation
# Regional certificate for API Gateway
RegionalCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Sub "${Environment}.${DomainName}"
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Sub "${Environment}.${DomainName}"
Route53HostedZoneId: !Ref HostedZoneId
# Secure API Gateway
SecureApiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub "${AWS::StackName}-api-${Environment}"
Description: "Secure REST API with TLS enforcement"
EndpointConfiguration:
Types:
- REGIONAL
MinimumCompressionSize: 2048
# Policy to deny non-SSL access
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"
- Effect: Allow
Principal: "*"
Action: execute-api:Invoke
Resource: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${SecureApiGateway}/*"
# API Gateway Deployment
ApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- ApiGatewayMethodGet
Properties:
RestApiId: !Ref SecureApiGateway
StageName: !Ref Environment
# API Gateway Stage
ApiGatewayStage:
Type: AWS::ApiGateway::Stage
Properties:
RestApiId: !Ref SecureApiGateway
StageName: !Ref Environment
DeploymentId: !Ref ApiGatewayDeployment
StageDescription:
LoggingLevel: INFO
DataTraceEnabled: false
ThrottlingRateLimit: 1000
ThrottlingBurstLimit: 2000
MethodSettings:
- ResourcePath: "/*"
HttpMethod: "*"
LoggingLevel: INFO
DataTraceEnabled: false
ThrottlingRateLimit: 1000
ThrottlingBurstLimit: 2000
# API Gateway Domain
ApiGatewayDomain:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: !Sub "api.${DomainName}"
RegionalCertificateArn: !Ref RegionalCertificate
EndpointConfiguration:
Types:
- REGIONAL
# API Gateway Base Path Mapping
ApiBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Ref ApiGatewayDomain
RestApiId: !Ref SecureApiGateway
Stage: !Ref Environment
# Route 53 DNS records for API domain
ApiGatewayDNSRecord:
Type: AWS::Route53::RecordSet
Properties:
Name: !Sub "api.${DomainName}."
Type: A
AliasTarget:
DNSName: !GetAtt ApiGatewayDomain.RegionalHostname
HostedZoneId: !GetAtt ApiGatewayDomain.RegionalHostedZoneId
EvaluateTargetHealth: true
HostedZoneId: !Ref HostedZoneId
# Lambda Function URL con IAM authentication
SecureLambdaUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: AWS_IAM
TargetFunctionArn: !GetAtt LambdaFunction.Arn
Cors:
AllowCredentials: true
AllowHeaders:
- Authorization
- Content-Type
- X-Request-ID
AllowMethods:
- GET
- POST
- PUT
- DELETE
AllowOrigins:
- !Sub "https://${DomainName}"
- !Sub "https://${Environment}.${DomainName}"
MaxAge: 86400
InvokeMode: BUFFERED
# Lambda Permission for URL
LambdaPermissionForUrl:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunction
Action: lambda:InvokeFunctionUrl
Principal: "*"
# WAF Web ACL for API Gateway
WebACL:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub "${AWS::StackName}-waf"
Scope: REGIONAL
DefaultAction:
Allow: {}
Rules:
- Name: AWSManagedRulesCommonRuleSet
Priority: 1
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesCommonRuleSet
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: CommonRuleSet
- Name: RateLimit
Priority: 2
Statement:
RateBasedStatement:
Limit: 2000
EvaluationWindowSec: 60
AggregationKeyType: IP
Action:
Block:
CustomResponse:
ResponseCode: 429
ResponseBody: "Rate limit exceeded"
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: RateLimitRule
- Name: BlockSQLi
Priority: 3
Statement:
SqliMatchStatement:
FieldToMatch:
Body:
OversizeHandling: CONTINUE
SensitivityLevel: HIGH
Action:
Block:
CustomResponse:
ResponseCode: 403
ResponseBody: "Invalid request"
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: BlockSQLi
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${AWS::StackName}-WebACL"
SampledRequestsEnabled: true
# Associate WAF with API Gateway
WafAssociation:
Type: AWS::WAFv2::WebACLAssociation
Properties:
WebACLArn: !GetAtt WebACL.Arn
ResourceArn: !Sub "arn:aws:apigateway:${AWS::Region}::/restapis/${SecureApiGateway}/stages/${Environment}"
Outputs:
ApiEndpoint:
Description: API Gateway endpoint URL
Value: !Sub "https://${SecureApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${Environment}"
ApiDomainUrl:
Description: Custom domain URL for API
Value: !Sub "https://api.${DomainName}/${Environment}"
CertificateArn:
Description: ARN of the SSL certificate
Value: !Ref SSLCertificateExample 6: Complete Encrypted Infrastructure Stack
Complete production-ready infrastructure with encryption at rest and in transit.
AWSTemplateFormatVersion: 2010-09-09
Description: Complete encrypted infrastructure with defense in depth
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
VPCId:
Type: AWS::EC2::VPC::Id
PrivateSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
PublicSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Resources:
# KMS Key for encryption
MasterKmsKey:
Type: AWS::KMS::Key
Properties:
Description: "Master encryption key for ${Environment}"
KeyPolicy:
Version: "2012-10-17"
Statement:
- Sid: "EnableIAMAdmin"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowAppEncryption"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AppRole"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
EnableKeyRotation: true
# S3 Bucket con encryption
DataBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "secure-data-${AWS::AccountId}-${AWS::Region}"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !Ref MasterKmsKey
BucketKeyEnabled: true
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: ArchiveOldVersions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 90
# Encrypted RDS Instance
DatabaseInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub "${AWS::StackName}-db-${Environment}"
DBInstanceClass: !FindInMap [InstanceTypes, !Ref Environment, DBInstanceClass]
Engine: postgres
EngineVersion: "15.4"
MasterUsername: !Ref DBUsername
MasterUserPassword: !Ref DBPassword
DBName: !Ref DBName
VPCSecurityGroups:
- !Ref DatabaseSecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
StorageEncrypted: true
KmsKeyId: !Ref MasterKmsKey
BackupRetentionPeriod: 35
MultiAZ: !Ref IsProduction
AutoMinorVersionUpgrade: true
DeletionProtection: !Ref IsProduction
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Encrypted
Value: "true"
# Encrypted ElastiCache Redis
CacheCluster:
Type: AWS::ElastiCache::ReplicationGroup
Properties:
ReplicationGroupId: !Sub "${AWS::StackName}-redis-${Environment}"
ReplicationGroupDescription: "Redis cluster for ${Environment}"
Engine: redis
CacheNodeType: !FindInMap [InstanceTypes, !Ref Environment, CacheNodeType]
NumNodeGroups: !If [IsProduction, 2, 1]
ReplicasPerNodeGroup: !If [IsProduction, 1, 0]
AutomaticFailoverEnabled: !Ref IsProduction
CacheSubnetGroupName: !Ref CacheSubnetGroup
SecurityGroupIds:
- !Ref CacheSecurityGroup
AtRestEncryptionEnabled: true
TransitEncryptionEnabled: true
AuthToken: !Ref RedisAuthToken
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Encrypted
Value: "true"
# Encrypted DynamoDB Table
DataTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub "${AWS::StackName}-table-${Environment}"
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
SSESpecification:
SSEEnabled: true
SSEType: KMS
KMSMasterKeyId: !Ref MasterKmsKey
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Encrypted
Value: "true"
# Encrypted CloudWatch Log Group
ApplicationLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/${AWS::StackName}/${Environment}"
RetentionInDays: 90
KmsKeyId: !Ref MasterKmsKey
# Encrypted SQS Queue
ProcessingQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Sub "${AWS::StackName}-queue-${Environment}"
VisibilityTimeout: 300
MessageRetentionPeriod: 1209600
KmsMasterKeyId: !Ref MasterKmsKey
KmsDataKeyReusePeriodSeconds: 300
RedrivePolicy:
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
maxReceiveCount: 5
# Dead Letter Queue
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Sub "${AWS::StackName}-dlq-${Environment}"
KmsMasterKeyId: !Ref MasterKmsKey
# Encrypted SNS Topic
NotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${AWS::StackName}-notifications-${Environment}"
KmsMasterKeyId: !Ref MasterKmsKey
# Security Groups
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-app-sg"
GroupDescription: "Application security group"
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref ALBSecurityGroup
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-db-sg"
GroupDescription: "Database security group"
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
# VPC Endpoints per accesso privato
S3VPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcEndpointType: Gateway
RouteTableIds: !Ref PrivateRouteTableIds
SecretsManagerVPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: !Sub "com.amazonaws.${AWS::Region}.secretsmanager"
VpcEndpointType: Interface
Subnets: !Ref PrivateSubnetIds
SecurityGroups:
- !Ref ApplicationSecurityGroup
Outputs:
DataBucketName:
Description: Name of the encrypted data bucket
Value: !Ref DataBucket
DatabaseEndpoint:
Description: Database connection endpoint
Value: !GetAtt DatabaseInstance.Endpoint.Address
CacheEndpoint:
Description: Redis cluster endpoint
Value: !GetAtt CacheCluster.PrimaryEndPoint.Address
TableName:
Description: DynamoDB table name
Value: !Ref DataTable
QueueUrl:
Description: SQS queue URL
Value: !Ref ProcessingQueueAWS CloudFormation Security - Reference
This reference guide contains detailed information about AWS CloudFormation resources and configurations for infrastructure security, encryption, and secrets management.
AWS::KMS::Key
Creates a customer master key (CMK) in AWS Key Management Service.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| KeyPolicy | Json | Yes | The key policy document |
| Description | String | No | Description of the key |
| KeyUsage | String | No | Key usage (ENCRYPT_DECRYPT or SIGN_VERIFY) |
| EnableKeyRotation | Boolean | No | Enable automatic key rotation |
| PendingWindowInDays | Integer | No | Pending deletion window (7-30 days) |
| MultiRegion | Boolean | No | Enable multi-region key |
Key Policy Structure
KeyPolicy:
Version: "2012-10-17"
Id: "key-policy-identifier"
Statement:
- Sid: "EnableIAMPolicies"
Effect: Allow
Principal:
AWS: "arn:aws:iam::account-id:role/role-name"
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: "*"
- Sid: "AllowCryptographicOperations"
Effect: Allow
Principal:
AWS: "arn:aws:iam::account-id:role/role-name"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
- kms:ReEncrypt*
Resource: "*"Key Policy Conditions
Conditions:
- StringEquals:
aws:PrincipalOrgID: "o-organization-id"
- StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
- ArnEquals:
aws:SourceArn: "arn:aws:lambda:region:account:function:function-name"Example
Resources:
SecureKmsKey:
Type: AWS::KMS::Key
Properties:
Description: "KMS Key for sensitive data encryption"
KeyPolicy:
Version: "2012-10-17"
Id: "secure-key-policy"
Statement:
- Sid: "EnableIAMPolicies"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AdminRole"
Action: kms:*
Resource: "*"
- Sid: "AllowCryptographicOperations"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AppRole"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
- Sid: "AllowAWSServiceAccess"
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey*
Resource: "*"
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
EnableKeyRotation: true
PendingWindowInDays: 30Attributes
| Attribute | Description |
|---|---|
| Arn | The ARN of the key |
| KeyId | The unique identifier of the key |
AWS::KMS::Alias
Creates an alias for a KMS key.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| AliasName | String | Yes | The alias name (must start with 'alias/') |
| TargetKeyId | String | Yes | The key ID to associate with the alias |
Example
Resources:
KmsKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub "alias/application-${Environment}"
TargetKeyId: !Ref SecureKmsKeyAWS::SecretsManager::Secret
Creates a secret in AWS Secrets Manager.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| Name | String | No | The name of the secret |
| Description | String | No | Description of the secret |
| SecretString | String | No | The secret value |
| SecretStringTemplate | String | No | JSON template for secret values |
| GenerateSecretString | SecretGenerator | No | Generate secret automatically |
| KmsKeyId | String | No | KMS key ID for encryption |
| RotationRules | RotationRules | No | Automatic rotation configuration |
| RotationLambdaARN | String | No | Lambda function ARN for rotation |
| ResourcePolicy | Json | No | Resource-based policy |
SecretGenerator Structure
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateSecretKey: "password"
PasswordLength: 32
ExcludeCharacters: '"@/\\'
ExcludeLowercase: false
ExcludeUppercase: false
ExcludeNumbers: false
ExcludePunctuation: trueRotationRules Structure
RotationRules:
AutomaticallyAfterDays: 30
Duration: 8h
ScheduleExpression: "rate(30 days)"Resource Policy Example
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowLambdaAccess"
Effect: Allow
Principal:
AWS: "arn:aws:iam::account-id:role/LambdaRole"
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Environment: "production"Example
Resources:
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}"
}
KmsKeyId: !Ref SecretsKmsKeyId
RotationRules:
AutomaticallyAfterDays: 30
ResourcePolicy:
Version: "2012-10-17"
Statement:
- Sid: "AllowAppAccess"
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/AppRole"
Action:
- secretsmanager:GetSecretValue
Resource: "*"Attributes
| Attribute | Description |
|---|---|
| Arn | The ARN of the secret |
| Name | The name of the secret |
AWS::SSM::Parameter
Creates a parameter in AWS Systems Manager Parameter Store.
Parameter Types
| Type | Description |
|---|---|
| String | Plain text parameter |
| StringList | Comma-separated list |
| SecureString | Encrypted parameter |
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| Name | String | No | The parameter name |
| Type | String | Yes | Parameter type (String, StringList, SecureString) |
| Value | String | Yes | The parameter value |
| Description | String | No | Description of the parameter |
| AllowedPattern | String | No | Regex pattern for validation |
| NoEcho | Boolean | No | Hide value in console |
Example
Parameters:
DBCredentials:
Type: AWS::SSM::Parameter::Value<SecureString>
NoEcho: true
Description: Database credentials
Value: "/app/database/credentials"
ApiEndpoint:
Type: AWS::SSM::Parameter::Value<String>
Description: API endpoint URL
Value: "https://api.example.com"
AllowedIPs:
Type: AWS::SSM::Parameter::Value<StringList>
Description: List of allowed IP addresses
Value: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
Resources:
CustomParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub "/${AWS::StackName}/custom/setting"
Type: SecureString
Value: "sensitive-value"
Description: "Custom secure parameter"
AllowedPattern: "^[a-zA-Z0-9_-]+$"AWS::IAM::Role
Creates an IAM role for AWS services or cross-account access.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| RoleName | String | No | The name of the role |
| AssumeRolePolicyDocument | Json | Yes | Trust policy document |
| ManagedPolicyArns | List | No | AWS managed policies |
| Policies | List | No | Inline policies |
| PermissionsBoundary | String | No | Permissions boundary ARN |
| MaxSessionDuration | Integer | No | Max session duration (3600-43200) |
| Description | String | No | Description of the role |
Assume Role Policy Examples
# Service role for Lambda
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
# Cross-account role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub "arn:aws:iam::account-id:root"
Action: sts:AssumeRole
Condition:
StringEquals:
aws:PrincipalAccount: "trusted-account-id"
Bool:
aws:MultiFactorAuthPresent: trueExample
Resources:
SecureRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-secure-role"
Description: "IAM role with least privilege"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
MaxSessionDuration: 3600
PermissionsBoundary: !Ref PermissionsBoundary
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: SecretsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref SecretArnAttributes
| Attribute | Description |
|---|---|
| Arn | The ARN of the role |
| RoleName | The name of the role |
AWS::EC2::SecurityGroup
Creates a security group for VPC resources.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| GroupName | String | No | The name of the security group |
| GroupDescription | String | Yes | Description of the group |
| VpcId | String | No | VPC ID (required for non-default VPC) |
| SecurityGroupIngress | List | No | Inbound rules |
| SecurityGroupEgress | List | No | Outbound rules |
| Tags | List | No | Tags for the group |
Security Group Rule Structure
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS from internet"
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: "PostgreSQL from app tier"Example
Resources:
SecureSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-secure-sg"
GroupDescription: "Security group with restricted rules"
VpcId: !Ref VPCId
Tags:
- Key: Environment
Value: !Ref Environment
SecurityGroupIngress:
# HTTPS from ALB
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: "HTTPS from ALB"
# SSH from bastion only
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "SSH from bastion host"
SecurityGroupEgress:
# HTTPS outbound
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS outbound"AWS::CertificateManager::Certificate
Creates an SSL/TLS certificate in AWS Certificate Manager.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| DomainName | String | Yes | Primary domain name |
| SubjectAlternativeNames | List | No | Additional domain names |
| DomainValidationOptions | List | No | Domain validation settings |
| ValidationMethod | String | No | Validation method (DNS or EMAIL) |
| Options | CertificateOptions | No | Additional certificate options |
CertificateOptions Structure
Options:
CertificateTransparencyLoggingPreference: ENABLED | DISABLEDExample
Resources:
SSLCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: example.com
SubjectAlternativeNames:
- "*.example.com"
- "api.example.com"
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: example.com
Route53HostedZoneId: !Ref HostedZoneId
- DomainName: "*.example.com"
Route53HostedZoneId: !Ref HostedZoneId
Options:
CertificateTransparencyLoggingPreference: ENABLEDAttributes
| Attribute | Description |
|---|---|
| Arn | The ARN of the certificate |
| DomainName | The primary domain name |
AWS::WAFv2::WebACL
Creates a Web ACL for AWS WAF.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| Name | String | Yes | The name of the Web ACL |
| Scope | String | Yes | CLOUDFRONT or REGIONAL |
| DefaultAction | Action | Yes | Default action for unmatched requests |
| Rules | List | No | List of rules |
| VisibilityConfig | VisibilityConfig | Yes | CloudWatch metrics configuration |
Rule Structure
Rules:
- Name: "RateLimitRule"
Priority: 1
Statement:
RateBasedStatement:
Limit: 2000
EvaluationWindowSec: 60
AggregationKeyType: IP
Action:
Block:
CustomResponse:
ResponseCode: 429
ResponseBody: "Too many requests"
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: RateLimitRuleAction Types
Action:
Block:
CustomResponse:
ResponseCode: 403
ResponseBody: "Request blocked"
ResponseHeaders:
- Name: X-Frame-Options
Value: DENY
Allow:
CustomRequestHandling:
InsertHeaders:
- Name: X-Content-Type-Options
Value: nosniff
Count: {}Example
Resources:
SecureWebACL:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub "${AWS::StackName}-waf"
Scope: REGIONAL
DefaultAction:
Allow: {}
Rules:
- Name: BlockSQLInjection
Priority: 1
Statement:
SqliMatchStatement:
FieldToMatch:
Body:
OversizeHandling: CONTINUE
SensitivityLevel: HIGH
Action:
Block:
CustomResponse:
ResponseCode: 403
ResponseBody: "Request blocked - SQL injection detected"
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: ManagedRuleSet
Priority: 3
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesCommonRuleSet
Version: Version_1.0
ExcludedRules:
- Name: SizeRestrictions_BODY
Action:
Count: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: ManagedRuleSet
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${AWS::StackName}-WAF"
SampledRequestsEnabled: trueAWS::Logs::LogGroup
Creates a CloudWatch Logs log group.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| LogGroupName | String | Yes | The name of the log group |
| RetentionInDays | Integer | No | Retention period in days |
| KmsKeyId | String | No | KMS key ID for encryption |
| LogGroupClass | String | No | Log group class (STANDARD or INFREQUENT_ACCESS) |
Example
Resources:
EncryptedLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/${AWS::StackName}/application"
RetentionInDays: 30
KmsKeyId: !Ref ApplicationKmsKey
LogGroupClass: STANDARDAttributes
| Attribute | Description |
|---|---|
| Arn | The ARN of the log group |
AWS::EC2::VPCEndpoint
Creates a VPC endpoint for private connectivity to AWS services.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| VpcId | String | Yes | The VPC ID |
| ServiceName | String | Yes | The service name |
| VpcEndpointType | String | No | Interface or Gateway |
| Subnets | List | Cond | Subnets for interface endpoints |
| SecurityGroups | List | Cond | Security groups for interface endpoints |
| PrivateDnsEnabled | Boolean | No | Enable private DNS |
Example
Resources:
SecretsManagerEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: !Sub "com.amazonaws.${AWS::Region}.secretsmanager"
VpcEndpointType: Interface
Subnets:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
SecurityGroups:
- !Ref AppSecurityGroup
PrivateDnsEnabled: true
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PrivateRouteTable1
- !Ref PrivateRouteTable2Intrinsic Functions per Security
!GetAtt for Security Resources
# Get KMS key ARN
KmsKeyArn: !GetAtt ApplicationKmsKey.Arn
# Get secret ARN
SecretArn: !Ref DatabaseSecret
# Get security group ID
SecurityGroupId: !Ref ApplicationSecurityGroup
# Get log group ARN
LogGroupArn: !GetAtt EncryptedLogGroup.Arn!Sub with AWS Variables
# Construct ARN with account and region
RoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/${RoleName}"
# Construct secret name
SecretName: !Sub "${AWS::StackName}/${Service}/${Environment}"!ImportValue per Cross-Stack References
# Import from network stack
VPCId: !ImportValue !Sub "${NetworkStackName}-VPCId"
# Import with function
SecurityGroupId: !ImportValue
Fn::Sub: "${NetworkStackName}-SecurityGroupId"Condition Functions per Security
Conditions:
IsProduction: !Equals [!Ref Environment, production]
EnableDetailedMonitoring: !Equals [!Ref Environment, production]
UseCustomKMS: !Not [!Equals [!Ref KMSKeyId, ""]]
EnableCrossAccount: !Equals [!Ref EnableCrossAccountAccess, true]
Resources:
# Conditional KMS key
ConditionalKmsKey:
Type: AWS::KMS::Key
Condition: UseCustomKMS
Properties:
Description: "Conditional KMS key"
KeyPolicy: !Ref KeyPolicy
# Conditional encryption
EncryptedResource:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: !If [UseCustomKMS, aws:kms, AES256]
KMSMasterKeyID: !If [UseCustomKMS, !Ref CustomKmsKey, !Ref AWS::NoValue]KMS Key States
| State | Description |
|---|---|
| Enabled | Key is available for use |
| Disabled | Key is not available for use |
| PendingDeletion | Key is scheduled for deletion |
| PendingImport | Key is being imported |
| Unavailable | Key is unavailable |
Secrets Manager Limits
| Resource | Limit |
|---|---|
| Secrets per account | 500,000 |
| Secret size | 65,536 bytes |
| Version stages | 20 per version |
| Rotation attempts | 3 per day |
Security Group Limits
| Resource | Limit |
|---|---|
| Rules per security group | 60 inbound + 60 outbound |
| Security groups per VPC | 2,500 |
| Security groups per instance | 5 |
Common Security Tags
Resources:
SecureResource:
Type: AWS::KMS::Key
Properties:
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Project
Value: !Ref ProjectName
- Key: ManagedBy
Value: CloudFormation
- Key: SecurityClassification
Value: "confidential"
- Key: Compliance
Value: "SOC2,ISO27001"
- Key: Owner
Value: "security-team@example.com"