AWS

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

cleaning your work space.
hiropy

はじめに

みなさんこんにちは。仕事のお供はお煎餅なhiropyです。
今回は、ControlTowerで構築したマルチアカウント環境で、aws-nukeを使って、自動、かつ定期的にリソースのクリーンアップを行う方法について書いてみようと思います。

これを導入することで、社内の検証環境の不要なリソースの手動削除の手間を減らせるかも  

前提

今回は、以下の前提で進めさせていただきます。

  • ControlTowerがデプロイされていること
  • ControlTowerのルートアカウントが使用できること
  • ControlTowerのルートアカウントから各アカウントに対してAssumeRoleができること
  • リソース削除の対象となるアカウントにエイリアスが付与されていること

エイリアスの付与に関しては見落としがちな点だと思うので、注意してください。
アカウントのエイリアス付与の方法についてはこちらをご参照ください。

aws-nukeとは?

本題に入る前に、「そもそもaws-nukeってなんなの?」と思われる方が多いと思うのでその説明をさせていただきます。
そもそもaws-nukeとは、アカウント内のリソースを強制的に削除するコマンドラインツールです。(Githubリポジトリ

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

利用するための料金はかかりません。

今回のゴール

aws-nukeのご紹介もさせていただいたところで、早速本題に入ろうと思います。
今回ご紹介させていただくリソースのアーキテクチャは以下の通りです。

image.png (232.3 kB)

やっていることとしては、

  • EventBridgeスケジュールで定期的にStep Functionsステートマシンを呼び出す
  • Step Functionsステートマシン内で
    • Lambda関数によりOU内にあるアカウントIDのリストを取得
    • アカウントIDのリストをCodeBuildプロジェクトに渡す
    • CodeBuildプロジェクト内でaws-nukeをアカウントIDの分だけ実行しリソースを削除する

となっています。

今回は、

  1. アカウントIDを取得するLambda関数を作成する
  2. CodeBuildプロジェクトを作成する
  3. Step Functionsのステートマシンを作成する
  4. EventBridgeスケジュールを作成する

の順番でご紹介させていただきます。

  以降の作業は、すべてControlTowerの管理アカウントで行ってください 

やってみよう

1. アカウントIDを取得するLambda関数を作成する

まずはOU内のアカウントIDを取得するLambda関数を作成します。

以下のコードをコピーして、Lambdaで新しい関数を作成してください。

Q
アカウントIDを取得するLambdaのコード
import boto3

client = boto3.client('organizations')

# 異なるAWSアカウント/ロールのクレデンシャル取得を実行する
def sts_assume_role(account_id):
    role_arn = "arn:aws:iam::%s:role/AWSControlTowerExecution" % account_id
    session_name = "create-alias"

    client = boto3.client('sts')

    # AssumeRoleで一時クレデンシャルを取得
    response = client.assume_role(
        RoleArn=role_arn,
        RoleSessionName=session_name
    )
    
    iam_client = boto3.client(
        'iam',
        aws_access_key_id=response['Credentials']['AccessKeyId'],
        aws_secret_access_key=response['Credentials']['SecretAccessKey'],
        aws_session_token=response['Credentials']['SessionToken'],
    )


    return iam_client
 
# アカウントのエイリアスを取得する   
def get_account_aliases(account_id):
    iam = sts_assume_role(account_id)

    res = []
    # List account aliases through the pagination interface
    paginator = iam.get_paginator('list_account_aliases')
    for response in paginator.paginate():
        res = response["AccountAliases"]
    return res
    

# エイリアスが存在するアカウントIDのリストを取得する
def get_account_ids():
    # 全組織のアカウント情報を取得する
    pagenator = client.get_paginator('list_accounts')
    response_iterator = pagenator.paginate()
    
    row_value = []
    for response in response_iterator:
        for acct in response['Accounts']:
            # 取得するアカウント情報のリスト化
            row_value.append(acct['Id'])
    
    res = ""
    # アカウントIDのリストを空白区切りの文字列にする
    for index,value in enumerate(row_value):
        # 先頭の要素はスキップする
        if index == 0:
            res = value
            continue
        res = res + " " + value

    return res

def lambda_handler(event, context):
    account_list = get_account_ids()
    return {"accounts": account_list}

任意のテストケースを作成して、OU内のアカウントIDが取得できることを確認しましょう。

2. nukeを実行するためのCodeBuildプロジェクトを作成する

次にaws-nukeの実行体である、CodeBuildプロジェクトの作成を行います。
前準備として、aws-nukeの設定ファイルとログを置くためのS3 Bucketを作成します。

任意の名前のS3Bucketを2つ作成し、どちらかにnuke-config.yamlという名前で以下のファイルを配置してください。

Q
nuke-config.yamlのコード
regions:
  # add `global` here to include IAM entities to be nuked
  # nukeでリソースを削除する対象のリージョンをリストアップします
  - ap-northeast-1 
  - us-east-1

account-blocklist:
  - 123456789012 # 削除対象から外すアカウントをリストアップします

# optional: restrict nuking to these resources
resource-types:
  excludes: # 削除の対象外とするリソースをリストアップします
    - IAMUser
    - IAMUserPolicyAttachment
    - IAMUserAccessKey
    - IAMLoginProfile
    - IAMGroup
    - IAMRolePolicy
    - IAMRolePolicyAttachment
    - IAMPolicy
    - IAMSAMLProvider
    - CloudTrailTrail
    - SNSTopic
    - LambdaFunction
    - CloudFormationStack
    - Budget

  targets: # 削除対象とするサービスをリストアップします
    - S3Bucket
    - S3Object
    - SQSQueue
    - RDSInstance
    - EC2Instance
    - EC2Image
    - EC2NATGateway
    - DynamoDBTableItem
    - DynamoDBTable

accounts:
  ACCOUNT: # ACCOUNTという文字列は後ほどCodeBuild内で実際のアカウントIDに書き換えられます
    filters: # 特定のタグがついたリソースを削除対象外にできます
      EC2Instance:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      S3Bucket:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      S3Object:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      SQSQueue:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      RDSInstance:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      EC2Image:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      EC2InternetGateway:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      EC2NATGateway:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      DynamoDBTableItem:
      - type: exact
        property: tag:DoNotNuke
        value: "true"
      DynamoDBTable:
      - type: exact
        property: tag:DoNotNuke
        value: "true"

feature-flags:
  disable-deletion-protection:
    RDSInstance: true
    EC2Instance: true

yamlファイルを構成する各要素について解説します。

  • regions: nukeでリソースを削除する際、どのリージョンのリソースを削除するかを指定することができます。
  • account-blocklist:指定したアカウントをnukeの削除対象から外すことができます。
  • resource-types
    • excludes: 削除の対象外とするサービスを指定することができます。
    • targets: 削除の対象とするサービスを明示的に指定することができます。
  • accounts: アカウント別に削除するリソースを絞り込むことができます。今回はDoNotNuke: trueのタグがついたリソースを削除の対象外としています。
  • feature-flags: その他のオプションです。今回は、削除保護がついているRDSとEC2も強制的に削除するようにしています。

このようにaws-nukeでは、削除対象の指定などを柔軟に行うことができます。
皆様が利用されているAWS環境のニーズに合わせて、こちらのルールは変更してください。

続いて、nukeを実行するためのCodeBuildプロジェクトをCloudFormationのStackでデプロイします。

以下のコードを手元にコピーして新規ファイルを作成し、CloudFormationでStackを作成してください。

なお、以下の箇所は対応する値に置き換えてください。

  • <管理アカウントのアカウントID>:CloudFormationをデプロイするアカウント(=ControlTowerの管理アカウント)のアカウントID
  • <設定ファイル用のバケット名>nuke-config.yamlを配置したバケットの名前
  • <ログ用のバケット名>:先ほど作成したバケットのうち、何も配置していないバケットの名前
Q
CodeBuildデプロイ用のCloudFormation Stack
Parameters:
  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
  AccountId:
    Description: The AWS account ID
    Type: String
  

Resources:
  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
      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: AccountId
            Type: PLAINTEXT
            Value: !Ref AccountId
          - Name: NukeCodeBuildProjectName
            Type: PLAINTEXT
            Value: !Sub "AccountNuker-codeBuild"
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Sub "AccountNuker-logs"
          Status: ENABLED
      Name: !Sub "AccountNuker-${AWS::StackName}"
      ServiceRole: arn:aws:iam::<管理アカウントのアカウントID>:role/CodeBuildExecNukeRole
      TimeoutInMinutes: 120
      Source:
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              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.credential_source "EcsContainer"
                  - export AWS_PROFILE=nuke
                  - export AWS_DEFAULT_PROFILE=nuke
                  - export AWS_SDK_LOAD_CONFIG=1
            build:
              commands:
                  - echo "Getting nuke generic config file from S3";
                  - aws s3 cp s3://<設定ファイル用のバケット名>/nuke_config.yaml .
                  - echo "Configured nuke_config.yaml";
                  - echo " ------------------------------------------------ " >> error_log.txt
                  - echo "Running Nuke on Account";
                  - |
                    for i in $AccountId;
                      do
                        aws configure set profile.nuke.role_arn arn:aws:iam::$i:role/AWSControlTowerExecution
                        touch nuke_config_$i.yaml
                        cp nuke_config.yaml nuke_config_$i.yaml
                        sed -i "s/ACCOUNT/$i/g" nuke_config_$i.yaml
                        log_file=aws-nuke-$(date +%Y%m%d%I%M%S).log
                        if [ "$AWS_NukeDryRun" = "true" ]; then
                          aws-nuke -c nuke_config_$i.yaml --quiet --force --profile nuke 2>&1 |tee -a $log_file;
                        elif [ "$AWS_NukeDryRun" = "false" ]; then
                          aws-nuke -c nuke_config_$i.yaml --quiet --force --no-dry-run --profile nuke 2>&1 |tee -a $log_file;
                        else
                          echo "Couldn't determine Dryrun flag...exiting"
                          exit 1
                        fi;
                        aws s3 cp $log_file s3://<ログ用のバケット名>/$i/
                        nuke_pid=$!;
                        wait $nuke_pid;
                        echo "Checking if Nuke Process completed for account"
                        if cat $log_file | grep -F "Error:"; then
                          echo "Nuke errored due to no AWS account alias set up - exiting"
                          cat $log_file >> error_log.txt
                          aws s3 cp error_log.txt s3://<ログ用のバケット名>/$i/error/
                          exit 1
                        else
                          echo "Nuke completed Successfully - Continuing"
                        fi
                      done

        Type: NO_SOURCE

3. Step Functionsのステートマシンを作成する

次に、アカウントIDを取得するLambda関数とCodeBuildを呼び出すためのStep Functionsステートマシンを作成します。

Step Functionsコンソールを表示し、「ステートマシンを定義する」ボタンをクリックしたら、画像のように「コードでワークフローを記述」を選択してください。

下にスクロールするとワークフローを定義できる箇所が出てくるので、

以下のコードを貼り付けてください。

なお、以下の箇所は該当の値に置き換えてください。

  • <アカウントID取得Lambdaの関数名>: 1.で作成したLambda関数名
  • <CodeBuildプロジェクトのARN>: 2.で作成したCodeBuildプロジェクトのARN
Q
Step Functionsのステートマシン定義
{
  "Comment": "AWS Nuke Account Cleanser",
  "StartAt": "GetAccountIds",
  "States": {
    "GetAccountIds": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "<アカウントID取得Lambdaの関数名>"
      },
      "ResultPath": "$.accountList",
      "Next": "NukeCodeBuildJob"
    },
    "NukeCodeBuildJob": {
      "Type": "Task",
      "Resource": "arn:aws:states:::codebuild:startBuild.sync",
      "Parameters": {
        "ProjectName": "<CodeBuildプロジェクトのARN>",
        "EnvironmentVariablesOverride": [
          {
            "Name": "AccountId",
            "Type": "PLAINTEXT",
            "Value.$": "$.accountList.Payload.accounts"
          },
          {
            "Name": "AWS_NukeDryRun",
            "Type": "PLAINTEXT",
            "Value.$": "$.InputPayLoad.nuke_dry_run"
          },
          {
            "Name": "AWS_NukeVersion",
            "Type": "PLAINTEXT",
            "Value.$": "$.InputPayLoad.nuke_version"
          }
        ]
      },
      "Next": "NukeStatusCheck",
      "ResultSelector": {
        "NukeBuildOutput.$": "$.Build"
      },
      "ResultPath": "$.AccountCleanserRegionOutput",
      "Retry": [
        {
          "ErrorEquals": [
            "States.TaskFailed"
          ],
          "BackoffRate": 1,
          "IntervalSeconds": 1,
          "MaxAttempts": 0
        }
      ],
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "NukeFailed",
          "ResultPath": "$.AccountCleanserRegionOutput"
        }
      ]
    },
    "NukeStatusCheck":{
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus",
          "StringEquals": "SUCCEEDED",
          "Next": "NukeSuccess"
        },
        {
          "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus",
          "StringEquals": "FAILED",
          "Next": "NukeFailed"
        }
      ],
      "Default": "NukeSuccess"
    },
    "NukeSuccess":{
      "Type": "Succeed",
    },
    "NukeFailed":{
      "Type": "Fail",
      "Cause": "nukeの実行に失敗しました。",
    }
  }
}

