aws-cloudformation-security

AWS CloudFormation Security

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "aws-cloudformation-security" with this command: npx skills add giuseppe-trisciuoglio/developer-kit/giuseppe-trisciuoglio-developer-kit-aws-cloudformation-security

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:

  • Define Encryption Keys: Create KMS keys for data encryption

  • Set Up Secrets: Use Secrets Manager for credentials and API keys

  • Configure Secure Parameters: Use SSM Parameter Store with encryption

  • Implement IAM Policies: Apply least privilege principles

  • Create Security Groups: Configure network access controls

  • Set Up TLS Certificates: Use ACM for SSL/TLS certificates

  • Enable Encryption: Configure encryption for storage and transit

  • 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 KmsKey

Example 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 service

For 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 Environment

AWS 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 AllowedResourceArns

IAM 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: true

TLS/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: BUFFERED

Parameter 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 production

Outputs 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: true

Best 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

  • AWS KMS Documentation

  • AWS Secrets Manager

  • IAM Best Practices

  • Security Groups

  • AWS WAF

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-admin

Termination 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 Environment

Drift 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: CloudFormation

Change 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-changeset

Stack 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

Enable Termination Protection

  • Always enable for production stacks

  • Use as a safety mechanism against accidental deletion

  • Requires manual disabling before deletion

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

Implement Drift Detection

  • Run drift detection regularly (daily for production)

  • Alert on any drift detection

  • Investigate and remediate drift immediately

Use Change Sets

  • Always use Change Sets for production updates

  • Review changes before execution

  • Use descriptive change set names

Configure Rollback Triggers

  • Set up CloudWatch alarms for critical metrics

  • Configure monitoring time to allow stabilization

  • Test rollback triggers in non-production first

Implement Change Management

  • Require approval for production changes

  • Use CodePipeline with manual approval gates

  • Document all changes in change log

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

spring-boot-security-jwt

No summary provided by upstream source.

Repository SourceNeeds Review
Security

unit-test-security-authorization

No summary provided by upstream source.

Repository SourceNeeds Review
Security

typescript-security-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review