EC2のWebサーバーのIPアドレスを固定せず、起動時にRoute53のレコードを自動更新（自作DDNS？）

2024.6.2 IMDSV2（インスタンスメタデータV2）の利用パターンを追加

# 作業の経緯

実験用のAWSのEC2上のWebサーバーでhttpsを有効化するため、固定IPアドレス(Elastic IP）を使用しています。
サーバーは使用する時だけ起動してコスト削減を図っているのですが、ElasticIPはずっと課金されてしまうため、これを節約したいというのが今回の経緯です。

:::note warn
~~この記事で使用したEC2インスタンスはかなり古いモノですので、メタデータのバージョンがIMDSv1です。~~
IMDSv2の場合のスクリプトを追加しました。

IMDSv1は現在非推奨となっており、今年中には新しいインスタンスでIMDSv2が強制されるようになるはずですが、IMDSv2ではパブリックIPアドレスの取得方法が変わっています。
~~この手順は今後確かめて、改めて掲載しようと思います。~~
:::

## 作業前の状態

- EC2インスタンスでWebサーバーを構築
- ElasticIPを確保して上記インスタンスに割り当て
- お名前.comで独自ドメインを取得してRoute53サービスに権限を移譲
- Route53のホストゾーンにサーバー名を登録
- Let's Encryptで上記サーバー名の証明書を作成

## 作業後のイメージ

- ElasticIPを解放してサーバーのIPアドレスを動的割り当てとする
- サーバー起動時にパブリックIPアドレスを取得する
- 取得したパブリックIPアドレスでRoute53のレコードを自動更新する

# 作業手順

## パブリックIPアドレスの取得

EC3インスタンス上でパブリックIPアドレスを取得するには、以下のURLでメタデータを取得します。
このIPアドレスはメタデータを取得するために特別に割り当てられているようです。

【IMDSv1の場合】
```bash
curl -H "Metadata-Flavor: AWS-V2" http://169.254.169.254/latest/meta-data/public-ipv4
```
【IMDSv2の場合】
IMDSv2では、最初にPUTリクエストで一時トークンを生成、トークンを渡してメタデータを取得します。
```bash
(一時トークン)
curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds:300" http://169.254.169.254/latest/api/token
(結果例)
AQAAAP3MH9PAkcyqtZmB_S_q9n5Ah9eX8YawSmzb0n8L5UcKHpLJLQ==
(メタデータ取得)
curl -X GET -H "X-aws-ec2-metadata-token:AQAAAP3MH9PAkcyqtZmB_S_q9n5Ah9eX8YawSmzb0n8L5UcKHpLJLQ==" http://169.254.169.254/latest/meta-data/public-ipv4
```


## レコード更新のための権限付与

### IAMポリシーの作成

Route53のレコードを更新するためのIAMポリシーを作成する必要があります。

- IAMサービスを開く
- [アクセス管理]-[ポリシー]を開く
- [ポリシーの作成]を選択
- サービスの選択でRoute53を選ぶ
- アクションの選択でChangeResourceRecordSetsを選ぶ
- リソースARNにarn:aws:route53:::hostedzone/[ホストゾーンID]を選ぶ（ホストゾーンIDはRoute53サービスで自分のゾーンを選ぶと表示されている）
- ポリシー名を付けて保存

```json:IAMポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/<ホストゾーンID>"
        }
    ]
}
```

### IAMロールの作成

EC２にポリシーを割り当てるためにIAMロールを作成します。

- IAMサービスを開く
- [アクセス管理]-[ロール]を開く
- [ロールの作成]を選択
- 信頼されたエンティティタイプで[AWSサービス]を選択
- ユースケースで[EC2]を選ぶ
- 許可ポリシーとして、前項で作成したポリシーを選択する
- ロール名を付けて保存

### EC2インスタンスへの割り当て

- EC2サービスを開く
- Webサーバーのインスタンスを選択
- [アクション]-[セキュリティ]-[IAMロールの変更]を選択
- 前項で作成したIAMロールを選択する

※もともとEC2にIAMロールを割り当てている場合は、そのロールに前述の手順で作成したポリシーを割り当てるか、新しいIAMロールに必要なポリシーを割り当てる必要があります。

## Route53のレコード更新

### AWS-CLIのインストール

Route53のレコードを更新するには、AWS-CLIコマンドを使用します。
私が使っているUbuntuのAMIにはデフォルトでAWS-CLIが入っていないのでインストールします。

```bash
sudo apt install aws
```
### Route53レコード更新

AWS-CLIのコマンドは以下のような感じです。

```bash
aws route53 change-resource-record-sets --hosted-zone-id <Route５３のゾーンID> --change-batch {"Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "<サーバーのFQDN>",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "<パブリックIPv4アドレス>"
          }
        ]
      }
    }
  ]
}
```

### 自動更新のスクリプト作成

レコードの更新を自動化するため、スクリプトを作成します。

【IMDSv1の場合】
```bash:update.sh
#!/bin/bash

PUBIP=`curl -H "Metadata-Flavor: AWS-V2" http://169.254.169.254/latest/meta-data/public-ipv4`

ZONEID=${1:-"<ホストゾーンID>"}

JSON="{\"Changes\": [
    {
      \"Action\": \"UPSERT\",
      \"ResourceRecordSet\": {
        \"Name\": \"<サーバーのFQDN>\",
        \"Type\": \"A\",
        \"TTL\": 300,
        \"ResourceRecords\": [
          {
            \"Value\": \"$PUBIP\"
          }
        ]
      }
    }
  ]
}"
```
【IMDSv2の場合】
```bash
#!/bin/bash

