Ubuntu服务器直接依赖外部DNS解析,等于把每次内部服务互访的路径暴露在公网环境里。一旦外部DNS被劫持、投毒或响应延迟,内部系统之间的调用就可能被导向错误地址,甚至造成数据泄露。内网DNS部署的核心价值不是简单“加速解析”,而是把解析控制权收回自己手里,切断外部不可控因素对内部服务发现的影响。下面直接讲部署和加固的具体方法。

确认内网DNS的部署场景与边界

先明确你的Ubuntu服务器集群里,哪些主机需要被内部域名访问,哪些服务依赖域名而不是IP。常见场景包括:数据库主从用域名切换、微服务注册中心用内部域名通信、CI/CD流水线拉取内部Git仓库。内网DNS只负责解析这些内部域名,所有非内部域名的查询可以转发到上游可信DNS,或者直接拒绝,避免服务器把内部请求发到公网。

部署前要规划一个专用域名区域,例如internal.example.com或lan.local。不要使用公网真实存在的域名作为内部区域,否则解析逻辑会混乱。所有内部主机的A记录、CNAME记录都归属这个区域,外部用户无法从公网解析到这些名称。

安装BIND9并锁定监听地址

Ubuntu 22.04/24.04仓库里的BIND9版本稳定,直接安装即可:

sudo apt update
sudo apt install bind9 bind9utils bind9-doc -y

安装后第一步不是配置区域文件,而是限制BIND只监听内网网卡。编辑/etc/bind/named.conf.options:

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

    listen-on port 53 { 127.0.0.1; 10.10.10.10; };
    listen-on-v6 port 53 { ::1; };

    allow-query { 127.0.0.1; 10.10.10.0/24; };
    allow-recursion { 127.0.0.1; 10.10.10.0/24; };
    allow-transfer { none; };

    recursion yes;
    dnssec-validation yes;

    forwarders {
        223.5.5.5;
        119.29.29.29;
    };
    forward only;
};

这里的10.10.10.10是假设的Ubuntu服务器内网IP,10.10.10.0/24是允许查询的内网网段。关键点在于allow-recursion只放行内网,allow-transfer设为none,防止区域传送泄露内部记录。上游转发使用了国内公共DNS,避免依赖单一外部解析源,同时forward only确保外部查询失败时不会自行迭代到根服务器,减少外部暴露面。

创建内部区域与正向解析记录

编辑/etc/bind/named.conf.local,声明内部正向区域:

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

然后创建区域文件/etc/bind/db.internal.example.com:

$TTL 3600
@   IN  SOA ns1.internal.example.com. admin.internal.example.com. (
        2025061801 ; serial
        3600       ; refresh
        600        ; retry
        1209600    ; expire
        3600 )     ; negative cache TTL
@       IN  NS  ns1.internal.example.com.
ns1     IN  A   10.10.10.10
db1     IN  A   10.10.10.21
cache1  IN  A   10.10.10.22
gitlab  IN  A   10.10.10.23
registry IN A   10.10.10.24

每修改一次记录,必须递增serial值,否则从属DNS不会同步。如果只有单台内网DNS,serial可以手动维护;如果有多台,建议用脚本自动更新serial。

配置反向解析减少日志误报

很多安全审计工具和系统日志会做反向PTR查询,如果内网IP没有PTR记录,日志里会出现大量解析失败噪音,掩盖真正的异常。为内网网段创建反向区域:

zone "10.10.10.in-addr.arpa" {
    type master;
    file "/etc/bind/db.10.10.10";
};

区域文件内容:

$TTL 3600
@   IN  SOA ns1.internal.example.com. admin.internal.example.com. (
        2025061801 ; serial
        3600       ; refresh
        600        ; retry
        1209600    ; expire
        3600 )     ; negative cache TTL
@       IN  NS  ns1.internal.example.com.
10      IN  PTR ns1.internal.example.com.
21      IN  PTR db1.internal.example.com.
22      IN  PTR cache1.internal.example.com.
23      IN  PTR gitlab.internal.example.com.
24      IN  PTR registry.internal.example.com.

PTR记录让内部IP反查域名时返回可控结果,避免外部DNS被查询,也方便运维排障时快速识别主机角色。

限制递归查询防止被滥用为放大攻击源

内网DNS如果开放递归给所有来源,可能被外部主机利用进行DNS放大攻击。即使监听在内网IP上,如果该IP通过NAT或防火墙误暴露到公网,风险依然存在。除了allow-recursion限制内网网段,还可以在防火墙层面只放行内网网段访问53端口:

sudo ufw allow from 10.10.10.0/24 to any port 53 proto udp
sudo ufw allow from 10.10.10.0/24 to any port 53 proto tcp
sudo ufw deny 53

