🗂

CloudFormation スタック削除保護の自動設定

に公開

やりたいこと

CloudFormationの変更セットを削除する際に誤ってスタックごと削除することが多いので自動で削除保護を有効化したい

構成図

Configで検知をしてリソース情報をSystemsManagerの自動修復機能を使用して削除保護を付与する構成

CloudFormation

sampleとしてcfnのテンプレートを載せておきます

注意点

  • Timestampはcfnを更新した際にSSMドキュメント名が重複するとデプロイ失敗するのでつけてます
  • SSMドキュメントのPython versionは適宜更新してください
AWSTemplateFormatVersion: "2010-09-09"
Description: "CloudFormation Auto Protection"
Parameters:
  Timestamp:
    Type: String
Resources:
  # ------------------------------------------------------------#
  # IAM : IAM Role & Policy for CloudFormation Termination Protection
  # ------------------------------------------------------------#
  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "cfn-termination-protection-role"
      AssumeRolePolicyDocument:
        {
          "Version": "2012-10-17",
          "Statement":
            [
              {
                "Effect": "Allow",
                "Principal": { "Service": "ssm.amazonaws.com" },
                "Action": "sts:AssumeRole",
              },
            ],
        }
  IAMPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: "cfn-termination-protection-policy"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AWSConfig"      
            Effect: Allow
            Action:
              - config:ListDiscoveredResources
              - ssm:GetAutomationExecution
              - iam:DeleteAccessKey
              - ssm:StartAutomationExecution
              - iam:GetAccessKeyLastUsed
              - iam:UpdateAccessKey
              - iam:GetUser
              - iam:GetLoginProfile
              - iam:DeleteLoginProfile
              - iam:ListAccessKeys
            Resource:
              - !Sub "arn:aws:config:*:${AWS::AccountId}:config-rule/*"
          - Sid: "AWSCloudFormation"      
            Effect: Allow
            Action:
              - cloudformation:UpdateTerminationProtection
            Resource:
              - !Sub "arn:aws:cloudformation:*:${AWS::AccountId}:stack/*"
      Roles:
        - !Ref IAMRole
  # ------------------------------------------------------------#
  # Config : Config Rule for CloudFormation Termination Protection
  # ------------------------------------------------------------#
  ConfigRule:
    Type: "AWS::Config::ConfigRule"
    Properties:
      ConfigRuleName: "cfn-termination-protection-config-rule"
      Scope: 
        ComplianceResourceTypes: 
          - "AWS::CloudFormation::Stack"
      Source: 
        Owner: "CUSTOM_POLICY"
        SourceDetails: 
          - EventSource: "aws.config"
            MessageType: "ConfigurationItemChangeNotification"
          - EventSource: "aws.config"
            MessageType: "OversizedConfigurationItemChangeNotification"
        CustomPolicyDetails: 
          PolicyRuntime: "guard-2.x.x"
          PolicyText: "rule checkcompliance when resourceType == 'AWS::CloudFormation::Stack' {configuration.enableTerminationProtection == true}"
          EnableDebugLogDelivery: true
  ConfigRemediationConfiguration:
    Type: "AWS::Config::RemediationConfiguration"
    Properties:
      Automatic: true
      MaximumAutomaticAttempts: 5
      RetryAttemptSeconds: 60
      ConfigRuleName: !Ref ConfigRule
      TargetId: !Ref SSMDocument
      TargetType: "SSM_DOCUMENT"
      Parameters: 
        AutomationAssumeRole: 
          StaticValue: 
            Values: 
              - !Sub "arn:aws:iam::${AWS::AccountId}:role/cfn-termination-protection-role"
        stackNameArn: 
          ResourceValue: 
            Value: "RESOURCE_ID"
  # ------------------------------------------------------------#
  # SystemsManager : SSM Document for CloudFormation Termination Protection
  # ------------------------------------------------------------#
  SSMDocument:
    Type: "AWS::SSM::Document"
    Properties:
      Name: !Sub "cfn-termination-protection-ssm-document-${Timestamp}"
      DocumentType: "Automation"
      DocumentFormat: YAML
      Content:
        schemaVersion: '0.3'
        parameters:
          AutomationAssumeRole:
            type: String
          stackNameArn:
            type: String
        assumeRole: '{{ AutomationAssumeRole }}'
        mainSteps:
          - name: EnableStackProtection
            action: aws:executeScript
            isEnd: true
            inputs:
              Runtime: python3.11
              Handler: script_handler
              InputPayload:
                stackNameArn: '{{ stackNameArn }}'
              Script: |-
                import boto3
                import re

                def script_handler(events, context):
                  stackNameArn = events['stackNameArn']
                  client = boto3.client('cloudformation')

                  stackName = re.search(r'stack/([^/]+)', stackNameArn)
                  response = client.update_termination_protection(
                    EnableTerminationProtection=True,
                    StackName=stackName.group(1)
                  )

Discussion