【CloudFlare】動的IPアドレスをAPIで自動更新する。DDNS

前回Google DomainsからCloudFlareにドメインとDNSを移管する記事を書きました。

https://qiita.com/napspans/items/3e4030ea54948295c53e

動的IPでwebサーバーを運用している方はIPが更新されたら通知してあげる必要があります。今回はこの手順について記述していきます。IP更新していくために材料をそろえるのが少しめんどくさいですがお付き合いください。

## 目次
- [前提](#前提)
- [【材料1】 APIキーの取得](#材料1-apiキーの取得)
- [【材料2】ゾーンIDの取得](#材料2ゾーンidの取得)
- [【材料3】DNSレコードIDの取得](#材料3dnsレコードidの取得)
- [シェルスクリプトの作成](#シェルスクリプトの作成)
- [Crontabで自動実行](#crontabで自動実行)
- [まとめ](#まとめ)

## 前提
- CloudFlareでDNSを運用していること。
- Linuxサーバーでwebサーバーが運用されていること。
- sudo 権限
:::note warn
コード、コマンドの***で囲んでいるメールなどの情報は適宜自身の環境にあった項目に書き換えてください。
:::

## 【材料1】 APIキーの取得
DNSを遠隔で編集するために、2種類の

https://dash.cloudflare.com/profile/api-tokens

上記リンクから自身のCloudFlaredのAPIトーク画面を開き、**APIキー**の**Global API key**を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/da4bc679-0756-e187-7bae-29a8d71e8cdb.png)
パスワードを入力し、表示された文字列をコピーしてグローバルAPIキーとわかるように保管しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/aad21571-993d-422b-8940-c18a13415df9.png)
次に、APIトーク画面に戻り**トークンを作成**します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/4c4d196b-19be-e88f-505a-f42de33ce51e.png)
```ゾーンDNSを編集する```のテンプレートで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/212dd873-e75f-0440-9a43-6c88bffb8fd2.png)
次の画面で```アクセス許可```と```ゾーンリソース```を編集します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/a7a54ba1-8352-afbf-3075-0f9db78388d4.png)

:::note info
- アクセス許可
    - ゾーン -> DNS -> 編集
    - ゾーン -> ゾーン -> 読み込み
- ゾーンリソース
    - 包含 -> 特定のゾーン -> ドメイン名
:::

 
次の確認画面で```ドメイン名 - ゾーン:読み取り, DNS:編集```となっていればOKです。***トークンを作成する***をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/3667240c-b0d2-a2a3-3f4b-6d68ef44b796.png)
トークンが作成されたら**APIトークン**とテストコードが発行されます。ここで表示されるキーはこの画面でのみ表示されますので、一度閉じると再表示できませんので大切に保管してください。先ほどと同様にDNS編集用トークンとわかるようにしましょう。DNSレコードを修正する場合はこちらのトークンを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/734fd182-1f88-9bb1-24e2-6e1aba9e3149.png)
表示されているテストコードを実行します。
```.sh
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify"
-H "Authorization: Bearer ***APIトークン***" 
-H "Content-Type:application/json"
```
実行するとidなどが返ってきます。
```
{"result":{"id":"*****","status":"active"},"success":true,"errors":[],"messages":[{"code":10000,"message":"This API Token is valid and active","type":null}]}
```
```This API Token is valid and active```とメッセージが入っていればAPIトークンアクセスは正常です。

## 【材料2】ゾーンIDの取得
```.sh
curl -w "\n" --request GET \
  --url https://api.cloudflare.com/client/v4/zones?name=***example.com*** \
  --header 'Content-Type: application/json' \
  --header 'X-Auth-Email: ***CloudFlareアカウントメールアドレス***'\
  --header 'X-Auth-Key: ***グローバルAPIキー***'
```
出力が下記の通りとなっていれば成功です。jsonの```"name":"example.com"```が自身のドメインとなっていれば```"id":"***32文字乱数***"```が**ゾーンID**となります。これ次の項で使用しますのでゾーンIDとわかるようにメモしておきます。
```
{"result":[{"id":"***32文字乱数***","name":"example.com","status":"active",...
```

## 【材料3】DNSレコードIDの取得
```.sh
curl -w "\n" --request GET \
  --url https://api.cloudflare.com/client/v4/zones/***ゾーンID***/dns_records \
  --header 'Content-Type: application/json' \
  --header 'X-Auth-Email: ***メールアドレス***'\
  --header 'X-Auth-Key: ***グローバルAPIキー***'
```
ゾーンID取得と同様にjsonが帰ってきます。```"zone_id":"***ゾーンID***"```の項目が入力したIDと合っていれば最初の項目の```"id":"***32文字乱数***"```が**DNSレコードID**になります。
```
"result":[{"id":"***32文字乱数***","zone_id":"***ゾーンID***",...
```
ここまでで取得した**ゾーンID**、**DNSレコードID**と最初に生成した**APIトークン**を使ってIPを更新していきます。

