😎

DNSサーバー構築 -権威DNS前編-

に公開

はじめに

今回、インフラを勉強しようと前々から思い立っていたのだが、何から始めて良いか分からず、延々と悩んだ末に出たのが、片っ端から色々な種類のサーバーを構築してって自宅サーバー周りを強化してやろうという魂胆でございます。ついでに一応、セキュリティ勉強も兼ねてそのサーバーのセキュリティについて調べていこうかなと考えています。

そして、今回私はvalue-domainを使った。この記事ではそれについて深く知ることができるので、ぜひ使ってみてほしい。(初っ端から年700円ほどかかるので、違うドメインレジストリでも良い)

DNSについて

DNSサーバーとはDomainNameSystemサーバーの略称であり、ドメインとIPアドレスを対応させて保持することによって名前解決を手伝うまたは名前解決そのものを行うものである。
DNSには以下のような種類があり、

種類 役割 特徴
権威DNSサーバー 自分のドメインの「正解」を持ち、世界に公開する 例:example.com の NS サーバー 再帰しない。誰でも問い合わせ可能(allow-query any; recursion no)
キャッシュDNSサーバー(再帰DNS / リゾルバ) クライアントの代わりに外へ問い合わせ、キャッシュする 例:8.8.8.8 (Google Public DNS) 再帰あり(recursion yes)。外部DNSへ問い合わせる
ローカルDNSサーバー(社内用) LAN内の名前解決+キャッシュ用。ときどきローカルゾーンも権威 例:test.local LANだけ許可。外部には公開しない

細かいところは以下から

https://hnavi.co.jp/knowledge/blog/dns-server/

そして今回は、BINDとDockerを使ってDNSの構築を行なっていく

権威DNS作成!!!

権威DNSのディレクトリ構成などはこんな感じ

とりあえず、テスト環境のみで動かすための最低限のものを用意しました。

.
└── bind-auth
    ├── Dockerfile
    ├── named.conf
    └── zones
        └── db.example.com

ということで組んでいきます

Dockerfile

FROM ubuntu/bind9:latest 
COPY named.conf /etc/bind/named.conf
#named.confをetc/bind/named.confへおく
COPY zones/ /etc/bind/zones/
#zonesディレクトリをetc/bind/zonesへcopy
EXPOSE 53/udp 53/tcp
#port listenの設定。つまり53番ポートで待ち受け。ここで注意なのが待ち受けているだけで開けてはいない。開けるにはdockerで -p オプションが必要 
CMD ["named","-f","-u","bind"]
#namedはbindの実行ファイル(namedデーモン)DNSサーバー本体
#-f フォアグランドで実行
#-u bind ユーザーをbindに切り替えて実行

named.conf

options {
  directory "/var/cache/bind";
  
  //BINDの動作時に使う実行ファイル(キャッシュ、一時ファイル、ヒントファイルなどをここに置く)このディレクトリパスがデフォ。bindユーザーがいじれるようになっている

  listen-on { any; };
  listen-on-v6 { any; };
  
  // 公開するので全インターフェースで待受。デフォルトは127.0.0.1(ローカル)のみだけど{ any; } にすることで誰でも待ち受けipv6も同様。特定のIPを指定することで絞り込むことも可能({ 192.0.2.53; };)

  allow-query { any; };
  
  //DNSサーバーがクエリをつけつけるクラインアントを制御。権威DNSは名前解決を外から受ける役割なのでanyが基本キャッシュDNSは社内IPのみにするのが基本

  recursion no;
  
  // ★超重要★ 外向け(インターネット公開)の権威は再帰させない。

  dnssec-validation no;
  
  //dnssecとはDNS応答にデジタル署名をつけることで改竄されていないことを保証する仕組みでこれはその検証を行うかの設定
  
  
  minimal-responses yes;
  
  // 必要最低限のレスポンスだけを返す設定noにすると標準的なDNS応答で余分な権威情報なども返してしまう

  // (任意)RRL:応答レート制限(対応ビルドで有効)
  // rate-limit { responses-per-second 20; window 5; };
};

