Cloudflare でアカウントレベルのカスタムネームサーバーを使う

# 概要

> [Custom nameservers · Cloudflare DNS docs](https://developers.cloudflare.com/dns/additional-options/custom-nameservers/)

Cloudflare を権威 DNS として使う場合、通常 `xxx.ns.cloudflare.com` のネームサーバーが払い出されます。

それをカスタムしたい場合、自身が保有するドメインを使って `ns1.example.com`、`ns2.example.com` 等にカスタムできます。

Business と Enterprise Plan で利用できる機能ですが、ゾーンレベルとアカウントレベルの2種類があります。

ゾーンレベルの場合は、こちらに記載しましたので、<u>**その設定がなされた前提で**</u>進めます。

> [Cloudflare でカスタムネームサーバーを設定する - Qiita](https://qiita.com/khayama/items/e216822a54995e2821da)

一方、アカウントレベルのカスタムネームサーバーは、

- アカウント内の1つのゾーン（Business か Enterprise Plan）を使ったカスタムネームサーバー（ `ns1.example.com`、`ns2.example.com` 等）を、他のゾーンで利用することができる
- API 経由でのみ設定可能
  https://api.cloudflare.com/#account-level-custom-nameservers-properties

です。

アカウントレベルのカスタムネームサーバーを使うと、例えば、同一アカウント内に多数のゾーン登録が必要な場合で、ランダムにアサインされる `xxx.ns.cloudflare.com` を都度設定するより、固定の  `ns1.example.com`、`ns2.example.com` 等を使った方が運用上シンプルにできるケースがあります。

# アカウントレベルのカスタムネームサーバー作成

```shell
export EMAIL='YOUR_EMAIL'
export APIKEY='YOUR_APIKEY'
export ACCOUNT_ID='YOUR_ACCOUNT_ID'
```

以下のように1つずつ設定が必要です。

```shell
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"ns_name":"ns1.example.com"}'
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"ns_name":"ns2.example.com"}'
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"ns_name":"ns3.example.com"}'
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"ns_name":"ns4.example.com"}'
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"ns_name":"ns5.example.com"}'
```

設定結果を確認します。

```
curl -sX GET "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" | jq
```

 [グルーレコード（glue records）](https://jprs.jp/glossary/index.php?ID=0185) が設定済みのため、`"status": "verified"` として確認できます。

```json:result.json
{
  "result": [
    {
      "dns_records": [
        {
          "type": "A",
          "value": "162.159.0.xxx"
        },
        {
          "type": "AAAA",
          "value": "2400:cb00:2049:1::a29f:xxx"
        }
      ],
      "ns_name": "ns1.example.com",
      "status": "verified",
      "zone_tag": "xxx"
    },
    {
      "dns_records": [
        {
          "type": "A",
          "value": "162.159.1.xxx"
        },
        {
          "type": "AAAA",
          "value": "2400:cb00:2049:1::a29f:xxx"
        }
      ],
      "ns_name": "ns2.example.com",
      "status": "verified",
      "zone_tag": "xxx"
    },
    {
      "dns_records": [
        {
          "type": "A",
          "value": "162.159.3.xxx"
        },
        {
          "type": "AAAA",
          "value": "2400:cb00:2049:1::a29f:xxx"
        }
      ],
      "ns_name": "ns4.example.com",
      "status": "verified",
      "zone_tag": "xxx"
    },
    {
      "dns_records": [
        {
          "type": "A",
          "value": "162.159.2.xxx"
        },
        {
          "type": "AAAA",
          "value": "2400:cb00:2049:1::a29f:xxx"
        }
      ],
      "ns_name": "ns3.example.com",
      "status": "verified",
      "zone_tag": "xxx"
    },
    {
      "dns_records": [
        {
          "type": "A",
          "value": "162.159.4.xxx"
        },
        {
          "type": "AAAA",
          "value": "2400:cb00:2049:1::a29f:xxx"
        }
      ],
      "ns_name": "ns5.example.com",
      "status": "verified",
      "zone_tag": "xxx"
    }
  ],
  "result_info": {
    "page": 1,
    "per_page": 20,
    "total_pages": 1,
    "count": 5,
    "total_count": 5
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

# 既存ゾーンに設定

アカウントレベルのカスタムネームサーバーを作っただけでは、既存ゾーンに適用されません。

また、アカウントレベルのカスタムネームサーバーを適用した後でも `xxx.ns.cloudflare.com` は有効なままで残ります。

早速、同一アカウント内の他のゾーン（プランは問わず、Free Plan でも可）に適用できます。

```shell
export EXISTING_ZONE_ID='YOUR_EXISTING_ZONE_ID'
```

```
curl -X PUT "https://api.cloudflare.com/client/v4/zones/$EXISTING_ZONE_ID/custom_ns" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
      --data '{"enabled":true}'
```

設定結果を確認します。

```shell
curl -sX GET "https://api.cloudflare.com/client/v4/zones/$EXISTING_ZONE_ID" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" | jq '[.result]|map(. |= {vanity_name_servers_ips,vanity_name_servers})|.[]'
```

```json:result.json
{
  "vanity_name_servers_ips": {
    "ns1.example.com": {
      "ipv4": "162.159.0.xxx",
      "ipv6": "2400:cb00:2049:1::a29f:xxx"
    },
    "ns2.example.com": {
      "ipv4": "162.159.1.xxx",
      "ipv6": "2400:cb00:2049:1::a29f:xxx"
    },
    "ns3.example.com": {
      "ipv4": "162.159.2.xxx",
      "ipv6": "2400:cb00:2049:1::a29f:xxx"
    },
    "ns4.example.com": {
      "ipv4": "162.159.3.xxx",
      "ipv6": "2400:cb00:2049:1::a29f:xxx"
    },
    "ns5.example.com": {
      "ipv4": "162.159.4.xxx",
      "ipv6": "2400:cb00:2049:1::a29f:xxx"
    }
  },
  "vanity_name_servers": [
    "ns1.example.com",
    "ns2.example.com",
    "ns3.example.com",
    "ns4.example.com",
    "ns5.example.com"
  ]
}
```

# 新規登録ゾーンに適用

`"use_account_custom_ns_by_default":true` に設定します。

```shell
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"settings":{"use_account_custom_ns_by_default":true}}'
```

実際にゾーンを新規登録します。

```shell
export ZONE_NAME='YOUR_ZONE_NAME'
curl -sX POST "https://api.cloudflare.com/client/v4/zones" \
     -H "X-Auth-Email: $EMAIL" \
     -H "X-Auth-Key: $APIKEY" \
     -H "Content-Type: application/json" \
     --data '{"name":"'"$ZONE_NAME"'","account":{"id":"'"$ACCOUNT_ID"'"},"jump_start":false,"type":"full"}' | jq
```

登録時に `vanity_name_servers` が自動で有効になっていることが以下の結果で確認できます。

```json:result.json
{
  "result": {
    "id": "xxx",
    "name": "xxx.gq",
    "status": "pending",
    "paused": false,
    "type": "full",
    "development_mode": 0,
    "name_servers": [
      "xxx.ns.cloudflare.com",
      "yyy.ns.cloudflare.com"
    ],
    "original_name_servers": [
      "xxx.ns.cloudflare.com",
      "yyy.ns.cloudflare.com"
    ],
    "original_registrar": null,
    "original_dnshost": null,
    "modified_on": "2022-08-29T15:15:23.545798Z",
    "created_on": "2022-08-29T15:15:23.545798Z",
    "activated_on": null,
    "vanity_name_servers": [
      "ns1.example.com",
      "ns2.example.com",
      "ns3.example.com",
      "ns4.example.com",
      "ns5.example.com"
    ],
    "vanity_name_servers_ips": {
      "ns1.example.com": {
        "ipv4": "162.159.0.xxx",
        "ipv6": "2400:cb00:2049:1::a29f:xxx"
      },
      "ns2.example.com": {
        "ipv4": "162.159.1.xxx",
        "ipv6": "2400:cb00:2049:1::a29f:xxx"
      },
      "ns3.example.com": {
        "ipv4": "162.159.2.xxx",
        "ipv6": "2400:cb00:2049:1::a29f:xxx"
      },
      "ns4.example.com": {
        "ipv4": "162.159.3.xxx",
        "ipv6": "2400:cb00:2049:1::a29f:xxx"
      },
      "ns5.example.com": {
        "ipv4": "162.159.4.xxx",
        "ipv6": "2400:cb00:2049:1::a29f:xxx"
      }
    },
    "meta": {
      "step": 4,
      "custom_certificate_quota": 0,
      "page_rule_quota": 3,
      "phishing_detected": false,
      "multiple_railguns_allowed": false
    },
    "owner": {
      "id": "xxx",
      "type": "organization",
      "name": "xxx Account"
    },
    "account": {
      "id": "xxx",
      "name": "xxx Account"
    },
    "tenant": {
      "id": null,
      "name": null
    },
    "tenant_unit": {
      "id": null
    },
    "permissions": [
      "#access:edit",
      "#access:read",
      "#analytics:read",
      "#app:edit",
      "#auditlogs:read",
      "#billing:edit",
      "#billing:read",
      "#blocks:edit",
      "#blocks:read",
      "#cache_purge:edit",
      "#dash_sso:edit",
      "#dash_sso:read",
      "#dns_records:edit",
      "#dns_records:read",
      "#fbm_acc:edit",
      "#fbm:edit",
      "#fbm:read",
      "#healthchecks:edit",
      "#healthchecks:read",
      "#http_applications:edit",
      "#http_applications:read",
      "#image:edit",
      "#image:read",
      "#lb:edit",
      "#lb:read",
      "#legal:edit",
      "#legal:read",
      "#logs:edit",
      "#logs:read",
      "#magic:edit",
      "#magic:read",
      "#member:edit",
      "#member:read",
      "#organization:edit",
      "#organization:read",
      "#ssl:edit",
      "#ssl:read",
      "#stream:edit",
      "#stream:read",
      "#subscription:edit",
      "#subscription:read",
      "#teams:edit",
      "#teams:pii",
      "#teams:read",
      "#teams:report",
      "#waf:edit",
      "#waf:read",
      "#waitingroom:edit",
      "#waitingroom:read",
      "#webhooks:edit",
      "#webhooks:read",
      "#worker:edit",
      "#worker:read",
      "#zaraz:edit",
      "#zaraz:read",
      "#zone:edit",
      "#zone:read",
      "#zone_settings:edit",
      "#zone_settings:read"
    ],
    "plan": {
      "id": "0feeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
      "name": "Free Website",
      "price": 0,
      "currency": "USD",
      "frequency": "",
      "is_subscribed": false,
      "can_subscribe": false,
      "legacy_id": "free",
      "legacy_discount": false,
      "externally_managed": false
    }
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

# 参考

- [json - jq: selecting a subset of keys from an object - Stack Overflow](https://stackoverflow.com/questions/29518137/jq-selecting-a-subset-of-keys-from-an-object)
- [Extracting selected properties from a nested JSON object with jq - Stack Overflow](https://stackoverflow.com/questions/29259249/extracting-selected-properties-from-a-nested-json-object-with-jq)