# Route 53のホストゾーンID
ZONEID=${1:-"ZZZZAAAABBBBB11111"}
# サーバーのFQDN
FQDN=www.example.com

# パブリックIPv4アドレスを取得する
TOKEN=`curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds:300" http://169.254.169.254/latest/api/token`
PUBIP=`curl -X GET -H "X-aws-ec2-metadata-token:${TOKEN}" http://169.254.169.254/latest/meta-data/public-ipv4`

echo $PUBIP

# Route53のレコードを更新するためのJSON
JSON="{\"Changes\": [
    {
      \"Action\": \"UPSERT\",
      \"ResourceRecordSet\": {
        \"Name\": \"${FQDN}\",
        \"Type\": \"A\",
        \"TTL\": 300,
        \"ResourceRecords\": [
          {
            \"Value\": \"$PUBIP\"
          }
        ]
      }
    }
  ]
}"

# aws-cliコマンドによりRoute53のレコードを更新する
aws route53 change-resource-record-sets --hosted-zone-id $ZONEID --change-batch  "$JSON"
```

このシェルスクリプトを実行すると、EC2インスタンスのパブリックIPアドレスをホストゾーンのサーバー名のAレコードに登録します。

```bash
chmod +x update.sh
./update.sh <ホストゾーンID>
```

### サービス登録

サーバー起動時にシェルを自動実行するため、サービス定義を作成して登録します。

```bash:/etc/systemd/system/dns-update.service
[Unit]
Description=Update Route53 Record to my address.
After=network.target

[Service]
Type=simple
User=root
ExecStart=/home/ubuntu/update.sh

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl enable dns-update
```

以上で、サーバー起動時にパブリックIPアドレスを取得して更新します。

# 補足
EC2のインスタンス生成時にAmazon Linux 2023を使用すると、デフォルトでIMDSv2のみが有効になっており、IMDSv1は利用できません。
古いAMI(Amazon Linux 2など)を利用している場合は両方が有効になっていますが、セキュリティを向上するためにIMDSv1を無効化することが推奨されています。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html

## EC2インスタンスのIMDSをv2のみに制限する

- インスタンス一覧でインスタンスを選択し、「アクション」-「インスタンスの設定」-「インスタンスメタデータオプションの変更」を選択する

![スクリーンショット 2024-06-02 17.29.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570192/c78a206c-3374-3297-19d3-8e363d14eedb.png)

- 「IMDSv2」の欄を[Optional]から[必須]に変更する

![スクリーンショット 2024-06-02 17.37.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570192/c3e04ab8-e938-5bf7-798d-37f893e1786e.png)