はじめに
お疲れ様です。yuki_inkです。
AWSのリソース全部消し飛ばせるボタンがあったら面白いなぁ という過激思想を抑えられず、ゴールデンウィークの自由研究として「リソース全削除ボタン」を作ってみました。
元ネタはこちら。2つの偉大な発表から着想を得たものです。
ゴールと構成概略
ゴール:Webアプリのボタンを押すとaws-nukeがキックされ、AWS上のリソースが削除される。
構成図は以下の通り。
"Webアプリのボタン"
今回はBubbleで実装しました。ノーコード万歳。
"aws-nukeがキックされ、AWS上のリソースが削除される"
そもそもaws-nukeとは。
aws-nukeとは、アカウント内のリソースを強制的に削除するコマンドラインツールです。
設定ファイルを用意し、コマンドを実行するだけで強制的にリソースを削除できます。
かなり強力なツールなので使用する際は細心の注意を払う必要がありますが、適切な使い方をすればとても便利ツールです。
利用するための料金はかかりません。
aws-nukeはAWS非公式のツールながら、AWSからサンプルのCloudFormationテンプレートが提供されています。
今回はそれを利用し、Step FunctionsとCodeBuildを使った構成としました。
BubbleとStep Functionsの連係のため、間にAPI Gatewayを挟み、Bubbleのプラグイン「API Connector」を利用してPOSTを投げる形にしました。
やったこと
以下について、それぞれ記載します。
- aws-nuke実行環境構築(Step Functions・CodeBuildなど)
- API Gateway設定
- Webアプリ作成(Bubble)
1. aws-nuke実行環境構築
1.1. 事前準備
AWSから提供されているサンプルファイルをダウンロードします。
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トピック
今回、StepFunctionsはAPI Gatewayから呼び出すようにするので、EventBridgeルール EventBridgeNukeSchedule
とEventBridge用のIAMロール・IAMポリシー EventBridgeNukeScheduleRole
の箇所はコメントアウトしました。
また、SNSトピック NukeEmailTopic
の箇所でサブスクリプションも作成されており、デフォルトで test@test.com
が入っていますが、ここで自分のメアドに変更しておきます。
修正後の 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
としました。
パラメータにはデフォルトの値が入っているので、必要に応じて値を修正します。
※修正しておいたほうがいいのは Owner
の値くらいかなと思います。。
CLIからスタック作成を行いたい場合、こちらの記事が参考になります。
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
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
regions: # 必要に応じて追加
- eu-west-1
- global
account-blocklist:
- "999999999999" # 本番環境
accounts:
"000000000000": {}
1.5. 設定ファイルをS3に配置
1.3. で作成したスタックの中で、設定ファイル格納用のS3バケットも作成されています。
S3バケットの名前は cfn設定ファイルで指定した文字列-アカウントID-作成リージョン-ランダム文字列
となっており、スタックの「出力」画面からも確認できます。
このS3バケットに nuke_config_update.py
と nuke_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
1.6. 削除用のリソースを作成
検証用に、実際にaws-nukeを動かすときに削除するリソースを作成します。
今回はEC2インスタンスを1台作りました。
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"
]
}
}
nuke_dry_runの部分にtrueと入力すると、削除は実行されずに現在の設定でaws-nukeを実行した場合の削除対象のリソース名の出力のみを行ってくれます。
そのため、実際に削除を行う前にはtrueを指定し削除対象リソースの確認をすることをおすすめします。
falseと入力すると、実際にその場で削除が実行されます。注意して操作してください。
また、region_listから削除対象に加えたいリージョン名を追加できます。
実行ステータスが 成功
となったら、実行結果(ログ)を確認します。
実行結果の確認は、CodeBuild と CloudWatch Logs のどちらでも行えますが、CloudWatch Logs はログの出方が汚いので、CodeBuild からの確認をおすすめします。
CodeBuildの画面から AccountNuker-NukeCleanser
をクリック。
「ビルド履歴」に各リージョン毎のログが出力されるので、それらを確認します。
今回はus-west-2のみを対象としたので、履歴は1件のみです。
ビルドログの末尾を確認すると以下のような記述があり、「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から案内されている手順そのままです。
- ステップ 1: API Gateway 用にIAM ロールを作成する
- ステップ 2: API Gateway で API を作成する
- ステップ 3: API Gateway API のテストとデプロイ
ステップ 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"
}
以下のようなログが出たら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
のような形になります。
3. Webアプリ作成(Bubble)
そもそものBubbleの始め方やアプリの作り方はここでは割愛します。
過去にいくつか記事を上げていますので、そちらも参考にしていただければと思います。
※めちゃ簡単に作れますので是非!
アプリをデプロイせずに開発画面とプレビューで遊ぶだけなら無料です。
3.1. プラグインのインストール・設定
今回はBubbleのプラグイン「API Connector」を利用します。
初期設定の手順はこちらの記事を参考にさせていただきました。
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の正誤によって表示するポップアップを変えています。
▼ Secret Codeが正しかった場合のポップアップ。リソース全削除
ボタンを押すとAPIが実行されます。
▼ Secret Codeが誤っていた場合のポップアップ。
▼ ワークフローは以下の通り。
リソース全削除
ボタンに 3.1. で作ったAPIを紐づけます。
(body) ExecutionName
欄にはfromBubble_(実行時のyyyymmdd_HHMMss)
が入るようにします。
これにより、name
の重複を防ぎ、Execution Already Exists
をエラー回避します。
API実行が成功すると、doneページへ移動します。
Secret Codeの正誤判定はワークフローの中で記述しています(あまりイケてない、、)
動かしてみた
Bubbleで作ったWebアプリのプレビューページを開きます。
Secret Codeを入力し、確認
ボタンをクリックすると、ポップアップが開きます。
ドキドキしますね。
思わず古より伝わる呪文を唱えてしまいます。「バ●ス」
リソース全削除
ボタンをクリックします。
ページ遷移しました。
やってやったぞ感(興奮)とやらかしてしまった感(絶望)が味わえます。
裏で動いているStep Functionsを確認します。
大丈夫そう!
ビルドログには以下の記述がありました。
ちゃんと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インスタンスのステータスが 終了済み
となっていました!
終わりに
遊び心もりもりでaws-nukeの検証を進めてきました。
タイトルで「リソース全削除ボタン」を作ると言っておきながら、怖すぎてEC2インスタンス1台での検証に留まってしまったところは、私の気の小ささゆえでございます。。
冗談はさておき、aws-nukeを適切に使えばリソースの削除が楽になり、コスト削減にもつなげられますが、設定によっては「システム破壊爆弾」になりかねません。
aws-nukeを利用する際は、注意に注意を重ねることが必要だと思いました。
aws-nukeの設定ファイルの作成にあたり、ノウハウをまとめた記事を書きました。
「システム破壊爆弾」を作らないためにも、是非ご参照ください。
また、今回はaws-nukeの実行環境としてStep FunctionsとCodeBuildを利用しましたが、シンプルにLambdaでもできるんじゃないか、というアイデアもいただきました。
今後、Lambdaでの実装もチャレンジしてみたいと思います!
ローカルでビルドしたnukeのイメージをLambdaで実行すれば、割とサクッとできそう、、?🤔
— hiropy (@hiropy6387) May 5, 2024
(できなかったらすみません🥺)https://t.co/unbqAXXvrm
最後までお目通しいただき、ありがとうございました。