10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

aws-nukeとBubbleで「リソース全削除ボタン」を作る

Last updated at Posted at 2024-05-06

はじめに

お疲れ様です。yuki_inkです。
AWSのリソース全部消し飛ばせるボタンがあったら面白いなぁ という過激思想を抑えられず、ゴールデンウィークの自由研究として「リソース全削除ボタン」を作ってみました。

元ネタはこちら。2つの偉大な発表から着想を得たものです。

ゴールと構成概略

ゴール:Webアプリのボタンを押すとaws-nukeがキックされ、AWS上のリソースが削除される。
構成図は以下の通り。
image.png

"Webアプリのボタン"
今回はBubbleで実装しました。ノーコード万歳。

"aws-nukeがキックされ、AWS上のリソースが削除される"
そもそもaws-nukeとは。

aws-nukeとは、アカウント内のリソースを強制的に削除するコマンドラインツールです。
設定ファイルを用意し、コマンドを実行するだけで強制的にリソースを削除できます。
かなり強力なツールなので使用する際は細心の注意を払う必要がありますが、適切な使い方をすればとても便利ツールです。
利用するための料金はかかりません。

(出展)aws-nukeで作るマルチアカウントのお掃除機能

aws-nukeはAWS非公式のツールながら、AWSからサンプルのCloudFormationテンプレートが提供されています。
今回はそれを利用し、Step FunctionsとCodeBuildを使った構成としました。

BubbleとStep Functionsの連係のため、間にAPI Gatewayを挟み、Bubbleのプラグイン「API Connector」を利用してPOSTを投げる形にしました。

やったこと

以下について、それぞれ記載します。

  1. aws-nuke実行環境構築(Step Functions・CodeBuildなど)
  2. API Gateway設定
  3. Webアプリ作成(Bubble)

1. aws-nuke実行環境構築

1.1. 事前準備

AWSから提供されているサンプルファイルをダウンロードします。

image.png

zipを解凍すると、以下のようなフォルダ構成になっていました。

aws-nuke-account-cleanser-example-main
│  CODE_OF_CONDUCT.md
│  CONTRIBUTING.md
│  LICENSE
│  nuke-cfn-stack.yaml
│  README.md
│
├─config
│      nuke_config_update.py
│      nuke_generic_config.yaml
│
└─images
        architecture-overview.png

1.2. nuke-cfn-stack.yaml の修正

nuke-cfn-stack.yaml には以下の要素が含まれています。

  • Step Functionsステートマシン
  • CodeBuildプロジェクト
  • EventBridgeルール
  • Step Functions用のIAMロール・IAMポリシー
  • CodeBuild用のIAMロール・IAMポリシー
  • EventBridge用のIAMロール・IAMポリシー
  • aws-nuke用のIAMロール・IAMポリシー
  • S3バケット
  • SNSトピック

image.png

今回、StepFunctionsはAPI Gatewayから呼び出すようにするので、EventBridgeルール EventBridgeNukeSchedule とEventBridge用のIAMロール・IAMポリシー EventBridgeNukeScheduleRoleの箇所はコメントアウトしました。

また、SNSトピック NukeEmailTopic の箇所でサブスクリプションも作成されており、デフォルトで test@test.com が入っていますが、ここで自分のメアドに変更しておきます。

修正後の nuke-cfn-stack.yaml
nuke-cfn-stack.yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: >
  This CFN Template creates a StepFunction state machine definition , to invoke a CodeBuild Project and 
  associated resources for setting up a single-account script that cleanses the resources across supplied regions
  via AWS-Nuke. It also creates the role for the nuke cleanser which is used for configuring the credentials
  for aws-nuke binary to cleanse resources within that account/region.

Parameters:
  BucketName:
    Description: The name of the bucket where the nuke binary and config files are stored
    Default: "nuke-account-cleanser-config"
    Type: String
  NukeCleanserRoleName:
    Description: The name of the Nuke Role to be assumed within each account, providing permissions to cleanse the account(s)
    Type: String
    Default: 'nuke-auto-account-cleanser'
  IAMPath:
    Type: String
    Default: /
    Description: IAM Path
  AWSNukeDryRunFlag:
    Description: The dry run flag to run for the aws-nuke. By default it is set to True which will not delete any resources.
    Type: String
    Default: 'true'
  AWSNukeVersion:
    Description: The aws-nuke latest version to be used from internal artifactory/S3. Make sure to check the latest releases
     and any resource additions added. As you update the version, you will have to handle filtering any new resources that gets
     updated with that new version.
    Type: String
    Default: 2.21.2
  NukeTopicName:
    Description: The SNS Topic name to publish the nuke output logs email
    Type: String
    Default: nuke-cleanser-notify-topic
  Owner:
    Type: String
    Default: OpsAdmin
    Description: The Owner of the account to be used for tagging purpose

