CloudWatch Logsのリソースポリシー作成がCloudFormationに対応していた!

概要

タイトルの通りなのですが、ついに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クエリログ設定

go-to-k.hatenablog.com


  • EventBridgeからのイベント転送

go-to-k.hatenablog.com


上記のようなログ保存・転送の際には、CloudWatchのロググループに対してリソースポリシーを割り当てなければなりませんでした。

※正確には、ロググループに割り当てるというより、そのロググループへの操作を許可するリソースポリシーを作成することによって、対象ロググループへのアクセスが可能になります。


CloudFormationでの今まで

自分はAWSでのリソース作成はCloudFormationで作成することが多いのですが、実はこのリソースポリシーですが、今までCloudFormationでの作成は対応しておりませんでした。


そのためコンソール以外で行う場合、CLIPutResourcePolicy (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での作成に対応していました!!


いや、知らなかったです。しかも半年くらい前でした。リリース情報を見逃していたのでしょうか。。。

github.com


CloudFormationでの書き方

docs.aws.amazon.com

上記公式を見れば明らかなのですが、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の方にも載せてあります。

github.com