AWS CDKでSPF、DKIM、DMARC設定をしたAmazon SES を作成する

## はじめに

NTTテクノクロス株式会社の渡邉洋平です。

今回はAWS CDKを使用して、「SPF、DKIM、DMARC設定を行ったAmazon SESを作成する方法」について解説します。
CDKでドメイン回りを設定するのは鬼門なのか、検索してもすぐに見つからない情報だったので、手を動かして実際に試してみました。

### 参考：SPF、DKIM、DMARC

送信元のなりすましを検出するための認証として「送信ドメイン認証」という技術が一般的にあります。

SPF、DKIM、DMARCの詳細は下記のような記事でも把握できるので割愛します。これら3つの対応をすべて実施することで、送信元の信頼性を高めることができます。

https://ent.iij.ad.jp/articles/172/


### 1. hostzoneの参照

※前提として、送信元メールアドレスを事前に同じアカウントのRoute53のホストゾーンに設定してあるものとします。

まずはじめに、指定済みのホストゾーンをCDKのコードから参照します。
- この例では、example.comがドメインとして定義されている既存のpublicHostZoneを参照(`fromLookup`)している。

```ts
    const domainName: string = "example.com";

    const hostedZone = route53.PublicHostedZone.fromLookup(
      this,
      "existHostZone",
      {
        domainName: domainName,
      }
    );

```

### 2. 適切なメール配信設定を行う

次に、Amazon SESの設定を行います。ここでは、ses.EmailIdentityを使用して、SESの設定を行っています。

```ts
const identity = new ses.EmailIdentity(this, 'Identity', {
  identity: ses.Identity.publicHostedZone(hostedZone),
  mailFromDomain: `bounce.${domainName}`,
});
```

#### SPF設定

Amazon SESとして必要な手順は以下の通りです。

https://docs.aws.amazon.com/ja_jp/ses/latest/dg/mail-from.html

CDKのL2 Constructとしては、上記の`mailFromDomain`設定を入力した時点で完了です。`ses.EmailIdentity`が自動的にホストゾーンを編集してくれます。これは非常に便利ですね！

#### DKIM設定

Amazon SESとして必要な手順は以下の通りです。

https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-email-authentication-dkim.html

実はDKIM設定もCDKのL2 Constructとしては、デフォルト値としてEasy DKIMを有効にしてくれる設定(dkimSigning: true)なので、これで十分であれば特別な設定は要りません。

BYODKIMなどを実施したい場合はこちらを参考にすると指定できます。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ses.DkimIdentity.html


#### DMARC

Amazon SESとして必要な手順は以下の通りです。
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-email-authentication-dmarc.html

こちらは現行のCDK(v2.75.0)のConstructでは未対応であるため、明示的にDMARC設定を行います。具体的には`route53.TxtRecord`を使用して、DMARCのTXTレコードを作成します。

```ts
new route53.TxtRecord(this, 'DmarcRecord', {
  zone: hostedZone,
  recordName: `_dmarc.${domainName}`,
  values: [`v=DMARC1; p=none; rua=mailto:dmarcreports@${domainName}`],
  ttl: Duration.hours(1),
});
```

これで、Route53のホストゾーンでサブドメインを定義し、Amazon SESの設定とDMARC設定が完了しました。

### コード

最後にこれまで説明したコードを改めて掲載します。

```ts
import { Duration, Stack, StackProps, RemovalPolicy } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as ses from "aws-cdk-lib/aws-ses";

export class Route53Stack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const domainName: string = "example.com";

    const hostedZone = route53.PublicHostedZone.fromLookup(
      this,
      "existHostZone",
      {
        domainName: domainName,
      }
    );

    const identity = new ses.EmailIdentity(this, "Identity", {
      identity: ses.Identity.publicHostedZone(hostedZone),
      mailFromDomain: `bounce.${domainName}`,
    });

    new route53.TxtRecord(this, "DmarcRecord", {
      zone: hostedZone,
      recordName: `_dmarc.${domainName}`,
      values: [
        `v=DMARC1; p=none; rua=mailto:dmarcreports@${domainName}`,
      ],
      ttl: Duration.hours(1),
    });

  }
}

```

#### Appendix. to CloudFormation Template

理解を深めるため、CDKからDumpしたCloudformationテンプレートも記載します。(一部はマスク済)

```yaml

Resources:
  IdentityDkimDnsToken133E6CCBA:
    Type: AWS::Route53::RecordSet
    Properties:
      Name:
        Fn::GetAtt:
          - Identity2D60E2CC
          - DkimDNSTokenName1
      Type: CNAME
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - Fn::GetAtt:
            - Identity2D60E2CC
            - DkimDNSTokenValue1
      TTL: "1800"
  IdentityDkimDnsToken2AD0AA6D9:
    Type: AWS::Route53::RecordSet
    Properties:
      Name:
        Fn::GetAtt:
          - Identity2D60E2CC
          - DkimDNSTokenName2
      Type: CNAME
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - Fn::GetAtt:
            - Identity2D60E2CC
            - DkimDNSTokenValue2
      TTL: "1800"
  IdentityDkimDnsToken3A6A24F78:
    Type: AWS::Route53::RecordSet
    Properties:
      Name:
        Fn::GetAtt:
          - Identity2D60E2CC
          - DkimDNSTokenName3
      Type: CNAME
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - Fn::GetAtt:
            - Identity2D60E2CC
            - DkimDNSTokenValue3
      TTL: "1800"
  Identity2D60E2CC:
    Type: AWS::SES::EmailIdentity
    Properties:
      EmailIdentity: example.com
      MailFromAttributes:
        MailFromDomain: bounce.example.com
  IdentityMailFromMxRecord4D92B544:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: bounce.example.com.
      Type: MX
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - 10 feedback-smtp.us-east-1.amazonses.com
      TTL: "1800"
  IdentityMailFromTxtRecord8A2CF0BC:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: bounce.example.com.
      Type: TXT
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - '"v=spf1 include:amazonses.com ~all"'
      TTL: "1800"
  DmarcRecord17E400DB:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: _dmarc.example.com.
      Type: TXT
      HostedZoneId: XXXXXXXXXXXXXXXXXXXX
      ResourceRecords:
        - '"v=DMARC1; p=none; rua=mailto:dmarcreports@example.com"'
      TTL: "3600"
```

## まとめ

このようにAWS CDKを使用することで、SPF、DKIM、DMARC設定を実施したAmazon SESを簡単に作成することができました。

今回紹介したCDKコードを使用すれば、独自のドメインでメール配信を行う際に必要な設定を簡単に行うことができます。ぜひこのコードを参考に、自分のプロジェクトでCDKを活用してください！