LetsEncrypt証明書を更新するために一時的にRoute53のルーティングポリシーを位置情報からシンプルに戻す（=海外からのアクセス許可）

# 背景

- ドメスティックなサービスの管理サイトのように日本国内利用者しか利用しないのが前提になっている時に海外アクセスを遮断したい｡
    - Route53のルーティングポリシー｢位置情報｣で簡単に実現できる
- ところが､
    - LetsEncrypt証明書を使用している場合､海外サーバからのアクセスが許可されていないと`certbot renew`でエラーが発生する｡
    - そこで､LetsEncrypt証明書を更新するために一時的に海外アクセスを許可したい
        - 具体的にはRoute53のルーティングポリシーを位置情報からシンプルに変更して､証明書更新が終わったら位置情報に戻す
    - もちろん手動でなく自動で実現したい

# 実現できる?

そもそも実現できるのか？という点ですが､YESです｡

# 環境

- Route53でドメイン管理している
- EC2上にWebサーバに証明書を割り当てている
- 証明書にLetsEncrpt利用
- aws cli利用

# 手順

## 準備

- Route53で更新したいドメインのゾーンID取得
- Route53レコード更新用のIAMポリシー作成
- IAMユーザ作成してポリシー割当
    - もちろんEC2ロールにポリシーを直接割り当ててもよいです
- EC2上のaws cliで上記IAMユーザのアカウントID/シークレットIDを割り当てたプロファイルを作成
- Route53レコード更新用の定義ファイルを作成
- 一連の処理を行うスクリプトを作成
- スクリプトをcronで定期実行する

## ゾーンID取得

Route53のドメインの編集画面で参照できます｡

[![Image from Gyazo](https://i.gyazo.com/5c4e1258c8fd48cb73403c90f7605a74.png)](https://gyazo.com/5c4e1258c8fd48cb73403c90f7605a74)

## Route53レコード更新用のIAMポリシー作成､IAMユーザ作成､awsプロファイル作成

以下のIAMポリシーを作成してIAMユーザに割り当てます｡
サーバ上にIAMユーザのawsプロファイルを作成(`aws configure --profile プロファイル名`)します｡

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "route53:ListResourceRecordSets"
            ],
            "Resource": "arn:aws:route53:::hostedzone/ホストゾーンID"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "route53:ListHostedZones",
            "Resource": "*"
        }
    ]
}
```

## Route53レコード更新用の定義ファイルを作成

route53のレコードは`change-resource-record-sets`コマンドを使って更新します｡

[AWS CLI を使用して Amazon Route 53 にシンプルなリソースレコードセットを作成する](https://aws.amazon.com/jp/premiumsupport/knowledge-center/simple-resource-record-route53-cli/)

ここで落とし穴ですが､マニュアルを読むとアクションには`UPSERT`（なければ新規､あれば更新）を使えば良いように思いますが､`UPSERT`これは`CNAME`レコードでしか動作しません｡AレコードでUPSERTしようとした場合､以下の実行エラーになります｡

```bash
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation: 
[RRSet with DNS name サブドメイン名., type A cannot be created as other RRSets exist with the same name and type.]
```

この制限は以下に記載されていました｡

[AWS CLI を使用して Route 53 リソースレコードセットを作成する際のエラーのトラブルシューティング](https://aws.amazon.com/jp/premiumsupport/knowledge-center/troubleshoot-route53-resource-record/)

解決方法は､`UPSERT`でなく`DELETE`->`CREATE`を立て続け（1つの定義ファイルに含める）に実行することで更新できます｡

ここで心配になるのが｢CREATEがなんらかの原因で失敗したら削除されてサイトが動作しなくなるのでは?｣という点です｡
これは､先達がいらしてすでに確認してくれています｡結論としては大丈夫です｡トランザクション処理になっているようで､CREATEがコケた場合はDELETEも実行されません｡

[【小ネタ】Route53のレコードをCLIから変更する際のDELETE/CREATEの動き | DevelopersIO](https://dev.classmethod.jp/articles/route53_delete_create_action/)

# 位置情報（日本のみ）をシンプルに変更する定義ファイル

以下の通りです｡逆の場合は､Action以外の定義の前後を入れ替えます.

```json:geo_to_simple.json
{
    "Comment": "Change routing policy from geo to simple",
    "Changes": [
        {
            "Action": "DELETE",
            "ResourceRecordSet": {
                "GeoLocation": {
                    "CountryCode": "JP"
                },
                "Name": "admin.example.com.",
                "Type": "A",
                "ResourceRecords": [
                    {
                        "Value": "255.255.255.255"
                    }
                ],
                "TTL": 300,
                "SetIdentifier": "任意のID名"
            }
        },
        {
            "Action": "CREATE",
            "ResourceRecordSet": {
                "Name": "admin.example.com.",
                "Type": "A",
                "ResourceRecords": [
                    {
                        "Value": "255.255.255.255"
                    }
                ],
                "TTL": 300
            }
        }
    ]
}
```


## スクリプト作成

定義ファイルと同じ場所に以下のスクリプトを作成します｡

定義ファイルは､`change-resource-record-sets`コマンドの`--change-batch`に渡す`file://`で指定しているので､この部分を調整すれば､
定義ファイルと当該スクリプトの場所は同じである必要はありません｡

あとはこれをcronで定期実行すれば完成です｡

※以下のスクリプトはパスワードなしsudo権限を持つ一般ユーザで実行する想定です｡
※Webサーバにはapacheを使用しています｡`systemctl reload httpd.service`の部分

```bash
#!/bin/sh

ABS_PATH=$(cd $(dirname $0); pwd)

# route53のホストゾーンID
HOSTED_ZONE_ID=ZXXXXXXXXXXXXXXX

# 上述したIAMポリシーを割り当てたIAMユーザのアカウントを割り当てたaws profile
AWS_PROFILE_USER=AWSプロファイル名

# route53の設定変更
function change_route53_recordsets () {
  echo "changing route53 recordsets file=$1"
  aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file://$ABS_PATH/$1 --profile $AWS_PROFILE_USER
}

# 位置情報からシンプルに戻す
change_route53_recordsets geo_to_simple.json

# 証明書更新
sudo certbot renew --post-hook "systemctl reload httpd.service"

# シンプルから位置情報に戻す
change_route53_recordsets simple_to_geo.json
```