Route53のDNSクエリログをCloudWatch Logsに保存する仕組みをCloudFormationで作成する

概要

Route53にはDNSクエリログというのがあり、それをCloudWatch Logsに保存する仕組みをCloudFormationで作成するのは実は大変でした。

それが少し簡単になったため、今回CloudFormationで作成する方法を書いてみました。


やること

Route53のDNSクエリログをCloudWatch Logsに保存する仕組みを、CloudFormationで構築します。


ポイント

同じような仕組みとして、ECSイベントやRDSイベントをCloudWatch Logsに転送する仕組みをCloudFormationで作成する方法を記事にしています。

タイトルだけだとRoute53とECS,RDSで関連性が薄いようにも見えますが、共通点が一緒なのです。

go-to-k.hatenablog.com


その共通点というのが、「リソースポリシー」です。

リソースポリシーの説明も以下の記事で記載しています。

go-to-k.hatenablog.com


ポイントなのが、CloudWatch Logsのロググループにリソースポリシーを与えてあげないと、DNSクエリログが転送されないのです。


そして、そのリソースポリシーが今まではCloudFormation未対応だったのが、ついに対応されるようになったのです。


コード

GitHubにもあります。

github.com

AWSTemplateFormatVersion: "2010-09-09"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Domain"
        Parameters:
          - Domain
      - Label:
          default: "LogName"
        Parameters:
          - LogName

Parameters:
  Domain:
    Type: String
  LogName:
    Type: String

Resources:
  Route53HostedZone:
    Type: AWS::Route53::HostedZone
    DependsOn: ResourcePolicyForLogs
    Properties:
      HostedZoneConfig:
        Comment: !Ref Domain
      Name: !Ref Domain
      QueryLoggingConfig:
        CloudWatchLogsLogGroupArn: !GetAtt DNSQueryLogGroup.Arn

  DNSQueryLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/route53/${LogName}"

  ResourcePolicyForLogs:
    Type: AWS::Logs::ResourcePolicy
    Properties:
      PolicyName: "DNSQueryLog-ToLogs-ResourcePolicy"
      PolicyDocument: !Sub 
        - |
          {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Principal": {
                  "Service": "route53.amazonaws.com"
                },
                "Action": [
                  "logs:CreateLogStream",
                  "logs:PutLogEvents"
                ],
                "Resource": "${CloudWatchLogsLogGroupArn}"
              }
            ]
          }
        - CloudWatchLogsLogGroupArn: !GetAtt DNSQueryLogGroup.Arn


解説

AWS::Route53::HostedZone

Route53のホストゾーン作成になります。

ここで大事なのはQueryLoggingConfigで、CloudWatchLogsLogGroupArnにログのARNを指定します。

  Route53HostedZone:
    Type: AWS::Route53::HostedZone
    DependsOn: ResourcePolicyForLogs
    Properties:
      HostedZoneConfig:
        Comment: !Ref Domain
      Name: !Ref Domain
      QueryLoggingConfig:
        CloudWatchLogsLogGroupArn: !GetAtt DNSQueryLogGroup.Arn


また、Route53HostedZoneDNSQueryLogGroupのARNを参照しているため依存関係にあり、ロググループが作られてからホストゾーン作成が始まります。

ところが、Route53HostedZoneのリソース定義にリソースポリシーであるResourcePolicyForLogsへの参照はなく依存関係がないため、ホストゾーンとリソースポリシーはどちらが先に作られるかわかりません。


しかし、ホストゾーンでクエリログ設定をするロググループにはリソースポリシーが付与されていないといけないため、DependsOn: ResourcePolicyForLogsとすることで、明示的に依存関係を持たせてリソースポリシーが作られてからホストゾーンが作成されるようにしています。

(なくても成功しました、、、)

  Route53HostedZone:
    Type: AWS::Route53::HostedZone
    DependsOn: ResourcePolicyForLogs


AWS::Logs::ResourcePolicy

AWS::Logs::ResourcePolicyによってリソースポリシーを作成しています。

  ResourcePolicyForLogs:
    Type: AWS::Logs::ResourcePolicy
    Properties:
      PolicyName: "DNSQueryLog-ToLogs-ResourcePolicy"
      PolicyDocument: !Sub 
        - |
          {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Principal": {
                  "Service": "route53.amazonaws.com"
                },
                "Action": [
                  "logs:CreateLogStream",
                  "logs:PutLogEvents"
                ],
                "Resource": "${CloudWatchLogsLogGroupArn}"
              }
            ]
          }
        - CloudWatchLogsLogGroupArn: !GetAtt DNSQueryLogGroup.Arn


このリソースポリシーのPrincipalには、Serviceとしてroute53.amazonaws.comを指定する必要があります。


そして、お決まりのログ転送に必要なアクション(logs:CreateLogStream, logs:PutLogEvents)を記述し、Resourceには転送先ログのARNを指定します。

このResource指定には、組み込み関数SubのList形式の記法により、変数で指定しています。

Resourceはワイルドカードの指定も可能です。


またリソースポリシーはJSON形式での記述になりますが、yamlJSONを扱う時、よく1行の文字列にして使用しているケースを見かけることがあります。

しかし、上記のように「|」を使うことで、そのままJSONの形式でyamlに記述することが可能になります。


注意

本題とは関係ないのですが、上記CloudFormationスタックは、バージニアリージョン(us-east-1)での実行を忘れないようにしてください。

Route53のDNSクエリログに指定するロググループはus-east-1での作成が必須であるからです。


たとえば東京リージョンなどで実行しようとすると以下のようなエラーメッセージに遭遇します。

Resource handler returned message: "The ARN for the CloudWatch Logs log group is invalid. (省略)


CloudFormationの公式ドキュメントなどにも載っています。

docs.aws.amazon.com


最後に

CloudFormationでCloudWatch Logsのリソースポリシーを、カスタムリソースを使わずに作成できるようになったのを数ヶ月遅れで知ったため、このような記事を書いてみました。