// 公開ドメインに置き換え
zone "example.com" {
  type master;
  file "/etc/bind/zones/db.example.com";
  allow-transfer { none; }; 
  // セカンダリ作るまで閉じるもし用意するならIPアドレスを入れる
}

named.confの説明

listen-on allowquery

listen-onとalow-queryの違いはざっくりいうとドアと鍵。listen-onがドアでallow-queryは鍵

ドアの場所を教える範囲を決めるのがlisten-onで鍵を渡すのがallow-query

再帰(recursion)とは

DNSサーバーが問い合わせ元に代わって他のDNSサーバーに問い合わせを行い、最終的な答えを取得して返す動作のこと。自分が権威を持たないドメインでも解決できる。

権威DNSで再帰をONにしてはいけない理由

外向け権威DNSが再帰処理を行うと、不特定多数の外部クライアントの問い合わせを代行してしまい、攻撃者に「踏み台」として悪用されやすくなる。

  • DNSリフレクション(増幅)攻撃の仕組み

    攻撃者が送信元IPを被害者のIPに偽装してDNSサーバーへ小さな問い合わせを送り、DNSサーバーが大きな応答を被害者に送らせることで被害者に大量トラフィックを集中させる攻撃(DDoSの一種)。

  • なぜ再帰が危険か

    再帰ONのサーバーはさらに他のサーバーへ問い合わせを広げ、応答量や負荷が増えるため、被害の拡大と追跡困難性が増す。

  • 対策(実務上の鉄則)

    • 公開(外向け)権威DNS:recursion noにする。
    • キャッシュ(社内)DNS:recursion yes にするが allow-query でアクセスをローカルに限定する。
    • RRL(rate-limit)やファイアウォールで応答を制限・フィルタする。
    • ゾーン転送や管理アクセスは厳格に制限・ログ監視を行う。

dnssecについて

DNSSEC(DNS Security Extensions)は、DNS応答にデジタル署名を付けることで、

  • 改ざんされていないこと(完全性)

  • 権威あるサーバーからの応答であること(真正性)

    を保証する仕組み。

dnssec-validationについて

「キャッシュDNSサーバー」がクライアントのために

上流(Root → TLD → 権威DNS)からたどって応答を取得するときに、

その署名を検証するか否かを指定する。

  dnssec-validation yes;
  //DNSSEC 検証を有効化。リゾルバが署名チェーンをたどって検証する。
  //**キャッシュDNS**(社内DNSサーバーなど)
  
  dnssec-validation no;
  //DNSSEC 検証を行わない。
  //**権威DNSサーバー**(ゾーンを返す側)
  
  dnssec-validation auto;
  //自動でルート鍵(managed-keys)を使って検証。
  //通常のキャッシュDNS

rate-limitについて

DNSサーバーへのDoS攻撃対策の1つ。

RRL(Response Rate Limiting)機能を使って、同一IPや同一クエリに対する応答レートを制限します。

  • responses-per-second 20 → 1秒あたり20件まで応答
  • window 5 → 5秒間のウィンドウでカウント

👉 デフォルトではオフ。必要に応じて有効化するとDDoSに強くなります💪

zoneブロックについて

自身のDNSサーバーが権威を持つドメインを定義する部分

zone "example.com" { //管理するドメイン名(ゾーン名)
  type master; //これはマスター(プライマリ)サーバーです、の意味
  file "/etc/bind/zones/db.example.com"; //ゾーンファイルのパス(A, NS, MX レコードなどを書く)
  allow-transfer { none; }; //ゾーン転送を許可する相手(セカンダリDNS)。none は転送禁止
}

typeについて

zoneファイルのtypeについて、色々あるので紹介

1.master(プライマリ)

権威DNS本体であり、こいつがいちばんのトップDNS

2.slave(セカンダリ)

master から **AXFR / IXFR(ゾーン転送)**でコピーし、masterが落ちても、応答可能(冗長化)

ゾーンの「バックアップ役」

3.stub(スタブゾーン)

ゾーン全体ではなく、権威DNSサーバーのNS情報だけを持つ。つまり、権威DNSがどれかを指すのみ。

4.forward(フォワーダー)

自分では解決せず、特定のDNSサーバーに転送。つまりキャッシュDNS。

