CloudFormation で AWS WAF ログを Kinesis Firehose を経由して別アカウントの S3 バケットに送る
概要
やりたいこと
前回、AWS WAF ログを S3 バケットに保存した。このログを別アカウントの S3 バケットに保存したい。
クロスアカウントアクセスというらしい。
状況を以下とする。
- アカウント A : ここの S3 バケットに保存したい
- アカウント B : AWS WAF が設定されている
クロスアカウントアクセス
公式より実現方法は 3 つある。今回は1の方法を採用。
- リソースベースのポリシーと AWS Identity and Access Management (IAM) ポリシー (S3 バケットオブジェクトへのプログラムによるアクセス専用)
- リソースベースのアクセスコントロールリスト (ACL) と IAM ポリシー (S3 バケットオブジェクトへのプログラムによるアクセス専用)
- クロスアカウント IAM ロール (S3 バケットオブジェクトへのプログラムおよびコンソールによるアクセス)
リソースベースポリシーと IAM ポリシー
クロスアカウントアクセスしたい場合、リソースベースポリシーと IAM ポリシー両方で許可しなければならない。
- 単一アカウント内アクセス:IAMポリシーかリソースベースポリシーのどちらかの許可が必要
- クロスアカウントアクセス:IAMポリシーとリソースベースポリシーの両方の許可が必要
今回のリソースベースのポリシーは S3 のバケットポリシーとなる。
AWS リソースごとのリソースベースポリシーの対応状況はこちら参照。
CloudFormation
CloudFormation 準備
こちら参照。
アカウント A はコンソールより操作する。
アカウント B は CloudFormation で実行する。
mkdir waf-v2-basepolicy cd waf-v2-basepolicy
スタック名も今回 aws-waf-v2-basepolicy とする。
アカウントA : S3 バケット作成
アカウント B にて、以下の名前で S3 バケットを作成。バケットポリシーは後ほど作成する。
- waf-test-cross-account
アカウントB : Kinesis Data Firehose に IAM ロールを割り当てる
公式より、Kinesis Data Firehose に IAM ロールを割り当てるポリシーは以下。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "firehose.amazonaws.com" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId":"account-id" } } } ] }
CloudFormation で作成する。
vi waf_v2.yaml
AWSTemplateFormatVersion: 2010-09-09 Description: Create WebACL example Resources: FirehoseRole: Type: AWS::IAM::Role Properties: Path: '/' AssumeRolePolicyDocument: #Kinesis Firehose に IAM を割り当てるため Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - firehose.amazonaws.com Action: - sts:AssumeRole Condition: StringEquals: sts:ExternalId: !Sub ${AWS::AccountId}
スタック作成。
aws cloudformation create-stack --stack-name waf-v2-basepolicy --region ap-northeast-1 --template-body file://waf_v2.yaml --capabilities CAPABILITY_NAMED_IAM
アカウントB : アカウント A の S3 バケットアクセス用の IAM ポリシー作成
公式より、Kinesis が S3 バケットへアクセスするのに必要なポリシーは以下。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*" ] }, { "Effect": "Allow", "Action": [ "kinesis:DescribeStream", "kinesis:GetShardIterator", "kinesis:GetRecords", "kinesis:ListShards" ], "Resource": "arn:aws:kinesis:region:account-id:stream/stream-name" }, { "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:GenerateDataKey" ], "Resource": [ "arn:aws:kms:region:account-id:key/key-id" ], "Condition": { "StringEquals": { "kms:ViaService": "s3.region.amazonaws.com" }, "StringLike": { "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::bucket-name/prefix*" } } }, { "Effect": "Allow", "Action": [ "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:region:account-id:log-group:log-group-name:log-stream:log-stream-name" ] }, { "Effect": "Allow", "Action": [ "lambda:InvokeFunction", "lambda:GetFunctionConfiguration" ], "Resource": [ "arn:aws:lambda:region:account-id:function:function-name:function-version" ] } ] }
ただし、クロスアカウントアクセスの場合、Action に “s3:PutObjectAcl” が必要なる。
上記を考慮した firehose_delivery_policy を作成し、firehose_delivery_policy をアタッチした IAM Role を作成する。
AWSTemplateFormatVersion: 2010-09-09 Description: Create WebACL example Parameters: BucketName: Type: String Description: The name for the bucket. Default: waf-test-cross-account Resources: FirehoseRole: Type: AWS::IAM::Role Properties: Path: '/' Policies: - PolicyName: firehose_delivery_policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - s3:AbortMultipartUpload - s3:GetBucketLocation - s3:GetObject - s3:ListBucket - s3:ListBucketMultipartUploads - s3:PutObject - s3:PutObjectAcl Resource: - !Sub arn:aws:s3:::${BucketName} - !Sub arn:aws:s3:::${BucketName}/* - Effect: "Allow" Action: - kinesis:DescribeStream - kinesis:GetShardIterator - kinesis:GetRecords - kinesis:ListShards Resource: - !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME% - Effect: "Allow" Action: - kms:GenerateDataKey - kms:Decrypt Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3 Condition: StringEquals: kms:ViaService: !Sub s3.${AWS::Region}.amazonaws.com StringLike: kms:EncryptionContext:aws:s3:arn: - !Sub arn:aws:s3:::${BucketName}/* - !Sub arn:aws:s3:::${BucketName}/%FIREHOSE_BUCKET_PREFIX%* - Effect: "Allow" Action: - logs:PutLogEvents Resource: - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/aws-waf-logs-${AWS::Region}-${WebACLName}:log-stream:* - Effect: "Allow" Action: - lambda:InvokeFunction - lambda:GetFunctionConfiguration Resource: - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION% - Effect: "Allow" Action: - kms:Decrypt Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/%SSE_KEY_ID% Condition: StringEquals: kms:ViaService: kinesis.%REGION_NAME%.amazonaws.com StringLike: kms:EncryptionContext:aws:kinesis:arn: !Sub arn:aws:kinesis:%REGION_NAME%:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME%
構文チェックして反映。
aws cloudformation validate-template --template-body file://waf_v2.yaml aws cloudformation deploy --stack-name aws-waf-v2-basepolicy --template-file waf_v2.yaml --capabilities CAPABILITY_NAMED_IAM
IAM Role が作成され firehose_delivery_policy がアタッチされていることを確認。
この ARN は アカウントAの S3 バケットポリシーで利用するためメモしておく。

アカウントB : Kinesis Firehose を作成し、IAM Role と WebACL を紐付ける
CloudFormation 全体像。WebACLのルールは省略。
AWSTemplateFormatVersion: 2010-09-09 Description: Create WebACL example Parameters: WebACLName: Description: The Name of WebACL. Type: String Default: ExampleWebACL BucketName: Type: String Description: The name for the bucket. Default: waf-test-cross-account Resources: FirehoseRole: Type: AWS::IAM::Role Properties: Path: '/' AssumeRolePolicyDocument: # Kinesis FirehoseにIAM Roleを割り当てるため Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - firehose.amazonaws.com Action: - sts:AssumeRole Condition: StringEquals: sts:ExternalId: !Sub ${AWS::AccountId} Policies: - PolicyName: firehose_delivery_role PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - s3:AbortMultipartUpload - s3:GetBucketLocation - s3:GetObject - s3:ListBucket - s3:ListBucketMultipartUploads - s3:PutObject - s3:PutObjectAcl Resource: - !Sub arn:aws:s3:::${BucketName} - !Sub arn:aws:s3:::${BucketName}/* - Effect: "Allow" Action: - kinesis:DescribeStream - kinesis:GetShardIterator - kinesis:GetRecords - kinesis:ListShards Resource: - !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME% - Effect: "Allow" Action: - kms:GenerateDataKey - kms:Decrypt Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3 Condition: StringEquals: kms:ViaService: !Sub s3.${AWS::Region}.amazonaws.com StringLike: kms:EncryptionContext:aws:s3:arn: - !Sub arn:aws:s3:::${BucketName}/* - !Sub arn:aws:s3:::${BucketName}/%FIREHOSE_BUCKET_PREFIX%* - Effect: "Allow" Action: - logs:PutLogEvents Resource: - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/aws-waf-logs-${AWS::Region}-${WebACLName}:log-stream:* - Effect: "Allow" Action: - lambda:InvokeFunction - lambda:GetFunctionConfiguration Resource: - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION% - Effect: "Allow" Action: - kms:Decrypt Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/%SSE_KEY_ID% Condition: StringEquals: kms:ViaService: kinesis.%REGION_NAME%.amazonaws.com StringLike: kms:EncryptionContext:aws:kinesis:arn: !Sub arn:aws:kinesis:%REGION_NAME%:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME% FirehoseDeliveryStream: Type: "AWS::KinesisFirehose::DeliveryStream" Properties: DeliveryStreamName: !Sub aws-waf-logs-${BucketName}-${AWS::Region}-${WebACLName} DeliveryStreamType: DirectPut S3DestinationConfiguration: BucketARN: !Sub arn:aws:s3:::${BucketName} BufferingHints: IntervalInSeconds: 300 SizeInMBs: 5 CompressionFormat: GZIP RoleARN: !GetAtt FirehoseRole.Arn WebACL: Type: AWS::WAFv2::WebACL Properties: Name: !Sub ${WebACLName} Scope: REGIONAL Description: This is an example WebACL DefaultAction: Allow: {} # Allow or Block or Count VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: !Sub ${WebACLName}
構文チェックして反映。
aws cloudformation validate-template --template-body file://waf_v2.yaml aws cloudformation deploy --stack-name aws-waf-v2-basepolicy --template-file waf_v2.yaml --capabilities CAPABILITY_NAMED_IAM
Kineis Firehose が作成され、作成した IAM がアタッチされていることを確認。

WebACL のLogging から Kinesis Firehose を有効化する。
アカウントA : バケットポリシー作成
公式より、以下のバケットポリシーを指定する必要があるとのこと。
{ "Version": "2012-10-17", "Id": "PolicyID", "Statement": [ { "Sid": "StmtID", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::accountA-id:role/iam-role-name" }, "Action": [ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*" ] } ] }
コンソールから操作。
- バケットを選択
- アクセス権限
- ブロックパブリックアクセス : オフ
- バケットポリシーに以下を記入。
- Principal : 上記で確認した Role の ARN
- Resource : S3 バケット名
{ "Version": "2012-10-17", "Id": "Policy1589030458582", "Statement": [ { "Sid": "Stmt1589030372922", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111111111111:role/waf-v2-basepolicy-FirehoseRole-1IZH91LWU3BNE" }, "Action": [ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:GetObject", "s3:GetObjectAcl", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::waf-test-cross-account/*", "arn:aws:s3:::waf-test-cross-account" ] } ] }
AWS WAF 側でアラートを発生させて、S3 バケットに保存されていること。
エラー
Invalid principal in policy
「アカウントA : バケットポリシー作成」のところでこのエラーに遭遇。
原因は、既にアカウント A で存在しない arn を指定していたため。CloudFormation で作り直した際に arn の値が変わってしまっていた。
アカウント A 側も CloudFormation か Terraform で管理しないと。。
ディスカッション
コメント一覧
まだ、コメントがありません