概要
AWS WAFのログで、以下を満たすようにCloudFormationで構築します。
- アクセスのブロック時にのみログ出力する
- 料金節約やログの視認性のため
- ヘッダーなどの情報をマスキングする
- トークンなどの情報を隠すため
WAFログ
従来
AWS WAFのログを出力するためには、今まではAmazon Kinesis Data Firehoseを使用して転送する必要がありました。
それが中々設定が面倒だったのですがつい最近、Firehose無しで直接WAFのログを出力することが可能になり、ずいぶん簡単にWAFのログを出力できるようになりました。
前提
WAFログの出力は、S3とCloudWatch Logs(とKinesis Data Firehose)が可能なのですが、本記事ではS3への出力を例に挙げます。
CloudWatch Logsへの出力も宛先を変更するだけでできます。
また上記で説明した、Firehose無しの方法でログ出力を行います。
コード
AWSTemplateFormatVersion: 2010-09-09 Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "Project Name" Parameters: - PJName - Label: default: "ApiGateway Resource ARN" Parameters: - ApiGatewayResourceArn Parameters: PJName: Type: String ApiGatewayResourceArn: Type: String Resources: ApiGatewayWebAclAssociation: Type: AWS::WAFv2::WebACLAssociation Properties: ResourceArn: !Ref ApiGatewayResourceArn WebACLArn: !GetAtt WAFWebACL.Arn WAFWebACL: Type: AWS::WAFv2::WebACL Properties: Name: !Sub "${PJName}-WebACL" DefaultAction: Block: {} Scope: "REGIONAL" VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Sub "${PJName}-WebACL" SampledRequestsEnabled: true WAFLogsS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "aws-waf-logs-${PJName}" BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True WAFLogConfig: Type: AWS::WAFv2::LoggingConfiguration Properties: LogDestinationConfigs: - !GetAtt WAFLogsS3Bucket.Arn ResourceArn: !GetAtt WAFWebACL.Arn LoggingFilter: DefaultBehavior: DROP Filters: - Behavior: KEEP Conditions: - ActionCondition: Action: BLOCK Requirement: MEETS_ALL RedactedFields: - SingleHeader: Name: "authorization"
解説
WAFWebACL(WebACLAssociation)
これはWAFをリソースにアタッチするためのものですが、例としてAPI Gatewayにアタッチする想定にしています。
アタッチ対象のリソースのARNはパラメータとして渡しています。
WAFWebACL(WebACL)
こちらがWAF本体の設定になります。
サンプルなので全部ブロックする設定にしていますが、実際はRulesなどで特定のIPからのアクセスは許可するように設定します。
WAFLogsS3Bucket(S3バケット)
これはWAFのログを転送するS3のバケット自体です。
注意点として、WAFのログを出力するバケット名は「aws-waf-logs-」で始まる必要があります。
でないと設定に失敗するため、忘れずに名前を決めましょう。
WAFLogsS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "aws-waf-logs-${PJName}"
WAFLogConfig(WAF LoggingConfiguration)
今回の肝である、WAFログ出力のための設定です。
(今まではこれがなく、Firehoseとそのためのやたら長いIAMロールを代わりに構築していました。。。)
※WAFではなくAmazon SESのメール送信ログですが、同じようなFirehoseをCloudFormation構築しているので、良かったらご覧下さい。
ブロック時のみのログ出力
まずは、今回は要件にあったように、「ブロック時にのみログ出力する」ようにします。
それが以下の部分になります。
LoggingFilter: DefaultBehavior: DROP Filters: - Behavior: KEEP Conditions: - ActionCondition: Action: BLOCK Requirement: MEETS_ALL
DefaultBehavior: DROP
にしておき、Filters
のConditions
に当てはまるときはBehavior: KEEP
する設定です。
Action: BLOCK
のとき、つまり、アクセスがブロックされたときにのみログ出力が行われるようになります。
Requirement: MEETS_ALL
というのは、List形式であるConditions
に指定した条件を全て満たすとき、という条件になります。
MEETS_ANY
とすることで、指定した条件のどれかに当てはまるとき、というような条件にすることができます。
今回は一つの条件なのでどちらでも構いません。
マスキング (RedactedFields)
また、ログ内の特定の情報をマスキング(Redact=墨塗り)することができます。
CloudFormationでマスキングできる種類は以下になります。
- UriPath
- QueryString
- SingleHeader
- Method
- JsonBody
詳しくは公式ドキュメントをご参照ください。
ここでは、「authorizationヘッダー」のマスキングをしてみます。
これでトークンなどの文字列をログに出力しないことができるようになります。
WAFのログには、基本的には本文(リクエストボディ)は出力されないため、今回はヘッダーを対象にしています。
RedactedFields: - SingleHeader: Name: "authorization"
RedactedFields
にSingleHeader
を指定し、Name: "authorization"
とすることで、このヘッダーの情報をマスキングすることができます。
実行
実際にブロック時のログの出力を確認してみます。
curl
domain
にエンドポイントのURL、method
にはメソッド名を入れてcurlで叩きます。
リクエストボディは適当ですが、-H "authorization: abcdef"
というようにauthorizationヘッダーに情報を格納して叩いています。
$ curl -i -X POST -H "Content-Type: application/json" -H "authorization: abcdef" -d '{"foo":"hoge"}' ${domain}/${method} HTTP/2 403 content-type: application/json content-length: 23 date: Sat, 12 Mar 2022 17:02:06 GMT x-amzn-requestid: abcdef-xxxx-xxxx-xxxx-abcdefghijk x-amzn-errortype: ForbiddenException ... ... ... {"message":"Forbidden"}
{"message":"Forbidden"}
というように、とりあえずブロックされることが確認できました。
WAFログ
数分待ってS3にログが出力されるのを確認し、ファイルをダウンロード・解凍して開きます。
すると、以下のように、authorizationの値が"value":"REDACTED"
というようにマスキングされて出力されました。
...,{"name":"Authorization","value":"REDACTED"},...
※authorizationの頭文字が大文字に変換されました (Authorization)。
※ログは長いので該当箇所のみ記載しています。
最後に
WAFのログ出力が簡単になったのはありがたいし、さらに出力の際に色々な設定ができるのは便利ですね。