zones/db.example.com

$TTL 300
;TTLの設定時間デフォは300
@   IN SOA ns1.example.com. admin.example.com. (
        2025101401 ; serial(編集ごとに+1)
        3600       ; refresh
        900        ; retry
        604800     ; expire
        300 )      ; minimum

    IN NS  ns1.example.com.
    
; ← ns1 は“この権威DNSサーバー”のグローバルIPを指す
; ns1 IN A. {IPアドレス}
ns1 IN A   203.0.113.10
ns2 IN A   203.0.113.10 ;二台用意できないのでとりあえず二役与えておく

; 公開したいホストこれはサブドメインの設定
www IN A   203.0.113.20
api IN A   203.0.113.21

; ルート(example.com)のAを返したいなら
@   IN A   203.0.113.20

TTLについて

TTL = Time To Live(生存時間)

ざっくりいうと、DNSのキャッシュであり、この時間が TTL です

TTLはレコードごとに個別設定も可能

$TTL 300

www IN A 203.0.113.20
api 600 IN A 203.0.113.21     ; ← このレコードは600秒(10分)キャッシュ

TTLが短いと変更が反映されやすい代わりにキャッシュDNSに負荷がかかりやすい

TTLが長いと、キャッシュが長持ちして安定する代わりに変更の反映に時間がかかる。

SOAレコードについて

SOAレコード =Start of Authority レコード

その DNS ゾーンの 管理情報をまとめた最重要レコード である。

ざっくりいうと、DNSの持ち主の情報だったりもろもろを書いたもの。

SOAレコードの基本構造

@   IN SOA ns1.example.com. admin.example.com. (
        2025101401 ; Serial(シリアル番号)
        3600       ; Refresh(更新間隔)
        900        ; Retry(再試行間隔)
        604800     ; Expire(有効期限)
        300 )      ; Negative Cache TTL(ネガティブキャッシュTTL)

各フィールドの意味

フィールド 意味
@ example.com ゾーンのルート(ドメイン名)を指す(@ は省略記法)
IN IN インターネットクラス(固定でIN)
SOA SOA レコード種別(Start of Authority)
ns1.example.com. プライマリDNS(master)サーバー名。このゾーンの責任サーバー。
admin.example.com. 管理者のメールアドレス。@. に置き換える(例:admin@example.com)。
2025101401 シリアル番号。ゾーン変更時に +1 して slave に通知。
2025年10月14日に01回このファイ雨を変更した
3600 Refresh(更新間隔):slaveがmasterに変更確認する間隔(秒)
900 Retry(再試行間隔):更新失敗時に再試行する間隔
604800 Expire(有効期限):この期間を超えると slave はゾーン情報を破棄
300 Negative Cache TTL(存在しないドメインのキャッシュ時間)

フィールド 読み方 役割 よくある値
Serial シリアル ゾーン更新バージョン YYYYMMDDnn 2025101401
Refresh リフレッシュ slave が master に更新確認する間隔 3600〜86400 秒 3600
Retry リトライ master に失敗したときの再試行間隔 300〜3600 秒 900
Expire エクスパイア master から更新を取れなかった場合の有効期限 604800 秒(7日) 604800
Minimum ミニマム NXDOMAIN のキャッシュTTL 300〜3600 秒 300

NSレコードについて

そのゾーンの権威DNSを宣言する。今回は、これ自体が権威DNSなので以下のような書き方となっていた

IN NS  ns1.example.com.

Aレコードについて

ホスト名→IPv4アドレスに変換するいちばんDNSっぽい場所であり、

; サブドメイン IN A. {IPアドレス}
ns1 IN A   203.0.113.10

このように書くことができる。

Dockerで実験

同じフォルダ内にdocker-composeを作って実験する

services:
  bind-auth:
    image: ubuntu/bind9:latest
    container_name: bind-auth
    ports:
      - "5301:53/udp"
      - "5301:53/tcp"
    volumes:
      - ./bind-auth/named.conf:/etc/bind/named.conf:ro
      - ./bind-auth/zones:/etc/bind/zones:ro
    restart: unless-stopped

これにbind-authより一こ上のディレクトリ以下のコマンドで実行

