概要
タイトルの通りなのですが、ついにCloudWatch LogsのリソースポリシーがCloudFormationで公式に作成できるようになりました・・・!(数ヶ月前)
リソースポリシー
あるサービスからCloudWatch Logsにログを転送するとき、ものによってはCloudWatch Logsにリソースポリシー(リソースベースのポリシー)を設定しなければいけないケースがありました。
リソースポリシーとは、その名のように操作対象のリソースに対して設定するポリシーです。
これはリソース(操作対象)にアタッチするもので、「自分(アタッチされた自身)に対して、誰が、何をできる」というポリシーになります。
一方でその対になるものとして、アイデンティティベースのポリシーがあります。
こちらは「自分(アタッチされた自身)が、何に対して、何をできるか」といったポリシーで、ユーザーやアプリケーションのリソースなどいわゆるアクターにつけるポリシーになります。
まずポリシーと言ったら思い浮かぶであろうIAMポリシーが例に挙がります。
CloudWatch Logsでのリソースポリシー例
CloudWatch Logsでリソースポリシーが登場する例としては、例えば以下のようなものがよく挙げられます。
- Route53のDNSクエリログ設定
- ホストゾーンへのDNSクエリログをCloudWatch Logsに転送する
- EventBridgeからのイベント転送
- RDSイベントやECSイベントを、EventBridge経由でCloudWatch Logsに転送する
上記の方法に関して、以下の記事で書いてみたので、良かったらぜひご覧ください。
- Route53のDNSクエリログ設定
- EventBridgeからのイベント転送
上記のようなログ保存・転送の際には、CloudWatchのロググループに対してリソースポリシーを割り当てなければなりませんでした。
※正確には、ロググループに割り当てるというより、そのロググループへの操作を許可するリソースポリシーを作成することによって、対象ロググループへのアクセスが可能になります。
CloudFormationでの今まで
自分はAWSでのリソース作成はCloudFormationで作成することが多いのですが、実はこのリソースポリシーですが、今までCloudFormationでの作成は対応しておりませんでした。
そのためコンソール以外で行う場合、CLIでPutResourcePolicy (put-resource-policy) で作成するか、CloudFormationのカスタムリソース(Lambda-backedカスタムリソース)で行うのが一般的でした。
CloudFormationのカスタムリソースの例(Python)
ResourcePolicyLambda: Type: "AWS::Lambda::Function" Properties: Handler: "index.handler" Role: Fn::GetAtt: - "LambdaExecutionRole" - "Arn" Runtime: "python3.6" Code: ZipFile: | import json import cfnresponse import boto3 from botocore.exceptions import ClientError client = boto3.client("logs") def PutPolicy(arns, policyname, service): arn_str = '","'.join(arns) arn = "[\"" + arn_str + "\"]" response = client.put_resource_policy( policyName=policyname, policyDocument="{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"" + service + "\"},\"Action\":[\"logs:CreateLogStream\",\"logs:PutLogEvents\"],\"Resource\":"+ arn + "}]}", ) return def DeletePolicy(policyname): response = client.delete_resource_policy( policyName=policyname ) return def handler(event, context): CloudWatchLogsLogGroupArns = event['ResourceProperties']['CloudWatchLogsLogGroupArn'] PolicyName = event['ResourceProperties']['PolicyName'] ServiceName = event['ResourceProperties']['ServiceName'] responseData = {} try: if event['RequestType'] == "Delete": DeletePolicy(PolicyName) if event['RequestType'] in ["Create", "Update"]: PutPolicy(CloudWatchLogsLogGroupArns, PolicyName, ServiceName) responseData['Data'] = "SUCCESS" status=cfnresponse.SUCCESS except ClientError as e: responseData['Data'] = "FAILED" status=cfnresponse.FAILED print("Unexpected error: %s" % e) cfnresponse.send(event, context, status, responseData, "CustomResourcePhysicalID") EventsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/events/TestEvents RetentionInDays: 90 AddResourcePolicyForEventsToLogs: Type: Custom::AddResourcePolicy Version: "1.0" Properties: ServiceToken: !GetAtt ResourcePolicyLambda.Arn CloudWatchLogsLogGroupArn: - !GetAtt EventsLogGroup.Arn PolicyName: "TestEventsPolicy" ServiceName: "events.amazonaws.com"
ついにCloudFormation対応!
まあ上記のようなカスタムリソースは面倒(だけどやるしかなかった)だし、他リソースをCloudFormationで作っているのだからCLIで行うのもなあって感じな現状でした。
また、AWS公式のCloudFormationのロードマップとしても、リソースポリシー作成(PutResourcePolicy)への対応がIssueとして挙げられていました。
そして、ついに・・・!
というかふと、たまたま見ていたら見つけただけなのですが、、、
CloudWatch LogsのリソースポリシーがCloudFormationでの作成に対応していました!!
いや、知らなかったです。しかも半年くらい前でした。リリース情報を見逃していたのでしょうか。。。
CloudFormationでの書き方
上記公式を見れば明らかなのですが、PolicyNameとPolicyDocumentだけで非常にシンプルです。
Type: AWS::Logs::ResourcePolicy Properties: PolicyDocument: String PolicyName: String
具体例
具体的な使用例として、EventBridgeからのイベント転送に対応するリソースポリシーの例を挙げておきます。
CloudWatchLogsLogGroupArn
には、転送先のロググループのARNを与えてあげてください。
AWSTemplateFormatVersion: "2010-09-09" Metadata: "AWS::CloudFormation::Interface": ParameterGroups: - Label: default: "CloudWatch Logs LogGroup Arn" Parameters: - CloudWatchLogsLogGroupArn Parameters: CloudWatchLogsLogGroupArn: Type: String Resources: EventBridgeToCWLogsPolicy: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: EventBridgeToCWLogsPolicy PolicyDocument: !Sub | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${CloudWatchLogsLogGroupArn}" } ] }
注意
これでいくらでもリソースポリシーを複製できる!と思いきや、実はAWSアカウントの各リージョンにつき10個までしかポリシーを生成できないという制限があります。
上記CloudFormationガイドより引用
An account can have up to 10 resource policies per AWS Region.
そのためいくらでも作れるわけではないので、リソースポリシーが必要な各ログには命名規則をもとにログを作成し、リソースポリシーのステートメントのResource(ログARNを指定)ではワイルドカードを駆使してポリシー作成するのがオススメです。
- 例)Route53のDNSクエリログ
- 例)EventBridgeからのイベントログ
それかワイルドカードではなく、ResourceはList指定ができるので、必要なログARNを全部渡して配列形式で指定してあげるのも可能です。(動的に個数を増減させるのはCloudFormationではやりづらいですが)
最後に
もっと具体的な使用例・作成例などを別の記事で書いてみようと思います。
でもなぜ気づけなかったのか・・・
※今回のCloudFormationテンプレートはGitHubの方にも載せてあります。