本文將我在容器內部署 BIND 9.21 的完整過程整理成一篇教學。為保護實際環境,所有網域以 domainA、domainB 代稱,IP 也以示例地址(如 203.0.113.0/24)呈現。
目標架構
- 單一 BIND 服務,分內外 view:
- internal view:提供內網權威 + 遞迴(轉送至外部 DNS),不做 DNSSEC 驗證,簡單穩定。
- external view:只提供對外權威(不對外遞迴),對 zone 進行 DNSSEC 簽名;為了讓 KASP 自動健康檢查父網域 DS,僅對 127.0.0.1 開啟遞迴與驗證。
- 安全邊界
- 對外 拒絕遞迴、僅提供權威回覆。
- AXFR/NOTIFY 使用 TSIG,只允許指定次 DNS 抓取 external 區。
- 停用 IPv6 監聽(僅 IPv4)。
- 容器友善的檔案佈局
- 設定檔在
/etc/bind/(唯讀)。 - 所有會被 BIND 寫入 的檔案(簽名、journal、金鑰、KASP 狀態、trust anchor)集中在
/var/cache/bind(可寫、持久化)。
目錄佈局(建議)
/etc/bind/
├─ named.conf
├─ named.conf.options
├─ named.conf.logging
├─ named.conf.acl
├─ named.conf.views
└─ keys/
└─ rndc.key # RNDC 控制通道 TSIG
/var/cache/bind/ # options { directory } 指到這裡
├─ zones/
│ └─ ext/ # external view 的 zone 檔(會產生 .signed/.jnl/tmp-*)
│ ├─ db.domainA
│ └─ db.domainB
├─ keys/ # key-directory:DNSSEC K* 金鑰與 .state
│ ├─ KdomainA.+013+11652.{key,private,state}
│ └─ KdomainB.+013+51162.{key,private,state}
├─ managed-keys.bind{,.jnl} # 根信任錨(自動)
└─ external.mkeys{,.jnl} # external view 的 trust anchor 狀態(自動)
重點:
K*金鑰與.state一律放/var/cache/bind/keys;managed-keys.bind*、*.mkeys*放/var/cache/bind根目錄;external 區檔放/var/cache/bind/zones/ext。
關鍵設定片段
1) options:工作目錄、金鑰目錄、僅 IPv4 監聽
// /etc/bind/named.conf.options
options {
directory "/var/cache/bind";
key-directory "/var/cache/bind/keys";
listen-on { any; };
listen-on-v6 { none; }; // 不在 IPv6 聽 53
recursion no; // 由各 view 覆寫
// 不在全域寫 dnssec-validation,改由各 view 控制
};
2) RNDC 控制通道(只綁 127.0.0.1)
// /etc/bind/named.conf
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.logging";
include "/etc/bind/named.conf.acl";
include "/etc/bind/keys/rndc.key";
controls {
inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "rndc-key"; };
};
include "/etc/bind/named.conf.views";
如果容器外要下
rndc,要能讀到同一把rndc.key,或在容器內執行。
3) internal view:內網權威 + 遞迴(不做 DNSSEC 驗證)
// /etc/bind/named.conf.views
view "internal" {
match-clients { "internal_nets"; }; // 在 named.conf.acl 定義你的內網,例如 192.168.10.0/24
recursion yes;
dnssec-validation no; // 內網遞迴不驗證,體驗最單純
allow-recursion { "internal_nets"; 127.0.0.1; };
allow-query { "internal_nets"; 127.0.0.1; };
allow-query-cache { "internal_nets"; 127.0.0.1; };
// 轉送遞迴,降低外連風險與雜訊
forwarders { 1.1.1.1; 8.8.8.8; };
forward first;
// 內部權威區(未簽名)
zone "domainA" { type master; file "/etc/bind/zones/int/db.domainA"; };
zone "domainB" { type master; file "/etc/bind/zones/int/db.domainB"; };
// localhost 與內網反解可放在這個 view
zone "localhost" { type master; file "/etc/bind/db.local"; };
zone "127.in-addr.arpa" { type master; file "/etc/bind/db.127"; };
// 根提示(僅 internal 用得到)
zone "." { type hint; file "/etc/bind/root.hints.v4"; };
};
4) external view:對外權威(簽名),只對本機開遞迴+驗證供 KASP 使用
view "external" {
match-clients { any; };
// 只給本機遞迴 + 驗證,讓 KASP 能 checkds(外部客戶端看不到遞迴)
recursion yes;
allow-recursion { 127.0.0.1; };
allow-query-cache { 127.0.0.1; };
dnssec-validation auto;
forwarders { 1.1.1.1; 8.8.8.8; }; // 避免直接打 root
forward only;
allow-query { any; }; // 對外可查權威資料
// 不對外提供快取:未列入 allow-query-cache
// 對外權威區(簽名)
zone "domainA" {
type master;
file "zones/ext/db.domainA"; // 相對於 /var/cache/bind
notify yes;
allow-transfer { key xfr-key; 203.0.113.53; }; // 次 DNS
also-notify { 203.0.113.53 key xfr-key; };
dnssec-policy default;
inline-signing yes;
};
zone "domainB" {
type master;
file "zones/ext/db.domainB";
notify yes;
allow-transfer { key xfr-key; 203.0.113.53; };
also-notify { 203.0.113.53 key xfr-key; };
dnssec-policy default;
inline-signing yes;
};
};
TSIG:請用
tsig-keygen -a hmac-sha256 xfr-key > /etc/bind/keys/xfr-key.key產生,主從兩邊都include,從伺服器zone { masters { <MASTER_IP> key xfr-key; }; }。
Zone 檔小細節:TXT 長度與 $ORIGIN
- DKIM TXT 常超過 255 bytes,需 拆成多段字串(BIND 會自動串接):
selector._domainkey IN TXT ( "v=DKIM1; k=rsa; p=MIIBI..." "......剩餘段落......" ) $ORIGIN domainA.時,結尾若不加點會被展成name.domainA.。DMARC 建議用_dmarc IN TXT ...(相對名),或_dmarc.domainA. IN TXT ...(絕對名)。
常見錯誤與修復
.private: file not found
金鑰不在key-directory;把K*(.key/.private/.state)移至/var/cache/bind/keys,chown bind:bind,chmod 640,再rndc loadkeys <zone>。permission denied在.jnl/.signed/tmp-*
external 的file指到只讀位置(例如/etc/bind)。把 external 區檔放到/var/cache/bind/zones/ext。dnssec-validation redefined
同一個 scope 設兩次。建議不要在全域 options 設,改由各 view 寫一次。- KASP 噪音:
Invalid NS RRset for 'tw' ... no valid RRSIG
external view 關遞迴/驗證時,KASP 無法checkds。解法:只對 127.0.0.1 開遞迴+dnssec-validation auto,並加forward only;。
驗證清單(貼上即跑)
變數:
主 DNS(外部)=203.0.113.10,次 DNS =203.0.113.53
內部(從內網主機)
dig @203.0.113.10 www.domainA A # 內部權威
dig @203.0.113.10 example.com A # 內部遞迴(應有答案)
外部(從外網主機)
dig @203.0.113.10 www.domainA A +norecurse # flags: aa;不遞迴
dig @203.0.113.10 example.com A # 應 REFUSED(無遞迴)
dig @203.0.113.10 www.domainA A +dnssec +norecurse +multiline # 看到 RRSIG
DNSSEC 鏈路(需已在註冊商發布 DS)
delv @1.1.1.1 www.domainA A # 預期 secure(或 DS 未上時為 insecure)
dig @1.1.1.1 domainA DS +dnssec +multiline # 應看到與 KSK 對應的 key tag/alg/digest
主/次同步(TSIG)
# 次 DNS 上測(帶 TSIG)
dig @203.0.113.10 domainA AXFR -y hmac-sha256:xfr-key:<BASE64_SECRET>
# 未帶 TSIG(任意主機)應 Transfer failed
dig @203.0.113.10 domainA AXFR
# SOA 序號一致
dig @203.0.113.10 domainA SOA +short
dig @203.0.113.53 domainA SOA +short
RNDC 與簽名狀態
rndc -c /etc/bind/keys/rndc.key status
rndc -c /etc/bind/keys/rndc.key signing -list domainA
rndc -c /etc/bind/keys/rndc.key signing -list domainB
NAT/轉發小記(選讀)
- 若你在 Gateway A(如 203.0.113.1)上用 DNAT 把外部 53 → 主 DNS(203.0.113.10:53),請確保 UDP/TCP 53 都轉。
- 內網客戶端建議 直接指向主 DNS(少一層 DNAT,降低複雜度);若一定要經由跳板,避免對 LAN→LAN 做 SNAT,否則 BIND 看不到真實來源 IP。
例行健康檢查(建議)
- 每天跑一次:
delv @1.1.1.1 www.domainA A看secure/insecure。dig @203.0.113.10 domainA DNSKEY +dnssec +short比對 key tag 是否與註冊商 DS 相符。- 主/次 SOA 序號是否一致。
- 觀察
dnssec.log的next key event與是否仍出現Invalid NS RRset類訊息(若按本文做法 A,應消失)。
收尾
這套佈局與設定重點在於:
- 「設定與動態檔分離」:
/etc/bind放純設定;所有會寫入的檔案統一到/var/cache/bind(含 KASP、簽名、journal、trust anchor)。 - 「view 策略清楚」:internal 簡化(不驗證)、external 嚴謹(簽名,僅對本機遞迴/驗證以滿足 KASP)。
- 「邊界明確」:對外拒絕遞迴、AXFR 綁 TSIG、IPv6 可關閉監聽。
按照本文調整後,你就能在單一 BIND 服務裡,同時滿足內外不同需求,且具有可維運、可自動輪替的 DNSSEC 能力。