AWSマルチアカウント環境のセキュリティ強化としてAmazon Detectiveを導入した話(構築編)

こんにちは。
AWS推進グループの高田です。

私の所属するAWS推進グループでは、最近刷新されたパーソルグループ標準のAWS環境の整備、改善や、サービス移設の推進をしています。

2020年10月にTECH Streetにて、そのグループ標準AWS環境のセキュリティ強化のためにAmazon Detectiveを導入した話をさせていただいたのですが、今回はそこではあまり触れられなかったAmazon Detectiveの構築部分について紹介させていただこうかと思います。
▼登壇したイベントレポートはこちら。

www.tech-street.jp

前提

お話するにあたり、いくつかの前提があるので以下に記載させていただきます。

Amazon Detectiveについて

Amazon Detectiveは、2020年3月31にGAされたAWSのサービスで、以下のような特徴があります。

  • セキュリティデータの収集/分析/視覚化(グラフ化など)を、自動的/継続的/マネージドに、いい感じに実施してくれるサービス
    • セキュリティデータ=Guard Duty, CloudTrailログ, VPCフローログ
  • セキュリティインシデント対応のフローの中では、「調査」を担うサービス
  • マルチアカウントに対応しており、AWS Organizationsとは異なる独自のMaster/Memberアカウント構成を取ることができる
    • MasterアカウントからMemberアカウントへ招待を送り、Memberアカウントで承認する
  • Guard Dutyの有効化後、48時間経過しなければ有効化できない
  • 30日の無料お試し期間あり

また、詳細については以下のBlack Belt資料がとても分かりやすいかなと思います。
[AWS Black Belt Online Seminar] Amazon Detective 資料及び QA 公開

パーソルグループ標準AWS環境について

  • AWS Organizationsを利用したマルチアカウント構成をとっている
    • OrganizationsのMasterアカウントについてはパーソルホールディングスのクラウド基盤運用チームが運用、管理している
    • そのため、AWS Organizationsの機能はパーソルキャリアでは利用できない
  • パーソルキャリアでは2020年10月時点で約80アカウントを利用中
  • グループ標準AWS環境において、Guard Dutyは各AWSアカウント払い出し時に有効化されている

構築概要

まずパーソルキャリア用に新たにセキュリティアカウントを用意し、これをAmazon DetectiveのMasterアカウントとしました。
また、今回のAmazon Detective導入は次期以降のセキュリティ施策のための事前調査の意味も兼ねており、セキュリティアカウント周りは構成変更が予想されるので、再現性も持たせるためにCloudFormationですべてコード化しています。
以下が概要図です。

概要図

マルチアカウント&全リージョンでのDetective有効化

80のAWSアカウントとその各リージョンにAmazon DetectiveのMemberアカウントの招待を送信しなければならないため、AWSが公開しているマルチアカウント用のDetective有効化Pythonスクリプトを利用し、自動化しています。

自動化の方式ですが、ざっと考えると以下が考えられるかなと思います。

  1. ローカルでスクリプトを実行する
  2. EC2上でスクリプトを実行する
  3. ECSタスクとしてスクリプトを実行する
  4. Lambdaでスクリプトを実行する

今回は4のLambda案を採用しています。
理由としては、Detectiveを有効化するMemberアカウントのリストをCSVファイルで管理するのですが、管理場所をS3バケットとしたく、また、サーバーも管理したくないため、S3バケットの更新イベントトリガーと相性のいいサーバレスのLambdaを採用しました。

ただし、AWS公開のスクリプトは実行環境はEC2もしくはローカルを想定していて、引数としてDetectiveを有効化するMemberアカウントのリスト(CSVファイル)を取っているため、Lambda上で実行するために引数部分のみ修正を加えています。
具体的には、以下のようにCSVファイルはS3からDLし、AWS公開スクリプト(enableDetective.py)から関数をインポートしてLambdaのハンドラーで実行する形を取っています。

import urllib.parse
import boto3
import botocore.exceptions
import subprocess
import uuid
import logging
import os
import time
import itertools
from enableDetective import read_accounts_csv,get_regions,assume_role,get_graphs,get_members,create_members,accept_invitations,enable_detective

s3 = boto3.resource('s3')

# 基本的に全リージョンに対して実行する
ENABLED_REGIONS = ''

def download_s3_object(event):
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    file_path = '/tmp/{}{}'.format(uuid.uuid4(), key.split('/')[-1])
 
    try:
        bucket = s3.Bucket(bucket_name)
        bucket.download_file(key, file_path)
        return file_path
    except Exception as e:
        print(e)