Resources:
  NukeAccountCleanserPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: NukeAccountCleanser
      PolicyDocument:
        Statement:
          - Action: # Make sure to restrict this to the services/permissions as needed for your accounts
              - access-analyzer:*
              - autoscaling:*
              - aws-portal:*
              - budgets:*
              - cloudtrail:*
              - cloudwatch:*
              - config:*
              - ec2:*
              - ec2messages:*
              - elasticloadbalancing:*
              - eks:*
              - elasticache:*
              - events:*
              - firehose:*
              - guardduty:*
              - iam:*
              - inspector:*
              - kinesis:*
              - kms:*
              - lambda:*
              - logs:*
              - organizations:*
              - pricing:*
              - s3:*
              - secretsmanager:*
              - securityhub:*
              - sns:*
              - sqs:*
              - ssm:*
              - ssmmessages:*
              - sts:*
              - support:*
              - tag:*
              - trustedadvisor:*
              - waf-regional:*
              - wafv2:*
              - cloudformation:*
            Effect: Allow
            Resource: "*"
            Sid: WhitelistedServices
        Version: "2012-10-17"
      Description: Managed policy for nuke account cleansing
      Path:
        Ref: IAMPath
  NukeAccountCleanserRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref NukeCleanserRoleName
      Description: Nuke Auto account cleanser role for Dev/Sandbox accounts
      MaxSessionDuration: 7200
      Tags: 
        - Key: privileged
          Value: 'true'
        - Key: DoNotNuke
          Value: 'True'
        - Key: description
          Value: 'PrivilegedReadWrite:auto-account-cleanser-role'
        - Key: owner
          Value: !Ref Owner
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
              - !GetAtt NukeCodeBuildProjectRole.Arn
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Ref: NukeAccountCleanserPolicy
      Path:
        Ref: IAMPath