docker compose up -d

以下でcomposeが開かれたのが確認できたら、

docker compose ps

以下のコマンドを打つ

dig @127.0.0.1 -p 5301 {ドメイン} A +norecurse

これで

自分の設定内容が見れたらOK

https://qiita.com/hypermkt/items/610b5042d290348a9dfa

上の記事にdigコマンドが成功した時の挙動について触れているので参考に

本番環境移行

本番環境のディレクトリ構成

.
├── bind-auth
│   ├── keys/ #増えた
│   ├── named.conf
│   ├── named.conf.local #増えた
│   ├── named.conf.options #増えた
│   └── zones/
│       └── db.example.com
├── docker-compose.yml
└── logs/ #増えた

まず変更箇所からどんどんいっていく

named.conf

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";

named.conf.local

zone "example.com" {
  type master;
  file "/etc/bind/zones/db.example.com";
  allow-transfer { none; }; 
};

named.conf.options

options {
  directory "/var/cache/bind";

  listen-on { any; };
  listen-on-v6 { any; };

  allow-query { any; };

  recursion no;

  dnssec-validation no;
  
  minimal-responses yes;

};

テストでは分けなかった部分だが、本番では設定変更を容易にするために分ける

docker-compose.yml

\services:
  bind-auth:
    image: ubuntu/bind9:latest
    container_name: bind-auth
    ports:
      - "5301:53/udp"
      - "5301:53/tcp"
    command: ["-g","-c","/etc/bind/named.conf"]
    volumes:
      - ./bind-auth/named.conf:/etc/bind/named.conf:ro
      - ./bind-auth/named.conf.options:/etc/bind/named.conf.options:ro
      - ./bind-auth/named.conf.local:/etc/bind/named.conf.local:ro
      - ./bind-auth/zones:/etc/bind/zones:ro
      - ./bind-auth/keys:/etc/bind/keys:ro
      - bind-cache:/var/cache/bind
    tmpfs:
      - /var/run/named
    healthcheck:
      test: ["CMD-SHELL","named-checkconf -z && test -f /etc/bind/zones/db.med-000.com"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
volumes:
  bind-cache: {}

この時点で一度、実際に動くかどうかを確認

docker compose build
docker compose up -d
dig @127.0.0.1 -p 5301 {ドメイン名}

ここでREFUSEDが見つからなければOK

そしてこれが動けばほぼのファイルにエラーがないということなので、

docker-compose.ymlの最終調整

services:
  bind-auth:
    image: internetsystemsconsortium/bind9:9.18
    container_name: bind-auth
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    command: ["-g","-c","/etc/bind/named.conf"]
    volumes:
      - ./bind-auth/named.conf:/etc/bind/named.conf:ro
      - ./bind-auth/named.conf.options:/etc/bind/named.conf.options:ro
      - ./bind-auth/named.conf.local:/etc/bind/named.conf.local:ro
      - ./bind-auth/zones:/etc/bind/zones:ro
      - ./bind-auth/keys:/etc/bind/keys:ro
      - bind-cache:/var/cache/bind
    tmpfs:
      - /var/run/named
    healthcheck:
      test: ["CMD-SHELL","named-checkconf -z && test -f /etc/bind/zones/db.med-000.com"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
volumes:
  bind-cache: {}

imageのところは私はmacなのでなぜかubuntuじゃないと動かなかったです

そこをまぁ本来のbindが元々入ってるinternetsystemsconsortiumを使う

そしてポートの方は本番の53ポートを使います

これで本番のファイルは完成です!!

終わりに

次回は実際に、借りたVPSに諸々のファイルをぶち込んで、実際に動かすところまでやっていきます。

こっから先は後編へ!!

参考サイト

  1. How To Configure Bind as an Authoritative-Only DNS Server on Ubuntu(DigitalOcean)
  2. Set Up BIND Authoritative DNS Server on Ubuntu 22.04/20.04 (BIND9)(LinuxBabe)
  3. DNS Tutorials – Authoritative DNS, Recursive Resolver, etc.(DNS Monitor)
  4. Run Your Own Authoritative DNS Servers(Josh Mcguigan)

Discussion