Ubuntu默认防火墙规则顺序很重要,先放行内网,再拒绝所有其他来源访问53端口。这样即使BIND配置出现遗漏,网络层也能兜底。

配置客户端服务器使用内网DNS

Ubuntu服务器自身要优先使用内网DNS。如果使用netplan管理网络,编辑/etc/netplan/00-installer-config.yaml:

network:
  ethernets:
    ens18:
      dhcp4: no
      addresses:
        - 10.10.10.10/24
      routes:
        - to: default
          via: 10.10.10.1
      nameservers:
        addresses:
          - 127.0.0.1
          - 10.10.10.10
        search:
          - internal.example.com

注意把内网DNS放在第一位,并且search域设置为内部区域。这样服务器内部访问db1时,会自动补全为db1.internal.example.com并走内网解析。其他内网主机也把DNS指向10.10.10.10,不要混用外部DNS,否则会出现部分解析走公网的情况。

如果使用systemd-resolved管理DNS,需要修改/etc/systemd/resolved.conf:

[Resolve]
DNS=10.10.10.10
Domains=internal.example.com
FallbackDNS=223.5.5.5

然后重启服务:sudo systemctl restart systemd-resolved。但要确认/etc/resolv.conf是指向/run/systemd/resolve/stub-resolv.conf的软链接,否则配置不生效。

启用DNSSEC验证并处理内部区域信任链

外部解析启用DNSSEC验证可以防止缓存投毒,但内部区域如果也要DNSSEC,需要额外签名。对于大多数内网环境,内部区域不签名、外部转发启用验证是平衡安全与维护成本的做法。在named.conf.options里保持dnssec-validation yes,BIND会验证上游返回的DNSSEC签名,但不会要求内部区域签名,因为内部区域是权威数据,不走验证逻辑。

如果内网规模较大且有严格合规要求,可以对内部区域做DNSSEC签名,使用dnssec-keygen和dnssec-signzone生成密钥并签名区域。但签名后每次修改记录都要重新签名,维护复杂度明显上升。对于减少外部解析风险这个目标,先做好网络隔离和递归限制比内部签名更直接有效。

监控解析行为与异常查询

部署完成后,要持续观察BIND日志里是否有异常查询。开启查询日志:

sudo rndc querylog on

日志会写入/var/log/syslog,可以用以下命令过滤出被拒绝的查询:

sudo grep "denied" /var/log/syslog | tail -n 50

重点看是否有非内网来源尝试查询,或者内部主机频繁查询外部域名。如果某台内部主机突然大量查询外部域名,可能是被入侵后作为跳板进行C2通信。内网DNS在这里起到了一个轻量级检测点的作用。

还可以配置BIND的response policy zone(RPZ)来阻断已知恶意域名。Ubuntu仓库版本支持RPZ,创建一个本地策略区域,把威胁情报里的恶意域名指向0.0.0.0或127.0.0.1,这样内网主机即使被诱导访问恶意域名,也无法解析到真实IP。

多台内网DNS做主从冗余

单台内网DNS一旦宕机,所有内部服务解析中断,等于自己制造了一个单点故障。至少部署两台,一台主、一台从。主DNS上允许从DNS做区域传送,修改named.conf.local:

zone "internal.example.com" {
    type master;
    file "/etc/bind/db.internal.example.com";
    allow-transfer { 10.10.10.11; };
    also-notify { 10.10.10.11; };
};

从DNS上配置为slave类型:

zone "internal.example.com" {
    type slave;
    masters { 10.10.10.10; };
    file "/var/cache/bind/db.internal.example.com";
};

客户端DNS配置同时指向两台内网DNS,这样任意一台维护或故障都不影响解析。主从同步通过区域传送完成,serial递增后从服务器自动拉取更新。

验证部署效果与回滚方案

部署完成后用dig验证:

dig @10.10.10.10 db1.internal.example.com
dig @10.10.10.10 -x 10.10.10.21
dig @10.10.10.10 www.baidu.com

第一条应返回内网IP,第二条返回PTR记录,第三条走上游转发返回公网地址。如果第三条失败,检查forwarders和forward only配置。如果第一条失败,检查区域文件路径和权限,BIND运行用户bind需要能读取区域文件。

回滚方案是保留原有/etc/resolv.conf备份,如果内网DNS出现严重问题,客户端可以临时切回外部DNS。但要注意,切回外部DNS后内部域名将无法解析,依赖内部域名的服务会中断。所以更稳妥的做法是准备一台最小化的备用DNS,而不是直接退回外部解析。

内网DNS部署不是一次性工作,后续每增加一台内部服务器,都要同步添加A记录和PTR记录。把区域文件纳入版本管理,每次变更走审批和记录,才能长期保持解析可控。相比让服务器直接查询外部DNS,自建内网DNS把内部服务发现与外部分辨率隔离开,显著降低了被劫持、被投毒、被外部探测的风险。