## シェルスクリプトの作成
遠隔でDNSのAレコードを更新する際には下記のコマンドを使います。
```shell
curl --request PUT \
  --url https://api.cloudflare.com/client/v4/zones/***ゾーンID***/dns_records/***DNSレコードID*** \
  --header 'Content-Type: application/json' \
  --header "X-Auth-Email: ***メールアドレス***" \
  --header "Authorization: Bearer ***APIトークン***" \
  --data '{
    "content": "'"***新しいIPアドレス***"'",
    "name": "'"***更新するドメイン***"'",
    "proxied": false,
    "type": "A",
    "comment": "Domain verification record"
  }'
```
また、サーバーのIPアドレスが変更された際にサーバー自身は自分のアドレスを知ることができない為、ipアドレスを取得するサービスを利用して新しいIPを知る必要があります。ただし、回数制限やサービスエラーが起こった場合に対処するために複数のサービスで取得できるようにしておきます。
```shell
ip_address=""

# IPアドレスを取得する関数
get_ip_address() {
  local url="$1"
  ip_address=$(curl -s "$url")
}

# IPアドレスの正規表現パターン
ip_pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"

# 正規表現でIPアドレスが検証できるかチェック
is_valid_ip() {
  [[ $ip_address =~ $ip_pattern ]]
}

# IPアドレス取得のループ
for service in "${services[@]}"; do
  get_ip_address "$service"

  if is_valid_ip; then
    break
  fi
done
```

上記のコードを組み合わせてアクセス情報を追加したのが下記のコードになるので、```update_ip.sh```など名前を付けて保存します。
```update_ip.sh
#!/bin/bash

# 更新するドメイン
MY_DOMEIN="***sample.com***" 

# API アクセス情報
ZONE_ID="***ゾーンID***"
RECORD_ID="***レコードID***"
AUTH_EMAIL="***メールアドレス***"
API_TOKEN="***APIトークン***"

# IPアドレス取得サービスのリスト
services=(
  "https://api.ipify.org/"
  "https://checkip.amazonaws.com/"
  "https://ipv4.icanhazip.com/"
  "https://4.icanhazip.com/"
)

ip_address=""

# IPアドレスを取得する関数
get_ip_address() {
  local url="$1"
  ip_address=$(curl -s "$url")
}

# IPアドレスの正規表現パターン
ip_pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"

# 正規表現でIPアドレスが検証できるかチェック
is_valid_ip() {
  [[ $ip_address =~ $ip_pattern ]]
}

# IPアドレス取得のループ
for service in "${services[@]}"; do
  get_ip_address "$service"

  if is_valid_ip; then
    break
  fi
done

# ipアドレス更新テスト
# ip_address = 8.8.8.8

# CloudFlare API Aレコード更新
curl --request PUT \
  --url https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID \
  --header 'Content-Type: application/json' \
  --header "X-Auth-Email: $AUTH_EMAIL" \
  --header "Authorization: Bearer $API_TOKEN" \
  --data '{
    "content": "'"$ip_address"'",
    "name": "'"$MY_DOMEIN"'",
    "proxied": false,
    "type": "A",
    "comment": "Domain verification record"
  }'
```
私の場合はユーザフォルダの直下にscriptフォルダを作成し、その中に保存しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/d8bd122b-7192-f20f-15b3-7b1000182a0e.png)
保存したらbushコマンドで実行してみます。
```
~$ bash script/update_ip.sh 
```
実行結果としては大量のjsonオブジェクトが帰ってきますが、最後の方に```"success":true```となっていれば更新が正常に行われています。不安な方はスクリプトの```# ipアドレス更新テスト# ip_address = 8.8.8.8```をコメントアウトを削除して試してみるといいと思います。
```
..."success":true,"errors":[],"messages":[]}
```

## Crontabで自動実行
作成したスクリプトを自動実行してもらえるようにcrontabに設定していきますが、その前にファイルの権限を変更します。
```
chmod +x script/update_ip.sh
```
次にcrontabを呼び出します。
```
sudo crontab -e
```
crontabが開いたら実行コマンドを追記します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832106/28975404-18ce-7b8b-a0e1-e1954b28898f.png)

```
*/10 * * * * bash /home/napspans/script/update_ip.sh
```
追記したコマンドは10分毎に実行するコマンドです。各自サーバーの重要性に応じて設定してください。

http://scrap.php.xdomain.jp/create_tool/cron_setting_tool/

追加したら```ctrl+X```で保存して終了です。
crontabで本当に実行されているか心配な方はスクリプトの最後に```>> /path/to/update-ip.log 2>&1```を追加してログを取ってみるといいです。
```shell
curl --request PUT \
  --url https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID \
  --header 'Content-Type: application/json' \
  --header "X-Auth-Email: $AUTH_EMAIL" \
  --header "Authorization: Bearer $API_TOKEN" \
  --data '{
    "content": "'"$ip_address"'",
    "name": "'"$MY_DOMEIN"'",
    "proxied": false,
    "type": "A",
    "comment": "Domain verification record"
  }'>> /path/to/update-ip.log 2>&1

```

## まとめ

googleやmyDNSはwgetだけでペペっと簡単だったのに、今回はややこしかったですね。
以上で動的IPの方も安心して夜眠れると思います。
お疲れ様でした。 ではまた。

## 参考文献
https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-update-dns-record

https://developers.cloudflare.com/dns/manage-dns-records/how-to/managing-dynamic-ip-addresses/

https://b.muo.jp/2020/03/27/cloudflare-dyndns.html

https://510052.xyz/posts/x8xqi13fna0ppwpqc6x8/

https://chat.openai.com/share/ffa2fb14-b233-4866-9282-86041789cade