def lambda_handler(event, context):
    # S3からアカウント一覧を取得
    file_path = download_s3_object(event)
    
    # S3から取得したファイルからアカウント一覧を取り出す
    with open(file_path, mode='r') as input_file:
        aws_account_dict = read_accounts_csv(input_file)

    # Masterアカウント側の準備
    try:
        session = boto3.session.Session()
        detective_regions = get_regions(session, ENABLED_REGIONS)
        master_session = assume_role(os.environ['MASTER_ACCOUNT'], os.environ['ASSUME_ROLE'])
    except NameError as e:
        # logging.exception prints the full traceback, logging.error just prints the error message.
        # In this case, the NameError is handled in a specific way: it is an expected exception
        # and the traceback doesn't add any value to the error message.
        logging.error(f'Master account is not defined: {e.args}')
    except Exception as e:
        # in this case, there has been an unhandled exception, something that wasn't estimated.
        # Having the error traceback helps us know what happened and help us find a solution
        # for the bug. The code should never arrive to this except clause, but if it does
        # we want as much information as possible and that's why we use logging.traceback.
        # In this case the traceback adds LOTS of value.
        logging.exception(f'error creating session {e.args}')

    # MemberアカウントのDetective招待と承認
    for region in detective_regions:
        try:
            d_client = master_session.client('detective', region_name=region)
            graphs = enable_detective(d_client, region)

            if graphs is None:
                continue

            try:
                all_members, pending = get_members(d_client, graphs)

                for graph, members in all_members.items():
                    new_accounts = create_members(
                        d_client, graph, members, aws_account_dict)
                    print("Sleeping for 5s to allow new members' invitations to propagate.")
                    time.sleep(5)
                    accept_invitations(os.environ['ASSUME_ROLE'], itertools.chain(
                        new_accounts, pending[graph]), graph, region)
            except NameError as e:
                logging.error(f'account is not defined: {e}')
            except Exception as e:
                logging.exception(f'unable to accept invitiation: {e}')

        except NameError as e:
            logging.error(f'account is not defined: {e}')
        except Exception as e:
            logging.exception(f'error with region {region}: {e}')

上記の対応を取ることで、AWSアカウントリストを更新する場合などは、S3バケットに格納しているCSVファイルを更新すると自動的にDetectiveが有効化されるようにできました。

また、Detectiveを無効化する場合についても、AWSが同じくスクリプトを公開している(disableDetective.py)ため、上記と同じ対応を取ることでLambda化しています。

課題

いくつか課題もありますので、以下に記載させていただこうと思います。

Detective有効化用のIAMロールについて

Detectiveの有効化などを別アカウントから実行するためには、IAMロールを用意してAssume Roleにより権限を移譲する必要があります。
つまり、このIAMロールだけは各AWSアカウントに構築する必要があるのですが、これはCloudFormation StackSetsを利用して構築しています。
ただし、前提にもあるように今回はAWS Organizationsは利用しないため、各AWSアカウント払い出し時にStackSets用のIAMロールと信頼関係を追加する、という対策を実施しています。

CSVファイルの分割について

大量のアカウントへの処理になるとLambdaがタイムアウトしてしまうため、適切な単位でCSVファイルを分割することが必要となっています。
もしCSVファイルが大きくなりすぎてタイムアウトしてしまっても、再度CSVファイルを上書きアップロードすることで続きの処理は可能(すでに有効化されているアカウントに対しては処理がスキップされるため)ですが、ここらへんもエラー検知で対応できるかと思っています。

まとめ

  • 80アカウント&全リージョンに対して、Amazon Detectiveを有効化&Master/Memberアカウント関係構築処理を、Lambdaにて自動化した。
  • 構築したすべてのリソースはCloudFormationにてコード化しており、再現性を実現した。
  • AWSが公開しているマルチアカウント適用のPythonスクリプトを流用できたため、CloudFormationコード化まで含めて2人日で構築できた。

Amazon Detectiveは2020年3月末にGAされた比較的新しいサービスですが、有効化するだけであるというハードルの低さと、Guard Duty、ClouTrailログ、VPCフローログという主要なセキュリティデータが収集対象になっているというのが魅力的でした。
今回Detectiveを有効化していろいろ自分たちのAWSアカウントのデータを見ることができましたが、次のセキュリティ施策につながるような課題発見もあり、セキュリティ強化のための現状調査が行えるところも魅力なのではないかと思います。
AWSがマルチアカウント有効化のスクリプトも公開していて、30日の無料お試し期間もあるため、AWS環境のセキュリティ強化を考えているセキュリティ組織など、試しに有効化していろいろ見てみるのもいいんじゃないかなと個人的に思います!

高田さんに話を聞きました

高田 洸介さんのプロフィール

テクノロジー本部 インフラ基盤統括部 システム共通BITA部 AWS推進グループ リードエンジニア

※2020年11月現在の情報です。