Cloudflare と NS1 を使って Multi-signer DNSSEC を構成する

# 参考：DNSSEC

- [DNSSEC の仕組みを理解する - Qiita](https://qiita.com/khayama/items/222bc3342dd4543c774e)

# Multi-signer DNSSEC とは
複数のプロバイダで同時に DNSSEC 対応することです。
例えば、以下のマルチプライマリ構成では、ネームサーバー・NS レコードに複数のプロバイダを登録します。

> - [Announcing Foundation DNS — Cloudflare’s new premium DNS offering](https://blog.cloudflare.com/foundation-dns/)
![img](https://blog.cloudflare.com/content/images/2021/12/image7-2.png)

# 要件
以下の RFC にある通り、複数のプロバイダで DNSSEC に対応（Zone apex に DNSKEY 登録）できることが必要です。

> - [RFC 8901: Multi-Signer DNSSEC Models](https://www.rfc-editor.org/rfc/rfc8901.html)
> The central requirement for both of the multiple-signer models (Section 2.1) is to ensure that the ZSKs from all providers are present in each provider's apex DNSKEY RRset and vouched for by either the single KSK (in Model 1) or each provider's KSK (in Model 2.)

# 構成パターン
以下の2つが考えられます。
どちらのパターンでも、ZSK を相互にクロスインポートすることが必要です。

> - [権威DNSサービス調査 ～複数のDNSプロバイダでDNSSEC対応してみた～](https://dnsops.jp/event/20230623/20230623-tanaka.pdf)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/8c3ee853-e9ca-598d-1d35-92b86d00853e.png)

## モデル1：KSK をプロバイダ共通のものとする

> - [Multi-Signer DNSSEC models | APNIC Blog](https://blog.apnic.net/2021/08/25/multi-signer-dnssec-models/)
>![Model 1](https://blog.apnic.net/wp-content/uploads/2021/08/Figure-2-%E2%80%94-In-Multi-Signer-Model-1-the-zone-owner-obtains-the-ZSKs-signs-and-pushes-the-DNSKEY-set-to-each-provider..png)
>Figure 2 — In Multi-Signer Model 1, the zone owner obtains the ZSKs, signs, and pushes the DNSKEY set to each provider.

## モデル2：KSK をプロバイダ別に分ける
KSK を複数登録する代わりに、1つの Combined Signing Keys (CSK) を使う方法もあります。

>![Model 2](https://blog.apnic.net/wp-content/uploads/2021/08/Figure-4-%E2%80%94-Model-2-cross-imports-ZSKs-across-providers-and-publishes-the-DS-corresponding-to-each-of-their-KSKs-in-the-parent-DS-RRset..png)
>Figure 4 — Model 2 cross-imports ZSKs across providers and publishes the DS corresponding to each of their KSKs in the parent DS RRset.

# DNSSEC を無停止で移行するには
DNSSEC が有効なドメインを無停止で移行するには、モデル2（プロバイダ毎に固有のKSKとZSK）を過渡期の構成として使うことで実現できます。

> - [Multi-Signer DNSSEC models | APNIC Blog](https://blog.apnic.net/2021/08/25/multi-signer-dnssec-models/)
> Importantly, non-disruptively transferring a DNSSEC signed zone from one DNS operator to another operator can be accommodated by Model 2 (Unique KSK and ZSK per provider). In fact, operator handoff is just a transitional state of a Model 2 Multi-Signer configuration.

# Cloudflare と NS1 を使った Multi-signer DNSSEC
モデル2を使った構成になります。
レジストラには、Value Domain を使います。

```mermaid:
flowchart TB
subgraph Registrar["Registrar<br>(Value Domain)"]
DS1[DS]
NS1[Nameserver<br>xxx.ns.cloudflare.com]
NS2[Nameserver<br>dnsX.pXX.nsone.net]
DS2[DS]
end

subgraph Cloudflare
subgraph DNSKEYRRset1[DNSKEY RRset]
DNSKEY1["DNSKEY<br>(Cloudflare KSK)"]
DNSKEY3["DNSKEY<br>(Cloudflare ZSK)"]
DNSKEY5["DNSKEY<br>(NS1 ZSK)"]
DNSKEY3 ~~~ DNSKEY1
end
NS3[NS<br>xxx.ns.cloudflare.com]
NS4[NS<br>dnsX.pXX.nsone.net]
end
Cloudflare--->NS1
DNSKEY1--->|Hash|DS1
DNSKEY1 ~~~ NS3[NS<br>xxx.ns.cloudflare.com]
DNSKEY1 ~~~ NS4[NS<br>dnsX.pXX.nsone.net]


subgraph NSone[NS1]
subgraph DNSKEYRRset2[DNSKEY RRset]
DNSKEY4["DNSKEY<br>(NS1 ZSK)"]
DNSKEY6["DNSKEY<br>(Cloudflare ZSK)"]
DNSKEY2["DNSKEY<br>(NS1 KSK)"]
DNSKEY4 ~~~ DNSKEY2
end
DNSKEY2 ~~~ NS5[NS<br>xxx.ns.cloudflare.com]
DNSKEY2 ~~~ NS6[NS<br>dnsX.pXX.nsone.net]
end
NSone--->NS2
DNSKEY2--->|Hash|DS2
DNSKEY3-.->|Cross Import|DNSKEY6
DNSKEY4-.->|Cross Import|DNSKEY5
```

# セットアップ
以下のドキュメントに従って、実装します。

- [Set up multi-signer DNSSEC · Cloudflare DNS docs](https://developers.cloudflare.com/dns/dnssec/multi-signer-dnssec/setup/)

:::note info
このような Multi-Provider セットアップにおけるゾーンのアクティベーションには、Enterprise Plan が必要です
:::

## DNSSEC 有効化
### Cloudflare

通常通り、Primary DNS として Cloudflare を権威 DNS とするようにゾーン登録します。

- [Change your nameservers (Full setup) · Cloudflare DNS docs](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/)

以下の API 操作で Multi-signer DNSSEC を有効化します。

```shell:
export EMAIL='YOUR_EMAIL'
export APIKEY='YOUR_APIKEY'
export ZONE_ID='YOUR_ZONE_ID'

curl --request PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dnssec" \
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY" \
--header 'Content-Type: application/json' \
--data '{
  "status": "active",
  "dnssec_multi_signer": true
}'
```

`"dnssec_status": "pending"` は「DNSSECは有効になっていますが、レジストラでCloudflare DSレコードが追加されていない」という意味です。

- [DNSSEC states · Cloudflare DNS docs](https://developers.cloudflare.com/dns/dnssec/dnssec-states/)

```json:result.json
{
  "result": {
    "dnssec_status": "pending",
    "dnssec_enabled": true,
    "dnssec_multi_signer": true
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

### NS1
以下のリンクからフリーアカウントを作成できます。

https://ns1.com/signup

NS1 を Primary DNS として設定し、DNSSEC を有効化します。

- [NS1 を Primary DNS として設定する](https://qiita.com/khayama/items/2ad4089a5ae8fc8ba602#ns1-%E3%82%92-primary-dns-%E3%81%A8%E3%81%97%E3%81%A6%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)
- [Enabling DNSSEC for a primary (or non-secondary) zone – NS1 Help Center](https://help.ns1.com/hc/en-us/articles/360022247233-Enabling-DNSSEC-for-a-primary-or-non-secondary-zone)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/fea7756e-08f5-73e2-c226-895ea7c79492.png)


## ZSK クロスインポート・NS レコード設定

### Cloudflare

#### ZSK クロスインポート

NS1 から取得できる以下の Public Key (NS1 ZSK) を Cloudflare に設定します。

`flags` の値が `257` なら KSK、 `256` なら ZSK です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/aefaf2b2-1e48-e3ce-171f-c70a34dc7db1.png)


```shell:
curl --request POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY" \
--header 'Content-Type: application/json' \
--data '{
  "type": "DNSKEY",
  "name": "<ZONE_NAME>",
  "data": {
    "flags": 256,
    "protocol": 3,
    "algorithm": 13,
    "public_key": "<PUBLIC_KEY>"
  },
  "ttl": 3600
}'
```

```json:result.json
{
  "result": {
    "id": "xxx",
    "zone_id": "xxx",
    "zone_name": "example.com",
    "name": "example.com",
    "type": "DNSKEY",
    "content": "256 3 13 xxx",
    "proxiable": false,
    "proxied": false,
    "ttl": 3600,
    "locked": false,
    "data": {
      "algorithm": 13,
      "flags": 256,
      "protocol": 3,
      "public_key": "xxx"
    },
    "meta": {
      "auto_added": false,
      "managed_by_apps": false,
      "managed_by_argo_tunnel": false,
      "source": "primary"
    },
    "comment": null,
    "tags": [],
    "created_on": "2023-10-31T02:11:10.060349Z",
    "modified_on": "2023-10-31T02:11:10.060349Z"
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

#### NS レコード設定
NS1 から取得できる以下の Nameserver を Cloudflare で NS レコードに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/f4a12d07-e278-3e62-a0f5-be19cadc9506.png)

```shell:
curl --request POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY" \
--header 'Content-Type: application/json' \
--data '{
  "type": "NS",
  "name": "<ZONE_NAME>",
  "content": "dns2.p09.nsone.net",
  "ttl": 86400
}'
```

```json:result.json
{
  "result": {
    "id": "xxx",
    "zone_id": "xxx",
    "zone_name": "example.com",
    "name": "example.com",
    "type": "NS",
    "content": "dns2.p09.nsone.net",
    "proxiable": false,
    "proxied": false,
    "ttl": 86400,
    "locked": false,
    "meta": {
      "auto_added": false,
      "managed_by_apps": false,
      "managed_by_argo_tunnel": false,
      "source": "primary"
    },
    "comment": null,
    "tags": [],
    "created_on": "2023-10-31T02:25:44.9506Z",
    "modified_on": "2023-10-31T02:25:44.9506Z"
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

`use_apex_ns` を有効化します。

>This step is required if you are using Cloudflare as a primary DNS provider - without enabling this setting, DNS queries to the zone apex requesting NS records will be responded with Cloudflare nameservers.

```shell:
curl --request PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_settings/use_apex_ns" \
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY" \
--header 'Content-Type: application/json' \
--data '{
  "id": "use_apex_ns",
  "value": true
}'
```

```json:result.json
{
  "result": [
    {
      "id": "use_apex_ns",
      "value": true
    }
  ],
  "success": true,
  "errors": [],
  "messages": []
}
```

### NS1

#### ZSK クロスインポート
Cloudflare から取得できる以下の Public Key (Cloudflare ZSK) を NS1 に設定します。

```shell:
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dnssec/zsk" \
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY"
```

```json:cloudflare_zsk.json
{
  "result": [
    {
      "Hdr": {
        "Name": "example.com.",
        "Rrtype": 48,
        "Class": 1,
        "Ttl": 3600,
        "Rdlength": 0
      },
      "Flags": 256,
      "Protocol": 3,
      "Algorithm": 13,
      "PublicKey": "xxx"
    }
  ],
  "success": true,
  "errors": [],
  "messages": []
}
```

API 操作が必要なため、API KEY を発行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/81d8daa3-c663-dd40-42f5-a807a6e8f245.png)

以下のコマンドでインポートできます。

- [Managing external DNSSEC keys (API only) – NS1 Help Center](https://help.ns1.com/hc/en-us/articles/7620400771731#UUID-535319aa-da44-dbba-ce7b-4fd97a318c6f)

```shell:
export NSONE_API_KEY='YOUR_NSONE_API_KEY'
export ZONE_NAME='YOUR_ZONE_NAME'

curl -X PUT -H "X-NSONE-Key: $NSONE_API_KEY" https://api.nsone.net/v1/zones/$ZONE_NAME/dnssec/external_keys/cloudflare -d '{
    "dnskey": {
        "ttl": 1200,
        "data": [
            {
                "flags": 256,
                "protocol": 3,
                "algorithm": 13,
                "public_key": "xxx"
            }
        ]
    }
}'
```

```json:result.json
{
  "dnskey": {
    "data": [
      {
        "algorithm": 13,
        "flags": 256,
        "protocol": 3,
        "public_key": "xxx"
      }
    ],
    "ttl": 1200
  },
  "name": "cloudflare"
}
```

#### NS レコード設定

Cloudflare Nameservers `xxx.ns.cloudflare.com` を NS 1 の NS レコードに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/8cebed56-9dd3-7b51-a89a-a37c621426ee.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/e9414f6b-3874-2563-9434-48f4f402da6a.png)

## Value Domain

### DS レコード

Cloudflare と NS1 から取得できる DS レコード内容を Value Domain に設定します。

- Cloudflare DS レコード

```shell:
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dnssec" \                                                          
--header "X-Auth-Email: $EMAIL" \
--header "X-Auth-Key: $APIKEY"
```

- NS1 DS レコード

ポータルから確認できる情報を以下のようにつなげて、DS レコード内容とします。
`key_tag algorithm digest_type digest`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/9d7b84cd-a9df-954c-5edb-843ee444bc56.png)

- Value Domain 設定


> - [VALUE DOMAINにDNSSECの設定を登録する - レ点腫瘍学ノート](https://oncologynote.jp/?06f1d6ae28)
このうち使用するのはDSレコードのDS以降（上記の表であれば「12345 13 2 13579ACE...(64字の文字列)」）になります。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/14a7311a-5512-6b89-9f42-9a9695dfae8b.png)

### ネームサーバー

以下の6つのネームサーバーを登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/f18ae404-c2b5-6ed9-f461-8d3469c37ebb.png)

# 動作確認

- [DNSSEC migration tutorial · Cloudflare DNS docs](https://developers.cloudflare.com/dns/dnssec/dnssec-active-migration/#4-remove-previous-provider)
- [Troubleshooting | DNSSEC · Cloudflare DNS docs](https://developers.cloudflare.com/dns/dnssec/troubleshooting/)

## ZSK がクロスインポートされている

```shell:
% dig $ZONE_NAME dnskey @xxx.ns.cloudflare.com +noall +answer | grep 256
example.com.		3600	IN	DNSKEY	256 3 13 oJMRExxx==
example.com.		3600	IN	DNSKEY	256 3 13 pxEUuxxx==
```

```shell:
% dig $ZONE_NAME dnskey @dns1.p09.nsone.net +noall +answer | grep 256
example.com.		3600	IN	DNSKEY	256 3 13 pxEUuxxx==
example.com.		3600	IN	DNSKEY	256 3 13 oJMRExxx==
```

## DS レコードが複数設定されている

```shell:
% dig $ZONE_NAME ds +noall +answer
example.com.		900	IN	DS	2371 13 2 DC134xxx
example.com.		900	IN	DS	48553 13 2 5AAD0xxx
```

## 複数の権威 DNS からの応答が検証できる
[DNSSEC Analyzer](https://dnssec-debugger.verisignlabs.com/) を使って、以下のように Cloudflare と NS1 の両方で検証できていれば、Multi-signer DNSSEC です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/2cff923d-66e9-6172-99fd-d9ff9b2b6410.png)

[Visualize DNSSEC](https://dnssec.cctld.uz/en) や [DNSViz | A DNS visualization tool](https://dnsviz.net/) を使うと、DS レコードが複数存在する上で、どちらかが使われて、信頼のチェーンとなることが可視化できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/5bd00988-9357-c3b6-6a48-eab49de9c9ff.png)

以下の `dig` コマンドでも `trace` を確認できます。

```shell:
dig A www.$ZONE_NAME +dnssec +trace
```

# まとめ

比較的新しい RFC でまとめられている Multi-signer DNSSEC ですが、正しい手順を踏めば、DNSSEC を無効にすることなくドメイン移行する際の構成としては、難しくないと思います。

権威 DNS サービスを提供する事業者が、移行元・移行先の双方で DNSSEC 対応している必要がありますが、Cloudflare ではこうした構成に対応できることもわかりました。

# その他参考

- [DNS(SEC) Views](https://cs.gmu.edu/~eoster/talks/3.8%20Tehrani%20DNS(SEC)%20Views.pdf)
- https://www.dnssecviews.net/
    - We're under construction. Please check back for an update soon. となっているが、他にも良い可視化ツールが出てくれば歓迎