こんにちは。AWS CLIが好きな福島です。
- はじめに
- 参考
- 可視化のイメージ
- 概要図
- 前提
- ①ConfigSnapshotの出力設定(各アカウントで実行)
- ②AWS Config用S3のバケットポリシー更新(ログアーカイブアカウントで実行)
- ③AWS Config用のテーブル作成(可視化アカウントで実行)
- ④AWS Config用のテーブルを更新するLambdaの作成(可視化アカウントで実行)
- ⑤Lambdaのリソースベースポリシーの設定(可視化アカウントで実行)
- ⑥S3イベントの設定(ログアーカイブアカウントで実施)
- 終わりに
はじめに
唐突ですが、マルチアカウント環境におけるリソース情報の管理は、大変ではないでしょうか。
シングルアカウントであればリソース情報の管理はなんとかなりそうですが、 マルチアカウントになってくると、どのアカウントにどういったリソースがあるのか把握するのが困難だと感じています。
ということで、マルチアカウントのリソース情報をAWS Config + Athena + QuickSightを使い、可視化する方法を3回に分けてご紹介していきたいと思います。 今回は第1弾となります。
第1弾
①ConfigSnapshotの出力設定(各アカウントで実行)
②AWS Config用S3のバケットポリシー更新(ログアーカイブアカウントで実行)
③AWS Config用のテーブル作成(可視化アカウントで実行)
④AWS Config用のテーブルを更新するLambdaの作成(可視化アカウントで実行)
⑤Lambdaのリソースベースポリシーの設定(可視化アカウントで実行)
⑥S3イベントの設定(ログアーカイブアカウントで実施)
第2弾
⑦可視化したい情報ごとに様々なビューの作成(可視化アカウントで実行)
⑧アカウントIDとアカウント名のテーブル作成(可視化アカウントで実行)
⑨⑦と⑧の結合(可視化アカウントで実行)
第3弾
⑩QuickSightでデータの可視化(可視化アカウントで実行)
参考
可視化のイメージ
可視化のイメージは、以下の通りです。 あくまで以下はサンプルのため、AthenaのビューとQuickSightのダッシュボードを上手く活用することで 自身の環境に合わせてより最適にリソース情報を可視化することが可能です。
概要図
今回は、ログアーカイブアカウントと可視化アカウントを分けた構成を組んでみたいと思います。 場合によっては、ログアーカイブアカウント上でリソースの可視化をするのも有りかもしれません。
前提
- AWS ConfigのS3への配信設定を済ませていること
①ConfigSnapshotの出力設定(各アカウントで実行)
私も初めて知ったのですが、AWS ConfigにはConfigHistoryとConfigSnapshotと呼ばれるデータがあります。
普段はAWS ConfigのログをS3に出力するよう設定しているかと存じますが、そのデータはConfigHistoryにあたります。 そのため、意識して設定していない場合は、ConfigSnapshotを出力する設定をしていない可能性が高いため、必ず本手順を実施しましょう。
ConfigSnapshotの確認
以下のコマンドを実行し、configSnapshotDeliveryPropertiesがあるかを確認します。
aws configservice describe-delivery-channels
- 実行結果例
# aws configservice describe-delivery-channels { "DeliveryChannels": [ { "name": "default", "s3BucketName": "config-bucket-xxxxxxxxx" } ] } #
上記のような結果の場合、設定スナップショットの出力設定がされていないため、以下のコマンドを実行します。
ConfigSnapshotの設定
設定ファイルの作成
まずは設定ファイルを作成します。
aws configservice describe-delivery-channels \ --query "DeliveryChannels[0].{\ name:name,\ s3BucketName:s3BucketName,\ configSnapshotDeliveryProperties:{\ deliveryFrequency:'TwentyFour_Hours'\ }}" \ > delivery-channel.json
生成されたファイルは以下のイメージとなります。
以下の設定は、24時間おきにスナップショットを取得する設定となります。 取得する時間は以下から選択することができるため、deliveryFrequencyの値を必要に応じて変更します。
頻度を短くした場合はログの量が多くなり、コスト増に繋がりますが、Quickshightで可視化している情報が更新される頻度が短くなります。
One_Hour | Six_Hours | Three_Hours | Twelve_Hours | TwentyFour_Hours
# cat delivery-channel.json { "name": "default", "s3BucketName": "config-bucket-111111111111", "configSnapshotDeliveryProperties": { "deliveryFrequency": "TwentyFour_Hours" } }
設定ファイルを基にConfigSnapshotの設定
上記の生成されたファイルを基にConfigSnapshotの設定を行います。
aws configservice put-delivery-channel \
--delivery-channel file://delivery-channel.json
再度、describe-delivery-channelsコマンドを実行すると、 configSnapshotDeliveryPropertiesが設定されていることを確認できます。
# aws configservice describe-delivery-channels { "DeliveryChannels": [ { "name": "default", "s3BucketName": "config-bucket-111111111111", "configSnapshotDeliveryProperties": { "deliveryFrequency": "TwentyFour_Hours" } } ] } #
②AWS Config用S3のバケットポリシー更新(ログアーカイブアカウントで実行)
今回は、可視化アカウントからログアーカイブアカウントのS3へアクセスする必要があります。
おそらく、ConfigのS3バケットにはConfigからのアクセスを許可したバケットポリシーが設定されているかと存じますため、 それに加え、可視化アカウントからアクセスできるように以下のポリシーを追記します。
## 既存のバケットポリシーに以下を追記 ## <可視化アカウントのID>と<ConfigのS3バケット名>は環境に合わせて設定 { "Sid": "CrossAccountAccess", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<可視化アカウントのID>:root" }, "Action": [ "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:AbortMultipartUpload", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::<ConfigのS3バケット名>", "arn:aws:s3:::<ConfigのS3バケット名>/*" ] }
※オブジェクト所有者がバケット所有者と異なる場合、AthenaからS3へのアクセスが失敗します。 そのため、オブジェクトACLが有効の場合は、無効化しておきましょう。
③AWS Config用のテーブル作成(可視化アカウントで実行)
Athenaでログアーカイブアカウントに存在するS3をデータソースに aws_config_configuration_snapshotという外部テーブルを作成します。
## <ConfigのS3バケット名>は環境に合わせて設定 CREATE EXTERNAL TABLE aws_config_configuration_snapshot ( fileversion STRING, configsnapshotid STRING, configurationitems ARRAY < STRUCT < configurationItemVersion : STRING, configurationItemCaptureTime : STRING, configurationStateId : BIGINT, awsAccountId : STRING, configurationItemStatus : STRING, resourceType : STRING, resourceId : STRING, resourceName : STRING, ARN : STRING, awsRegion : STRING, availabilityZone : STRING, configurationStateMd5Hash : STRING, configuration : STRING, supplementaryConfiguration : MAP <STRING, STRING>, tags: MAP <STRING, STRING> , resourceCreationTime : STRING > > ) PARTITIONED BY ( accountid STRING, dt STRING, region STRING ) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' WITH SERDEPROPERTIES ( 'case.insensitive'='false', 'mapping.fileversion'='fileVersion', 'mapping.configsnapshotid'='configSnapshotId', 'mapping.configurationitems'='configurationItems', 'mapping.configurationitemversion'='configurationItemVersion', 'mapping.configurationitemcapturetime'='configurationItemCaptureTime', 'mapping.configurationstateid'='configurationStateId', 'mapping.awsaccountid'='awsAccountId', 'mapping.configurationitemstatus'='configurationItemStatus', 'mapping.resourcetype'='resourceType', 'mapping.resourceid'='resourceId', 'mapping.resourcename'='resourceName', 'mapping.arn'='ARN', 'mapping.awsregion'='awsRegion', 'mapping.availabilityzone'='availabilityZone', 'mapping.configurationstatemd5hash'='configurationStateMd5Hash', 'mapping.supplementaryconfiguration'='supplementaryConfiguration', 'mapping.configurationstateid'='configurationStateId' ) LOCATION 's3://<ConfigのS3バケット名>/AWSLogs/';
④AWS Config用のテーブルを更新するLambdaの作成(可視化アカウントで実行)
ConfigSnapshotが生成される度に③で作成したテーブルのパーティションを更新するLambdaを作成します。
以下は参考URLに記載のコードになりますが、処理内容は、以下になります。
- 全てのオブジェクトに反応するS3イベントを設定するため、トリガーとなったS3イベントがConfigHistoryであることをオブジェクトキーから判断
- オブジェクトキーから必要な情報を取得し、latestのパーティションを再作成およびオブジェクトキーの日付のパーティションを作成
また、IAMロールには、Athenaのフルアクセス権限(AmazonAthenaFullAccess)を付与します。
import datetime import re import boto3 import os TABLE_NAME = 'aws_config_configuration_snapshot' DATABASE_NAME = 'default' ACCOUNT_ID = None # Determined at runtime LATEST_PARTITION_VALUE = 'latest' athena = boto3.client('athena') def lambda_handler(event, context): global ACCOUNT_ID object_key = event['Records'][0]['s3']['object']['key'] match = get_configuration_snapshot_object_key_match(object_key) if match is None: print('Ignoring event for non-configuration snapshot object key', object_key) return print('Adding partitions for configuration snapshot object key', object_key) ACCOUNT_ID = context.invoked_function_arn.split(':')[4] object_key_parent = 's3://{bucket_name}/{object_key_parent}/'.format( bucket_name=event['Records'][0]['s3']['bucket']['name'], object_key_parent=os.path.dirname(object_key)) configuration_snapshot_accountid = get_configuration_snapshot_accountid(match) configuration_snapshot_region = get_configuration_snapshot_region(match) configuration_snapshot_date = get_configuration_snapshot_date(match) drop_partition(configuration_snapshot_accountid, configuration_snapshot_region, LATEST_PARTITION_VALUE) add_partition(configuration_snapshot_accountid, configuration_snapshot_region, LATEST_PARTITION_VALUE, object_key_parent) add_partition(configuration_snapshot_accountid, configuration_snapshot_region, get_configuration_snapshot_date(match).strftime('%Y-%m-%d'), object_key_parent) def get_configuration_snapshot_object_key_match(object_key): # Matches object keys like AWSLogs/123456789012/Config/us-east-1/2018/4/11/ConfigSnapshot/123456789012_Config_us-east-1_ConfigSnapshot_20180411T054711Z_a970aeff-cb3d-4c4e-806b-88fa14702hdb.json.gz return re.match('AWSLogs/(\d+)/Config/([\w-]+)/(\d+)/(\d+)/(\d+)/ConfigSnapshot/[^\\\]+$', object_key) def get_configuration_snapshot_accountid(match): print('AccountId:', match.group(1)) return match.group(1) def get_configuration_snapshot_region(match): return match.group(2) def get_configuration_snapshot_date(match): return datetime.date(int(match.group(3)), int(match.group(4)), int(match.group(5))) def add_partition(accountid_partition_value, region_partition_value, dt_partition_value, partition_location): execute_query('ALTER TABLE {table_name} ADD PARTITION {partition} location \'{partition_location}\''.format( table_name=TABLE_NAME, partition=build_partition_string(accountid_partition_value, region_partition_value, dt_partition_value), partition_location=partition_location)) def drop_partition(accountid_partition_value, region_partition_value, dt_partition_value): execute_query('ALTER TABLE {table_name} DROP PARTITION {partition}'.format( table_name=TABLE_NAME, partition=build_partition_string(accountid_partition_value, region_partition_value, dt_partition_value))) def build_partition_string(accountid_partition_value, region_partition_value, dt_partition_value): return "(accountid='{accountid_partition_value}', dt='{dt_partition_value}', region='{region_partition_value}')".format( accountid_partition_value=accountid_partition_value, dt_partition_value=dt_partition_value, region_partition_value=region_partition_value) def execute_query(query): print('Executing query:', query) query_output_location = 's3://aws-athena-query-results-{account_id}-{region}'.format( account_id=ACCOUNT_ID, region=os.environ['AWS_REGION']) start_query_response = athena.start_query_execution( QueryString=query, QueryExecutionContext={ 'Database': DATABASE_NAME }, ResultConfiguration={ 'OutputLocation': query_output_location, } ) print('Query started') is_query_running = True while is_query_running: get_query_execution_response = athena.get_query_execution( QueryExecutionId=start_query_response['QueryExecutionId'] ) query_state = get_query_execution_response['QueryExecution']['Status']['State'] is_query_running = query_state in ('RUNNING','QUEUED') if not is_query_running and query_state != 'SUCCEEDED': raise Exception('Query failed') print('Query completed')
⑤Lambdaのリソースベースポリシーの設定(可視化アカウントで実行)
ログアーカイブアカウントのS3イベントを受付られるよう、 Lambdaのリソースベースポリシー設定を行います。
変数定義
まずは環境に合わせて変数を設定します。
AWS_CONFIG_S3_BUCKET="" LAMBDA_FUNCTION=""
Lambdaのリソースベースポリシー設定
変数定義後、以下のコマンドを実行します。
aws lambda add-permission \ --function-name "${LAMBDA_FUNCTION}" \ --statement-id "${AWS_CONFIG_S3_BUCKET}" \ --action "lambda:InvokeFunction" \ --principal "s3.amazonaws.com" \ --source-arn "arn:aws:s3:::${AWS_CONFIG_S3_BUCKET}"
⑥S3イベントの設定(ログアーカイブアカウントで実施)
S3にオブジェクトが作成されたことをトリガーにLambdaが実行できるよう、S3イベントの設定を行います。
変数定義
まずは環境に合わせて変数を設定します。
AWS_CONFIG_S3_BUCKET="" LAMBDA_FUNCTION="" VISUALIZATION_AWS_ACCOUNT_ID=""
S3イベントの設定
変数定義後、以下のコマンドを実行します。
aws s3api put-bucket-notification-configuration \ --bucket "${AWS_CONFIG_S3_BUCKET}" \ --notification-configuration "{\"LambdaFunctionConfigurations\": [{\"Id\": \"${VISUALIZATION_AWS_ACCOUNT_ID}:function:${LAMBDA_FUNCTION}\",\"LambdaFunctionArn\": \"arn:aws:lambda:ap-northeast-1:${VISUALIZATION_AWS_ACCOUNT_ID}:function:${LAMBDA_FUNCTION}\",\"Events\": [\"s3:ObjectCreated:*\"]}]}"
動作確認
S3イベントが正常に動作するか確認します。
ConfigSnapshotを生成することができる以下のコマンドを実施後、 S3イベントをトリガーにLambdaが正常に動作するかを確認します。
aws configservice deliver-config-snapshot \ --delivery-channel-name $(aws configservice describe-delivery-channels \ --query "DeliveryChannels[].name" --output text)
Lambdaのログに「Query completed」が表示されていればOKです。
終わりに
今回は、「マルチアカウント環境のリソース情報を可視化してみる(AWS Config + Athena + QuickSight)」の第1弾となりました。 次回は、今回作成したAthenaの外部テーブルを基にAthenaのビューを作成していきます。
続き
【2/3】マルチアカウント環境のリソース情報を可視化してみる(AWS Config + Athena + QuickSight) - サーバーワークスエンジニアブログ