#  EventBridgeNukeScheduleRole:
#    Type: AWS::IAM::Role
#    Properties:
#      RoleName: !Sub EventBridgeNukeSchedule-${AWS::StackName}
#      AssumeRolePolicyDocument:
#        Version: 2012-10-17
#        Statement:
#          - Effect: Allow
#            Principal:
#              Service: events.amazonaws.com
#            Action: sts:AssumeRole
#      Tags:
#        - Key: DoNotNuke
#          Value: 'True'
#        - Key: owner
#          Value: !Ref Owner
#      Policies:
#        -
#         PolicyName: EventBridgeNukeStateMachineExecutionPolicy
#         PolicyDocument:
#           Version: 2012-10-17
#           Statement:
#              -
#                Effect: Allow
#                Action: states:StartExecution
#                Resource: !Ref NukeStepFunction

  NukeCodeBuildProjectRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub NukeCodeBuildProject-${AWS::StackName}
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner
      Policies:
        -
          PolicyName: NukeCodeBuildLogsPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                  - logs:FilterLogEvents
                Resource:
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:AccountNuker-${AWS::StackName}
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:AccountNuker-${AWS::StackName}:*
        -
         PolicyName: AssumeNukePolicy
         PolicyDocument:
           Version: 2012-10-17
           Statement:
              -
                Effect: Allow
                Action: sts:AssumeRole
                Resource: !Sub arn:aws:iam::*:role/${NukeCleanserRoleName}
        -
          PolicyName: NukeListOUAccounts
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action: organizations:ListAccountsForParent
                Resource: "*"
        -
          PolicyName: S3BucketReadOnly 
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action:
                  - s3:Get*
                  - s3:List*
                Resource:
                  - !Sub arn:aws:s3:::${NukeS3Bucket}
                  - !Sub arn:aws:s3:::${NukeS3Bucket}/*
        -
          PolicyName: SNSPublishPolicy 
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action:
                  - sns:ListTagsForResource
                  - sns:ListSubscriptionsByTopic
                  - sns:GetTopicAttributes
                  - sns:Publish
                Resource:
                  - !Ref NukeEmailTopic

  NukeCodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      BadgeEnabled: false
      Description: Builds a container to run AWS-Nuke for all accounts within the specified account/regions
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner
      Environment:
        ComputeType: BUILD_GENERAL1_2XLARGE
        Image: aws/codebuild/docker:18.09.0
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: true
        Type: LINUX_CONTAINER
        EnvironmentVariables:
        - Name: AWS_NukeDryRun
          Type: PLAINTEXT
          Value: !Ref AWSNukeDryRunFlag
        - Name: AWS_NukeVersion
          Type: PLAINTEXT
          Value: !Ref AWSNukeVersion
        - Name: Publish_TopicArn
          Type: PLAINTEXT
          Value: !Ref NukeEmailTopic
        - Name: NukeS3Bucket
          Type: PLAINTEXT
          Value: !Ref 'NukeS3Bucket'
        - Name: NukeAssumeRoleArn
          Type: PLAINTEXT
          Value: !GetAtt NukeAccountCleanserRole.Arn
        - Name: NukeCodeBuildProjectName
          Type: PLAINTEXT
          Value: !Sub "AccountNuker-${AWS::StackName}"
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Sub "AccountNuker-${AWS::StackName}"
          Status: ENABLED
      Name: !Sub "AccountNuker-${AWS::StackName}"
      ServiceRole: !GetAtt NukeCodeBuildProjectRole.Arn
      TimeoutInMinutes: 120
      Source:
        BuildSpec: |
          version: 0.2
          phases:
            install:
              on-failure: ABORT
              commands:
                - export AWS_NUKE_VERSION=$AWS_NukeVersion
                - apt-get install -y wget
                - apt-get install jq
                - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate
                - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz
                - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64
                - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke
                - aws-nuke version
                - echo "Setting aws cli profile with config file for role assumption using metadata"
                - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}
                - aws configure set profile.nuke.credential_source "EcsContainer"
                - export AWS_PROFILE=nuke
                - export AWS_DEFAULT_PROFILE=nuke
                - export AWS_SDK_LOAD_CONFIG=1
                - echo "Getting 12-digit ID of this account"
                - account_id=$(aws sts get-caller-identity |jq -r ".Account");
            build:
              on-failure: CONTINUE
              commands:
                - echo " ------------------------------------------------ " >> error_log.txt
                - echo "Getting nuke generic config file from S3";
                - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .
                - echo "Updating the TARGET_REGION in the generic config from the parameter"
                - sed -i "s/TARGET_REGION/$NukeTargetRegion/g" nuke_generic_config.yaml
                - echo "Getting filter/exclusion python script from S3";
                - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .
                - echo "Getting 12-digit ID of this account"
                - account_id=$(aws sts get-caller-identity |jq -r ".Account");
                - echo "Running Config filter/update script";
                - python3 nuke_config_update.py --account $account_id --region "$NukeTargetRegion";
                - echo "Configured nuke_config.yaml";
                - echo "Running Nuke on Account";
                - |
                  if [ "$AWS_NukeDryRun" = "true" ]; then
                    for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --profile nuke 2>&1 |tee -a aws-nuke.log; done
                  elif [ "$AWS_NukeDryRun" = "false" ]; then
                    for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done
                  else
                    echo "Couldn't determine Dryrun flag...exiting"
                    exit 1
                  fi
                - nuke_pid=$!;
                - wait $nuke_pid;
                - echo "Checking if Nuke Process completed for account"
                - |
                  if cat aws-nuke.log | grep -F "Error: The specified account doesn"; then
                    echo "Nuke errored due to no AWS account alias set up - exiting"
                    cat aws-nuke.log >> error_log.txt
                    exit 1
                  else
                    echo "Nuke completed Successfully - Continuing"
                  fi

            post_build:
              commands:
                - echo $CODEBUILD_BUILD_SUCCEEDING
                - echo "Get current timestamp for naming reports"
                - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))
                - CURR_TIME_UTC=$(date -u)
                - |
                  {
                          echo "  Account Cleansing Process Failed;"
                          echo    ""
                          
                          echo "  ----------------------------------------------------------------"
                          echo "  Summary of the process:"
                          echo "  ----------------------------------------------------------------"
                          echo "  DryRunMode                   : $AWS_NukeDryRun"
                          echo "  Account ID                   : $account_id"
                          echo "  Target Region                : $NukeTargetRegion"
                          echo "  Build State                  : $([ "${CODEBUILD_BUILD_SUCCEEDING}" = "1" ] && echo "JOB SUCCEEDED" || echo "JOB FAILED")"
                          echo "  Build ID                     : ${CODEBUILD_BUILD_ID}"
                          echo "  CodeBuild Project Name       : $NukeCodeBuildProjectName"
                          echo "  Process Start Time           : ${BLD_START_TIME}"
                          echo "  Process End Time             : ${CURR_TIME_UTC}"
                          echo "  Log Stream Path              : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}"
                          echo "  ----------------------------------------------------------------"
                          echo "  ################# Failed Nuke Process - Exiting ###################"
                          echo    ""
                  } >> fail_email_template.txt
                - | 
                  if [ "$CODEBUILD_BUILD_SUCCEEDING" = "0" ]; then 
                    echo " Couldn't process Nuke Cleanser - Exiting " >> fail_email_template.txt
                    cat error_log.txt >> fail_email_template.txt
                    aws sns publish --topic-arn $Publish_TopicArn --message file://fail_email_template.txt --subject "Nuke Account Cleanser Failed in account $account_id and region $NukeTargetRegion"
                    exit 1;
                  fi
                - sleep 120
                - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;
                - CURR_TIME_UTC=$(date -u)
                - | 
                  if [ -z "${LOG_STREAM_NAME}" ]; then
                    echo "Couldn't find the log stream for log events";
                    exit 0;
                  else
                    aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern "removed" --no-interleaved | jq -r .events[].message > log_output.txt;
                    awk '/There are resources in failed state/,/Error: failed/' aws-nuke.log > failure_email_output.txt
                    awk '/Error: failed/,/\n/' failure_email_output.txt > failed_log_output.txt
                  fi
                - |
                  if [ -r log_output.txt ]; then
                    content=$(cat log_output.txt)
                    echo $content
                  elif [ -f "log_output.txt" ]; then
                    echo "The file log_output.txt exists but is not readable to the script."
                  else
                    echo "The file log_output.txt does not exist."
                  fi
                - echo "Publishing Log Ouput to SNS:"
                - sub="Nuke Account Cleanser Succeeded in account "$account_id" and region "$NukeTargetRegion""
                - |
                  {
                          echo "  Account Cleansing Process Completed;"
                          echo    ""
                          
                          echo "  ------------------------------------------------------------------"
                          echo "  Summary of the process:"
                          echo "  ------------------------------------------------------------------"
                          echo "  DryRunMode                   : $AWS_NukeDryRun"
                          echo "  Account ID                   : $account_id"
                          echo "  Target Region                : $NukeTargetRegion"
                          echo "  Build State                  : $([ "${CODEBUILD_BUILD_SUCCEEDING}" = "1" ] && echo "JOB SUCCEEDED" || echo "JOB FAILED")"
                          echo "  Build ID                     : ${CODEBUILD_BUILD_ID}"
                          echo "  CodeBuild Project Name       : $NukeCodeBuildProjectName"
                          echo "  Process Start Time           : ${BLD_START_TIME}"
                          echo "  Process End Time             : ${CURR_TIME_UTC}"
                          echo "  Log Stream Path              : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}"
                          echo "  ------------------------------------------------------------------"
                          echo "  ################### Nuke Cleanser Logs ####################"
                          echo    ""
                  } >> email_template.txt

                - cat aws-nuke.log | grep -F "Scan complete:" || echo "No Resources scanned and nukeable yet"
                - echo "Number of Resources that is filtered by config:" >> email_template.txt
                - cat aws-nuke.log | grep -c " - filtered by config" || echo 0 >> email_template.txt
                - echo " ------------------------------------------ " >> email_template.txt
                - |
                  if [ "$AWS_NukeDryRun" = "true" ]; then
                    echo "RESOURCES THAT WOULD BE REMOVED:" >> email_template.txt
                    echo " ----------------------------------------- " >> email_template.txt
                    cat aws-nuke.log | grep -c " - would remove" || echo 0 >> email_template.txt
                    cat aws-nuke.log | grep -F " - would remove" >> email_template.txt || echo "No resources to be removed" >> email_template.txt
                  else
                    echo " FAILED RESOURCES " >> email_template.txt
                    echo " ------------------------------- " >> email_template.txt
                    cat failed_log_output.txt >> email_template.txt
                    echo " SUCCESSFULLY NUKED RESOURCES " >> email_template.txt
                    echo " ------------------------------- " >> email_template.txt
                    cat log_output.txt >> email_template.txt
                  fi
                - aws sns publish --topic-arn $Publish_TopicArn --message file://email_template.txt --subject "$sub"
                - echo "Resources Nukeable:"
                - cat aws-nuke.log | grep -F "Scan complete:" || echo "Nothing Nukeable yet"
                - echo "Total number of Resources that would be removed:"
                - cat aws-nuke.log | grep -c " - would remove" || echo "Nothing would be removed yet"
                - echo "Total number of Resources Deleted:"
                - cat aws-nuke.log | grep -c " - removed" || echo "Nothing deleted yet"
                - echo "List of Resources Deleted today:"
                - cat aws-nuke.log | grep -F " - removed" || echo "Nothing deleted yet"

        Type: NO_SOURCE

#  EventBridgeNukeSchedule:
#    Type: AWS::Events::Rule
#    Properties:
#      Name: !Sub EventBridgeNukeSchedule-${AWS::StackName}
#      Description: Scheduled Event for running AWS Nuke on the target accounts within the specified regions
#      ScheduleExpression: cron(0 7 ? * * *)
#      State:  ENABLED
#      RoleArn: !GetAtt EventBridgeNukeScheduleRole.Arn
      # Update the tags for this manually as the property is not available for all targets
      # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-tag.html
      # Tag:
      #- Key: DoNotNuke
      #  Value: 'True'
      #- Key: owner
      #  Value: !Ref Owner
#      Targets:
#      -
#        Arn: !Ref NukeStepFunction
#        RoleArn: !GetAtt EventBridgeNukeScheduleRole.Arn
#        Id: !GetAtt NukeStepFunction.Name
#        Input:
#          !Sub |-
#            {
#              "InputPayLoad": {
#                "nuke_dry_run": "${AWSNukeDryRunFlag}",
#                "nuke_version": "${AWSNukeVersion}",
#                "region_list": [
#                  "us-west-1",
#                  "us-east-1"
#                ]
#              }
#            }

  # S3 Bucket for storing nuke config yaml
  NukeS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain # should empty the S3 bucket contents if you want to delete the S3 when the stack is deleted
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Join
        - '-'
        - - !Ref BucketName
          - !Ref AWS::AccountId
          - !Ref AWS::Region
          - !Select
            - 0
            - !Split
              - '-'
              - !Select
                - 2
                - !Split
                  - /
                  - !Ref AWS::StackId
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        RestrictPublicBuckets: true
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner

  # S3 Bucket Policy with https secure deny
  NukeS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref NukeS3Bucket
      PolicyDocument:
        Version: '2012-10-17' 
        Statement:
          - Sid: ForceSSLOnlyAccess
            Effect: Deny
            Principal: "*"
            Action: s3:*
            Resource:
              - !Sub arn:aws:s3:::${NukeS3Bucket}
              - !Sub arn:aws:s3:::${NukeS3Bucket}/*
            Condition:
              Bool:
                aws:SecureTransport: 'false'

  NukeEmailTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: NukeTopic
      FifoTopic: false
      KmsMasterKeyId: "alias/aws/sns"
      Subscription:
       - Endpoint: "test@test.com" # Provide your valid email address for receiving notifications
         Protocol: "email"
      TopicName: !Ref NukeTopicName
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner

  ## Role for sample step function
  NukeStepFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: nuke-account-cleanser-codebuild-state-machine-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - !Sub "states.${AWS::Region}.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner
      Path: "/"
      Policies:
        - PolicyName: nuke-account-cleanser-codebuild-state-machine-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - codebuild:StartBuild
                  - codebuild:StartBuild
                  - codebuild:StopBuild
                  - codebuild:StartBuildBatch
                  - codebuild:StopBuildBatch
                  - codebuild:RetryBuild
                  - codebuild:RetryBuildBatch
                  - codebuild:BatchGet*
                  - codebuild:GetResourcePolicy
                  - codebuild:DescribeTestCases
                  - codebuild:DescribeCodeCoverages
                  - codebuild:List*
                Resource:
                  - !GetAtt 'NukeCodeBuildProject.Arn'
              - Effect: Allow
                Action:
                  - events:PutTargets
                  - events:PutRule
                  - events:DescribeRule
                Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventForCodeBuildStartBuildRule
              - Effect: Allow
                Action:
                  - sns:Publish
                Resource:
                  - !Ref NukeEmailTopic
              - Effect: "Allow"
                Action:
                  - states:DescribeStateMachine
                  - states:ListExecutions
                  - states:StartExecution
                  - states:StopExecution
                  - states:DescribeExecution
                Resource:
                  - !Sub "arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:nuke-account-cleanser-codebuild-state-machine"

  NukeStepFunction:
    Type: 'AWS::StepFunctions::StateMachine'
    Properties:
      StateMachineName: nuke-account-cleanser-codebuild-state-machine
      RoleArn: !GetAtt 'NukeStepFunctionRole.Arn'
      DefinitionString:
        Fn::Sub: |-
          {
            "Comment": "AWS Nuke Account Cleanser for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.",
            "StartAt": "StartNukeCodeBuildForEachRegion",
            "States": {
              "StartNukeCodeBuildForEachRegion": {
                "Type": "Map",
                "ItemsPath": "$.InputPayLoad.region_list",
                "Parameters": {
                  "region_id.$": "$$.Map.Item.Value",
                  "nuke_dry_run.$": "$.InputPayLoad.nuke_dry_run",
                  "nuke_version.$": "$.InputPayLoad.nuke_version"
                },
                "Next": "Clean Output and Notify",
                "MaxConcurrency": 0,
                "Iterator": {
                  "StartAt": "Trigger Nuke CodeBuild Job",
                  "States": {
                    "Trigger Nuke CodeBuild Job": {
                      "Type": "Task",
                      "Resource": "arn:aws:states:::codebuild:startBuild.sync",
                      "Parameters": {
                        "ProjectName": "${NukeCodeBuildProject.Arn}",
                        "EnvironmentVariablesOverride": [
                          {
                            "Name": "NukeTargetRegion",
                            "Type": "PLAINTEXT",
                            "Value.$": "$.region_id"
                          },
                          {
                            "Name": "AWS_NukeDryRun",
                            "Type": "PLAINTEXT",
                            "Value.$": "$.nuke_dry_run"
                          },
                          {
                            "Name": "AWS_NukeVersion",
                            "Type": "PLAINTEXT",
                            "Value.$": "$.nuke_version"
                          }
                        ]
                      },
                      "Next": "Check Nuke CodeBuild Job Status",
                      "ResultSelector": {
                        "NukeBuildOutput.$": "$.Build"
                      },
                      "ResultPath": "$.AccountCleanserRegionOutput",
                      "Retry": [
                        {
                          "ErrorEquals": [
                            "States.TaskFailed"
                          ],
                          "BackoffRate": 1,
                          "IntervalSeconds": 1,
                          "MaxAttempts": 1
                        }
                      ],
                      "Catch": [
                        {
                          "ErrorEquals": [
                            "States.ALL"
                          ],
                          "Next": "Nuke Failed",
                          "ResultPath": "$.AccountCleanserRegionOutput"
                        }
                      ]
                    },
                    "Check Nuke CodeBuild Job Status": {
                      "Type": "Choice",
                      "Choices": [
                        {
                          "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus",
                          "StringEquals": "SUCCEEDED",
                          "Next": "Nuke Success"
                        },
                        {
                          "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus",
                          "StringEquals": "FAILED",
                          "Next": "Nuke Failed"
                        }
                      ],
                      "Default": "Nuke Success"
                    },
                    "Nuke Success": {
                      "Type": "Pass",
                      "Parameters": {
                        "Status": "Succeeded",
                        "Region.$": "$.region_id",
                        "CodeBuild Status.$": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus"
                      },
                      "ResultPath": "$.result",
                      "End": true
                    },
                    "Nuke Failed": {
                      "Type": "Pass",
                      "Parameters": {
                        "Status": "Failed",
                        "Region.$": "$.region_id",
                        "CodeBuild Status.$": "States.Format('Nuke Account Cleanser failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountCleanserRegionOutput.Error, $.region_id)"
                      },
                      "ResultPath": "$.result",
                      "End": true
                    }
                  }
                },
                "ResultSelector": {
                  "filteredResult.$": "$..result"
                },
                "ResultPath": "$.NukeFinalMapAllRegionsOutput"
              },
              "Clean Output and Notify": {
                "Type": "Task",
                "Resource": "arn:aws:states:::sns:publish",
                "Parameters": {
                  "Subject": "State Machine for Nuke Account Cleanser completed",
                  "Message.$": "States.Format('Nuke Account Cleanser completed for input payload: \n {}. \n ----------------------------------------- \n Check the summmary of execution below: \n {}', $.InputPayLoad, $.NukeFinalMapAllRegionsOutput.filteredResult)",
                  "TopicArn": "${NukeEmailTopic}"
                },
                "End": true
              }
            }
          }
      Tags:
        - Key: DoNotNuke
          Value: 'True'
        - Key: owner
          Value: !Ref Owner


Outputs:
  NukeTopicArn:
    Description: Arn of SNS Topic used for notifying nuke results in email
    Value: !Ref NukeEmailTopic
  NukeS3BucketValue:
    Description: S3 bucket created with the random generated name
    Value: !Ref NukeS3Bucket

1.3. nuke-cfn-stack.yamlでCloudFormationスタックを作成

マネコンからスタックを作成しました。
スタック名は NukeCleanser としました。
image.png

パラメータにはデフォルトの値が入っているので、必要に応じて値を修正します。
※修正しておいたほうがいいのは Owner の値くらいかなと思います。。

CLIからスタック作成を行いたい場合、こちらの記事が参考になります。

スタックが完成したらOK!
image.png

CloudFormationで作成するIAMロールが最新のaws-nukeに追いついておらず、権限が不足している場合があります。
必要に応じてIAMロールへの権限付与を行ってください。

1.4. nuke_generic_config.yaml の修正

定義ファイル nuke_generic_config.yaml を修正し、aws-nukeで削除するリソースの範囲を指定します。
aws-nukeは定義ファイルで以下の要素を指定可能で、「安全装置」がちゃんとしている印象です。

  • regions: 対象のリージョンを指定(必須)
  • account-blocklist:指定したアカウントをnukeの削除対象から除外(必須)
  • accounts: 対象のアカウントを指定。アカウント別に、タグの値などを利用して削除するリソースを絞り込む(必須)
  • resource-types
    • excludes: 指定したサービスをnukeの削除対象から除外
    • targets: 削除の対象とするサービスを明示的に指定
  • feature-flags: その他のオプション(削除保護がついている場合の挙動の指定など)

詳細は公式の README.md をご確認ください。

全リソースを焼き払うのもよいですが、今回は検証ということでEC2インスタンスのみ削除するようにファイルを修正しました。。
excludes の設定はこちらの記事で紹介されているものを真似させていただきました。

修正後の nuke_generic_config.yaml
nuke_generic_config.yaml
regions:
  # add `global` here to include IAM entities to be nuked
  - TARGET_REGION # will be overridden during run time based on region parameter

account-blocklist:
- 123456789012 #prod

# optional: restrict nuking to these resources
resource-types:
  excludes:
    - IAMUser
    - IAMUserPolicyAttachment
    - IAMUserAccessKey
    - IAMLoginProfile
    - IAMGroup
    - IAMRolePolicy
    - IAMRolePolicyAttachment
    - IAMPolicy
    - IAMSAMLProvider
    - CloudTrailTrail
    - SNSTopic
    - LambdaFunction
    - CloudFormationStack
    - Budget
    - S3Bucket
    - S3Object
  targets:
    - EC2Instance

accounts:
  ACCOUNT: # will be overridden during run time based on account param
    filters:
      IAMRole:
      - "ProdRoles"
      - "DoNotDeleteRoles"
      - type: regex
        value: ".*"
      IAMUser:
      - "admin"
      - type: regex
        value: ".*"
      IAMUserPolicyAttachment:
      - property: RoleName
        value: "admin"
      IAMUserAccessKey:
      - property: UserName
        value: "admin"
      S3Bucket:
      - "s3://my-bucket"
      CloudTrailTrail: # filter all CloudTrail
      - type: regex
        value: ".*"
      SNSTopic: # SNS is protected based on global exception tags inside the nuke_config_update.py
      - type: regex
        value: ".*"
      SQSQueue: [] # delete all SQS

resource-types で指定できる値の全量は、aws-nukeをインストールした環境で以下のコマンドを実行することで確認できます。

aws-nuke resource-types

タイトル通りの「リソース全削除」をしたい場合は、以下の最小設定で良いと思いますが、全くおすすめはしません!!

最小設定の nuke_generic_config.yaml
nuke_generic_config.yaml
regions: # 必要に応じて追加
- eu-west-1
- global

account-blocklist:
- "999999999999" # 本番環境

accounts:
  "000000000000": {}

1.5. 設定ファイルをS3に配置

1.3. で作成したスタックの中で、設定ファイル格納用のS3バケットも作成されています。
S3バケットの名前は cfn設定ファイルで指定した文字列-アカウントID-作成リージョン-ランダム文字列 となっており、スタックの「出力」画面からも確認できます。

image.png

このS3バケットに nuke_config_update.pynuke_generic_config.yaml を配置します。
※以下に★マークで示しているファイル

aws-nuke-account-cleanser-example-main
│  CODE_OF_CONDUCT.md
│  CONTRIBUTING.md
│  LICENSE
│  nuke-cfn-stack.yaml
│  README.md
│
├─config
│      ★ nuke_config_update.py
│      ★ nuke_generic_config.yaml   # 1.4. で修正したもの
│
└─images
        architecture-overview.png

image.png

1.6. 削除用のリソースを作成

検証用に、実際にaws-nukeを動かすときに削除するリソースを作成します。
今回はEC2インスタンスを1台作りました。
image.png

1.7. Step Functionsの実行

マネコンからStep Functionsの画面を開き、ステートマシン nuke-account-cleanser-codebuild-state-machine を選択します。
実行を開始 ボタンをクリックするとポップアップが開くので、入力 - オプション 欄に以下の値を入力し、実行。
※ステートマシンの設定上、以下のパラメータの入力が必須でした。

{
  "InputPayLoad": {
    "nuke_dry_run": "true",
    "nuke_version": "2.21.2",
    "region_list": [
      "us-west-2"
    ]
  }
}

image.png

nuke_dry_runの部分にtrueと入力すると、削除は実行されずに現在の設定でaws-nukeを実行した場合の削除対象のリソース名の出力のみを行ってくれます。
そのため、実際に削除を行う前にはtrueを指定し削除対象リソースの確認をすることをおすすめします。
falseと入力すると、実際にその場で削除が実行されます。注意して操作してください。
また、region_listから削除対象に加えたいリージョン名を追加できます。

(出展)AWS環境のリソース一括削除ができるaws-nukeを導入した話

実行ステータスが 成功 となったら、実行結果(ログ)を確認します。
実行結果の確認は、CodeBuild と CloudWatch Logs のどちらでも行えますが、CloudWatch Logs はログの出方が汚いので、CodeBuild からの確認をおすすめします。

CodeBuildの画面から AccountNuker-NukeCleanser をクリック。
「ビルド履歴」に各リージョン毎のログが出力されるので、それらを確認します。
今回はus-west-2のみを対象としたので、履歴は1件のみです。

image.png

ビルドログの末尾を確認すると以下のような記述があり、「1個のリソースが削除対象と認識されたが削除はされていない」という内容が読み取れます。

Resources Nukeable:
Scan complete: 1 total, 1 nukeable, 0 filtered.
Total number of Resources that would be removed:
1

Total number of Resources Deleted:
0
Nothing deleted yet

ログをさかのぼると以下の記述があり、 1.6. で作成したEC2インスタンスがヒットしていることが分かります。

us-west-2 - EC2Instance - i-055ad6df0e3091f2e - [Identifier: "i-055ad6df0e3091f2e", ImageIdentifier: "ami-023e152801ee4846a", InstanceState: "running", InstanceType: "t2.micro", LaunchTime: "2024-05-06T02:39:53Z", tag:Name: "nuke-test"] - would remove
Scan complete: 1 total, 1 nukeable, 0 filtered.

The above resources would be deleted with the supplied configuration. Provide --no-dry-run to actually destroy resources.

意図通りの挙動をしていることが確認できました!
"Nukeable" という表現は個人的にツボでしたw

2. API Gateway設定

Step Functions・aws-nukeの挙動は問題なさそうなので、次は、それをAPI Gatewayから呼び出せるようにします。
やったことは、AWSから案内されている手順そのままです。

ステップ 3: API Gateway API のテストとデプロイ で「リクエスト本文」にコピーする内容は以下の通りになります。
「クエリ文字列」と「ヘッダー」は空白(入力なし)です。

{
  "input": "{\"InputPayLoad\":{\"nuke_dry_run\":\"true\",\"nuke_version\":\"2.21.2\",\"region_list\":[\"us-west-2\"]}}",
  "name": "MyExecution",
  "stateMachineArn": "arn:aws:states:(リージョン):(アカウントID):stateMachine:nuke-account-cleanser-codebuild-state-machine"
}

image.png

以下のようなログが出たらOK!

Successfully completed execution
Method completed with status: 200

念のため、Step Functionsの画面からも、実行ステータスが 成功 となっていることを確認しておきましょう。
必要に応じてビルドログも確認します。

問題なければAPIをデプロイします。
デプロイ後、API の呼び出し URL をコピーします。
URLは https://a1b2c3d4e5.execute-api.us-east-1.amazonaws.com/alpha/execution のような形になります。

image.png

3. Webアプリ作成(Bubble)

そもそものBubbleの始め方やアプリの作り方はここでは割愛します。
過去にいくつか記事を上げていますので、そちらも参考にしていただければと思います。
※めちゃ簡単に作れますので是非!
 アプリをデプロイせずに開発画面とプレビューで遊ぶだけなら無料です。

3.1. プラグインのインストール・設定

今回はBubbleのプラグイン「API Connector」を利用します。
初期設定の手順はこちらの記事を参考にさせていただきました。

今回設定した内容がこちら。
image.png

URLの部分には、2. API Gateway設定 でコピーしたURLを貼り付けます。

Body (JSON object, use <> for dynamic values) の値は以下のようにしました。

{
  "input": "{\"InputPayLoad\":{\"nuke_dry_run\":\"false\",\"nuke_version\":\"2.21.2\",\"region_list\":[\"us-west-2\"]}}",
  "name": "<ExecutionName>",
  "stateMachineArn": "arn:aws:states:(リージョン):(アカウントID):stateMachine:nuke-account-cleanser-codebuild-state-machine"
}

<ポイント>

  • nuke_dry_run の値を false にし、マジでリソースが削除されるように設定。
  • name が固定値だと、複数回実行したときに Execution Already Exists というエラーが出て怒られます。
    実行のたびに値を変えたいので、<ExecutionName> のようにして、Bubbleのワークフローの中でパラメータとして設定できるようにします。

Webアプリ作成

こんな感じで作ってみましたw
この記事の本筋ではないので、概要だけ記載します。

  • indexページ
    Secret Code(笑)を入れて 確認 ボタンを押すとポップアップが表示されます。
    Secret Codeの正誤によって表示するポップアップを変えています。
    image.png
    ▼ Secret Codeが正しかった場合のポップアップ。リソース全削除 ボタンを押すとAPIが実行されます。
    image.png
    ▼ Secret Codeが誤っていた場合のポップアップ。
    image.png
    ▼ ワークフローは以下の通り。
    image.png
    リソース全削除 ボタンに 3.1. で作ったAPIを紐づけます。
    (body) ExecutionName 欄には fromBubble_(実行時のyyyymmdd_HHMMss)が入るようにします。
    これにより、name の重複を防ぎ、Execution Already Exists をエラー回避します。
    API実行が成功すると、doneページへ移動します。
     
    Secret Codeの正誤判定はワークフローの中で記述しています(あまりイケてない、、)
    image.png

  • doneページ
    これだけです。
    image.png

動かしてみた

Bubbleで作ったWebアプリのプレビューページを開きます。
image.png

Secret Codeを入力し、確認 ボタンをクリックすると、ポップアップが開きます。
ドキドキしますね。
思わず古より伝わる呪文を唱えてしまいます。「バ●ス」
リソース全削除 ボタンをクリックします。
image.png

ページ遷移しました。
やってやったぞ感(興奮)とやらかしてしまった感(絶望)が味わえます。
image.png

裏で動いているStep Functionsを確認します。
image.png
大丈夫そう!

ビルドログには以下の記述がありました。
ちゃんとEC2インスタンスが削除されたようです。

Scan complete: 1 total, 1 nukeable, 0 filtered.
Total number of Resources that would be removed:
1
Total number of Resources Deleted:
1
List of Resources Deleted today:
us-west-2 - EC2Instance - i-055ad6df0e3091f2e - [Identifier: "i-055ad6df0e3091f2e", ImageIdentifier: "ami-023e152801ee4846a", InstanceState: "running", InstanceType: "t2.micro", LaunchTime: "2024-05-06T02:39:53Z", tag:Name: "nuke-test"] - removed

EC2の画面から確認すると、1.6. で作成したEC2インスタンスのステータスが 終了済み となっていました!
image.png

終わりに

遊び心もりもりでaws-nukeの検証を進めてきました。
タイトルで「リソース全削除ボタン」を作ると言っておきながら、怖すぎてEC2インスタンス1台での検証に留まってしまったところは、私の気の小ささゆえでございます。。

冗談はさておき、aws-nukeを適切に使えばリソースの削除が楽になり、コスト削減にもつなげられますが、設定によっては「システム破壊爆弾」になりかねません。
aws-nukeを利用する際は、注意に注意を重ねることが必要だと思いました。

aws-nukeの設定ファイルの作成にあたり、ノウハウをまとめた記事を書きました。
「システム破壊爆弾」を作らないためにも、是非ご参照ください。

また、今回はaws-nukeの実行環境としてStep FunctionsとCodeBuildを利用しましたが、シンプルにLambdaでもできるんじゃないか、というアイデアもいただきました。
今後、Lambdaでの実装もチャレンジしてみたいと思います!

最後までお目通しいただき、ありがとうございました。

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?