home / skills / giuseppe-trisciuoglio / developer-kit / aws-cloudformation-security

This skill helps you create secure AWS CloudFormation templates by applying best practices for encryption, secrets management, and defense-in-depth.

npx playbooks add skill giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-security

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
48.6 KB
---
name: aws-cloudformation-security
description: Provides AWS CloudFormation patterns for infrastructure security, secrets management, encryption, and secure data handling. Use when creating secure CloudFormation templates with AWS Secrets Manager, KMS encryption, secure parameters, IAM policies, VPC security groups, TLS/SSL certificates, and encrypted traffic configurations. Covers template structure, parameter best practices, cross-stack references, and defense-in-depth strategies.
category: aws
tags: [aws, cloudformation, security, kms, secrets-manager, iam, encryption, tls, ssl, vpc, infrastructure, iaac]
version: 1.0.0
allowed-tools: Read, Write, Bash
---

# 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](references/examples.md) file.

## Examples

The following examples demonstrate common security patterns:

### Example 1: KMS Key for Encryption

```yaml
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

```yaml
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

```yaml
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](references/examples.md).

## CloudFormation Template Structure

### Base Template with Security Section

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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

```yaml
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](https://docs.aws.amazon.com/kms/latest/developerguide/)
- [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/)
- [IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
- [Security Groups](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html)
- [AWS WAF](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html)

## Additional Files

For complete details on resources and their properties, see:
- [REFERENCE.md](references/reference.md) - Detailed reference guide for all CloudFormation security resources
- [EXAMPLES.md](references/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.

```yaml
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.

```yaml
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.

```yaml
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

```python
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.

```yaml
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

```bash
#!/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

```yaml
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

Overview

This skill provides reusable CloudFormation patterns to build secure AWS infrastructure focused on encryption, secrets management, and hardened access controls. It bundles best-practice templates and patterns for KMS, Secrets Manager, SSM SecureString, IAM least-privilege roles, VPC security, and TLS configurations. Use it to embed defense-in-depth into templates and to standardize secure parameter and cross-stack reference handling.

How this skill works

The skill supplies CloudFormation resource patterns and guidance that you can paste or adapt into stacks: KMS keys and aliases, S3/RDS encryption settings, Secrets Manager secrets with rotation and resource policies, and SSM SecureString parameters. It also demonstrates IAM role and permission boundary patterns, secure security group configurations, ACM certificate usage, and logging/monitoring hooks like CloudTrail. Examples show how resources reference keys and secrets securely and how to structure templates with parameters, mappings, conditions, and outputs for different environments.

When to use it

  • When you need encryption at-rest and in-transit in CloudFormation templates
  • When managing credentials with AWS Secrets Manager and enabling rotation
  • When creating KMS keys and applying them to S3, RDS, and other services
  • When enforcing least-privilege IAM roles and permission boundaries
  • When designing secure network access with security groups and TLS certificates
  • When implementing secure cross-stack references and production-ready parameter handling

Best practices

  • Define dedicated KMS keys per application or purpose and enable key rotation
  • Store credentials in Secrets Manager or SSM SecureString; avoid embedding secrets in templates
  • Apply least-privilege IAM policies and use permission boundaries for risky roles
  • Block public S3 access, enable bucket encryption with KMS, and version critical buckets
  • Use CloudTrail and resource logging to monitor key, secret, and API access
  • Model parameters and mappings for environments to avoid risky defaults in production

Example use cases

  • Create a KMS-backed S3 bucket template with enforced public access blocking and lifecycle rules
  • Provision Secrets Manager secrets with rotation Lambda and resource-based policies for Lambda/EC2 access
  • Define SSM SecureString parameters consumed by Lambda functions with NoEcho in parameterized stacks
  • Deploy IAM roles with tight assume-role conditions and a permissions boundary for serverless workloads
  • Compose a base template with environment mappings, security parameter groups, and outputs for cross-stack secure references

FAQ

Can I rotate Secrets Manager secrets automatically with these patterns?

Yes. The patterns include properties and examples to configure automatic rotation using a rotation Lambda and RotationRules.AutomaticallyAfterDays.

How do I avoid exposing secrets in stack outputs?

Do not output secret values. Use NoEcho for parameters, store secrets in Secrets Manager/SSM SecureString, and reference them by ARN or path instead of printing values.