Multi-Zone Views for BIND9 (Split Horizon)
HowToForge
- 7 minutes read - 1409 wordsGrowing organizations often hit a wall: they need one DNS server to handle both internal and external name resolution, but publishing private addresses to the public Internet is a security risk. BIND9 views solve this cleanly by letting you serve different zone content based on where the query comes from, all from a single server.
The Problem
It is a typical problem in organizations that are growing: they have to resolve two needs at once.
- A DNS server for the internal network, because there are too many computers to remember their IPs and too many to maintain a set of host files.
- A DNS server for external clients and public-facing services.
This becomes a bigger problem when the organization cannot supply more than one DNS server. If you configure your server with all names, public and private, you end up polluting the Internet with private addresses and exposing part of your internal topology to potential attackers.
The other side of the problem is efficiency: you want internal clients to resolve to internal IPs and external clients to resolve to public IPs.
There are many solutions to this. I remember solving it with BIND4, but here I will use BIND9 to build a clean setup. This was deployed on a Debian GNU/Linux 3.1 server, but it should work on any OS that runs BIND9. Just adjust your paths accordingly.
Initial Configuration
Let’s imagine the organization I work for makes examples of something. You can order them on example.com. Examples Corporation has been assigned the network 192.0.2.0/24, and internally we use 10.0.0.0/24.
Let’s start by serving the external names and IPs. Edit /etc/bind/named.conf.local and add:
zone "example.com" {
type master;
file "/etc/bind/db.example.com";
};Then create /etc/bind/db.example.com with the following contents:
; example.com
$TTL 604800
@ IN SOA ns1.example.com. root.example.com. (
2006020201 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800); Negative Cache TTL
;
@ IN NS ns1
IN MX 10 mail
IN A 192.0.2.1
ns1 IN A 192.0.2.1
mail IN A 192.0.2.128 ; We have our mail server somewhere else.
www IN A 192.0.2.1
client1 IN A 192.0.2.201 ; We connect to client1 very often.As you can see, our start-up has one computer to serve everything except mail. It even handles IP forwarding and a couple of databases.
A good DNS setup has at least one secondary server, and some registrars enforce this. Since we do not have a second computer, we go to XName, open an account, and register example.com as secondary with 192.0.2.1 as the transfer source. We need to let XName’s IP perform the transfer. We are a small organization, but we want to do things properly, so we use the BIND9 acl directive to define an identifier that aliases to XName’s IP addresses. At the beginning of /etc/bind/named.conf.local we add:
acl slaves {
195.234.42.0/24; // XName
193.218.105.144/28; // XName
193.24.212.232/29; // XName
};And we update the zone declaration:
zone "example.com" {
type master;
file "/etc/bind/db.example.com";
allow-transfer { slaves; };
};We could have typed the IPs directly instead of using the slaves alias.
Internals and Externals
Now that we have a solid base, we can start thinking about serving different content to the internal and external networks. First, we define what is internal and what is external.
In /etc/bind/named.conf.local, add the following definition (at the top or below the slaves definition):
acl internals {
127.0.0.0/8;
10.0.0.0/24;
};If we had more internal networks, we would just add them there. We do not define externals because everything that is not internal is external. You may, if you want, define sets of different externals to serve different content to different parts of the Internet.
We will use a BIND9 feature called views. A view lets you put a piece of configuration inside a conditional block. In this case, we depend on the internals ACL. Replace the zone declaration in /etc/bind/named.conf.local with:
view "internal" {
match-clients { internals; };
zone "example.com" {
type master;
file "/etc/bind/internals/db.example.com";
};
};
view "external" {
match-clients { any; };
zone "example.com" {
type master;
file "/etc/bind/externals/db.example.com";
allow-transfer { slaves; };
};
};The match-clients directive lets us conditionally show a view based on a set of IPs. any stands for any IP. Internal IPs will be served by the internal view, and everything else falls through to the external view. The outside world cannot see the internal view, and that includes XName, our secondary DNS provider. We removed allow-transfer from the internal view because we do not want anyone to be able to transfer the contents of the internal zone under any circumstances.
We also changed the file paths. Create the directories /etc/bind/externals and /etc/bind/internals, then move /etc/bind/db.example.com to /etc/bind/externals/.
In /etc/bind/internals/db.example.com, put a zone file similar to the external counterpart but holding the internal IPs:
; example.com
$TTL 604800
@ IN SOA ns1.example.com. root.example.com. (
2006020201 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800); Negative Cache TTL
;
@ IN A 10.0.0.1
boss IN A 10.0.0.100
printer IN A 10.0.0.101
scrtry IN A 10.0.0.102
sip01 IN A 10.0.0.201
lab IN A 10.0.0.103Great. We can now ping our boss’ computer with:
ping boss.example.comBut trying to reach mail.example.com will disappoint us. There is no reference to mail.example.com in the internal zone file, and since we are on the internal network, the query hits the internal view. Fine, let’s just copy the contents of the external zone file into the internal one. That would work.
But we are a small, smart start-up. We can do better than copy-paste every modification to the zone file. That approach is very error-prone. Will you always remember to update the internal zone file when you modify the external one, or will you forget and spend days debugging network problems?
Instead, include the external zone file in the internal file:
$include "/etc/bind/externals/db.example.com"
@ IN A 10.0.0.1
boss IN A 10.0.0.100
printer IN A 10.0.0.101
scrtry IN A 10.0.0.102
sip01 IN A 10.0.0.201
lab IN A 10.0.0.103And voila. The $include directive pulls in the external records, and the internal-specific records override or supplement them. Just remember to bump the serial in the external zone file whenever you change it. If you forget, nothing bad will happen since you are unlikely to have caching servers inside your own small network.
Security
It is not recommended to use the same DNS server as both a primary for a domain and a caching resolver, but in our case we are forced to do it. From the outside world, 192.0.2.1 is the primary DNS server for example.com. From our internal network, 192.0.2.1 (or its private address, 10.0.0.1) is our caching name server, configured as the nameserver on each workstation.
One risk is that someone might start using our caching nameserver from the outside. There is an attack called cache poisoning, and many other nasty things that can be done against an open resolver.
To improve our security, add a couple of directives to the configuration:
view "internal" {
match-clients { internals; };
recursion yes;
zone "example.com" {
type master;
file "/etc/bind/internals/db.example.com";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "example.com" {
type master;
file "/etc/bind/externals/db.example.com";
allow-transfer { slaves; };
};
};This prevents anyone on the public Internet from using our server recursively, while we on our own network can still do it.
Complete Configuration Files
/etc/bind/named.conf.local
acl slaves {
195.234.42.0/24; // XName
193.218.105.144/28; // XName
193.24.212.232/29; // XName
};
acl internals {
127.0.0.0/8;
10.0.0.0/24;
};
view "internal" {
match-clients { internals; };
recursion yes;
zone "example.com" {
type master;
file "/etc/bind/internals/db.example.com";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "example.com" {
type master;
file "/etc/bind/externals/db.example.com";
allow-transfer { slaves; };
};
};/etc/bind/externals/db.example.com
; example.com
$TTL 604800
@ IN SOA ns1.example.com. root.example.com. (
2006020201 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800); Negative Cache TTL
;
@ IN NS ns1
IN MX 10 mail
IN A 192.0.2.1
ns1 IN A 192.0.2.1
mail IN A 192.0.2.128 ; We have our mail server somewhere else.
www IN A 192.0.2.1
client1 IN A 192.0.2.201 ; We connect to client1 very often./etc/bind/internals/db.example.com
$include "/etc/bind/externals/db.example.com"
@ IN A 10.0.0.1
boss IN A 10.0.0.100
printer IN A 10.0.0.101
scrtry IN A 10.0.0.102
sip01 IN A 10.0.0.201
lab IN A 10.0.0.103References
- SINS: Securing an Internet Name Server
- HowtoForge: Two-in-One DNS with BIND9 Views