Simple DNS for your basic needs
Published on Wednesday, 03 February, 2021Intro
Sometimes you don't need (or want) some complex solution to simple problem like DNS. Therefore this one will be the simplified version of previous guide with bind as only element.
For this one you will again need podman
. If you are (like me in this case) doing this on centOS
or similar machine, getting podman
is as simple as:
# dnf install podman
If you are on some other distro, it shouldn't be that complicated.
Building container image
You could probably find one on dockerhub
or some other image repo, but building our own is simple and makes sure we know what we are running.
The goal of this will be simple, create a dns server that will provide a custom local domain, and forward everything else to upstream of choice.
All my guides follow same folder structure to keep all the files:
/containers
├── build
│ └── bind
└── run
└── bind
└── etc
Run following commands to create it:
# mkdir -p /containers/build/bind
# mkdir -p /containers/run/bind/etc
We'll create a simple Dockerfile
in /containers/build/bind
with following content:
FROM alpine:latest
LABEL maintainer="Marvin Sinister"
RUN addgroup -S -g 2021 bind && adduser -S -u 2001 -G bind bind; \
apk add --no-cache ca-certificates bind-tools bind; \
rm -rf /var/cache/apk/*; \
mkdir /var/cache/bind;
RUN chown -R bind: /etc/bind; \
chown -R bind: /var/cache/bind;
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s CMD nslookup ns.domain.tld 127.0.0.1 || exit 1
USER bind
CMD ["/bin/sh", "-c", "/usr/sbin/named -g -4"]
There is really nothing special about it, we are using latest alpine base image, installing bind, fixing some permissions and adding healthcheck. Make sure you use your domain instead of ns.domain.tld
in HEALTHCHECK
.
We can build the container now:
# podman build . -t bind
STEP 1: FROM alpine:latest
STEP 2: LABEL maintainer="Marvin Sinister"
--> Using cache 573e94441dfdbd7dcfa8e232ce7baedb9860192d749efd679b2bb8ccf64a797d
--> 573e94441df
STEP 3: RUN addgroup -S -g 2021 bind && adduser -S -u 2001 -G bind bind; apk add --no-cache ca-certificates bind-tools bind; rm -rf /var/cache/apk/*; mkdir /var/cache/bind;
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/53) Installing ca-certificates (20191127-r5)
...
(53/53) Installing bind (9.16.11-r0)
Executing bind-9.16.11-r0.pre-install
Executing bind-9.16.11-r0.post-install
wrote key file "/etc/bind/rndc.key"
Executing busybox-1.32.1-r2.trigger
Executing ca-certificates-20191127-r5.trigger
OK: 41 MiB in 67 packages
--> e90abd46aba
STEP 4: RUN chown -R bind: /etc/bind; chown -R bind: /var/cache/bind;
--> c5b324d0cff
STEP 5: HEALTHCHECK --interval=5s --timeout=3s --start-period=5s CMD nslookup ns.domain.tld 127.0.0.1 || exit 1
--> 3c7e5ae2cf3
STEP 6: USER bind
--> bbb48348aa0
STEP 7: CMD ["/bin/sh", "-c", "/usr/sbin/named -g -4"]
STEP 8: COMMIT bind
--> 1bd34f66b06
1bd34f66b066f714124253631db94d50a21643bf3b8c2fbb6539862d447bee32
If you are running recent enought version of podman
you might get warnings about HEALTCHECK
not being supported. In that case, just add --format docker
to end of build command.
You should now see the image:
# podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/bind latest 1bd34f66b066 3 minutes ago 41.9 MB
docker.io/library/alpine latest e50c909a8df2 5 days ago 5.88 MB
Creating configurations
The main config goes into etc/named.conf
:
// This is the primary configuration file for the BIND DNS server named.
// If you are just adding zones, please do that in /etc/bind/named.conf.local
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
Next, lets do options, etc/named.conf.options
:
options {
directory "/var/cache/bind";
forwarders {
1.1.1.1;
1.0.0.1;
};
recursion yes;
allow-query { lan; };
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
listen-on { any; };
};
Here we define our forwarders, in this case cloudflares 1.1.1.1
, allow the recursion and limit the queries to our local network (defined later).
Now, let's define local domains:
acl lan {
127.0.0.1;
};
zone "domain.tld" {
type master;
file "/etc/bind/db.domain.tld";
};
zone "122.168.192.in-addr.arpa" {
type master;
notify no;
file "/etc/bind/db.122.168.192.in-addr.arpa";
};
In this configuration we define the acl, we are only allowing localhost because bind will run in container and all calls will be from localhost from containers point of view. Next we define our domain domain.tld
and our reverse domain 122.168.192.in-addr.arpa
. The reverse zone should correspond to your ip range, in this case 192.168.122.0/24
.
Let's define the zones themself, first the forward zone domain.tld
:
; BIND data file for domain.tld zone
;
$TTL 86400
@ IN SOA ns.domain.tld. root.domain.tld. (
5 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS ns.domain.tld.
ns IN A 192.168.122.254
; hosts
containers IN A 192.168.122.254
This is where we add our dns entries, in this case we have two, ns.domain.tld
and containers.domain.tld
both pointing to 192.168.122.254
, the address of our podman host containers.domain.tld
.
And lats, the reverse zone:
;
; BIND reverse data file for lan zone
;
$TTL 604800
@ IN SOA ns.domain.tld. root.domain.tld. (
5 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.domain.tld.
254 IN PTR ns.domain.tld.
; hosts
254 IN PTR containers.domain.tld.
Similar to forward zone, the reverse zone defines the reverse pointers so you can find what hosts are behind some address.
Since bind uses DNSSEC to check the upstream repositories, we need to provide the upstream keys, you can download them from ICS website from bind.keys (at the time of writing this document). Download the file and save it as etc/bind.keys
.
Since we specified user with id 2021
in Dockerfile
we will make that user owner of those files:
# chown -R 2021 /containers/run/bind
Running the pod
While we could run this as a container, the decision was made to standardize on pods. There is no major reason to go one way or other, but standardization generally provides benefits long term. In that spirit, let's create the pod:
# podman pod create --name dns -p '192.168.122.254:53:53/udp'
And run the bind container within:
# podman run -d --name bind --pod dns -v '/containers/run/bind/etc:/etc/bind:Z' --user 2021 localhost/bind:latest
To allow external machines access to our new dns, we need to open 53/udp
on firewall:
# firewall-cmd --add-service=dns --permanent
# firewall-cmd --reload
Once everything starts, we can try running some dns queries to check if everything is okay, install the bind-utils
to get dig
command:
# dnf install bind-utils
And then check if you can resolve some addresses:
# dig +short @192.168.122.254 google.com
172.217.16.110
# dig +short @192.168.122.254 ns.domain.tld
192.168.122.254
And that's it, now you can point your network to 192.168.122.254
to resolve you local domain.