このステートマシンにより、

  • アカウントIDのリストの取得
  • CodeBuildプロジェクトの実行

を行っています。

4. EventBridgeスケジュールを作成する

最後に、定義したステートマシンを定期実行するEventBridgeのスケジュールを作成します。

EventBridgeコンソールからスケジュールタブに進み、新規作成ボタンを押します。
定期的なスケジュールを選択し、cron式で以下のように入力します。
(以下のcronで設定すると、毎週金曜日の19:00にnukeが実行されます。)

0 19 ? * 6 *
image.png (193.7 kB)

ターゲットの選択ではStep FunctionsのStart Executionを選択し、

3で作成したステートマシンを選択してください。
なお、その下のインプットには、以下のように入力してください。

{
    "Comment": "Insert your JSON here",
  	"InputPayLoad":{
      "nuke_dry_run":"false",
      "nuke_version":"2.21.2"
	}
}

これで、ステートマシンに対し強制削除するためのパラメータを渡していることになります。

そのほかは特に変更せずに、確認とレビューの画面で「作成」ボタンを押します。
これで「マルチアカウントにおけるリソースの定期削除機能」の実装は完了です!

最後に

いかがでしたでしょうか?
マルチアカウントを管理する際、こちらの記事が参考になれば幸いです!

参考

AUTHOR
hiropy
hiropy
記事URLをコピーしました