Internal DNS Names With WireGuard
Using raw WireGuard peer IP addresses to connect to internal resources on your WireGuard network can be difficult for regular users. Fortunately, it’s easy to set up an internal DNS server that allows your users to access resources exposed through WireGuard with friendly DNS names.
For example, say you have an internal chat server set up with a WireGuard peer IP address of 10.0.0.11
on your WireGuard network. Instead of requiring your users enter its IP address of 10.0.0.11
in their chat application or web browser to access it, you can configure your internal DNS server to use a friendly DNS name like chat.wg.corp
for it.
This article will show you how to set up a CoreDNS server on the hub of a WireGuard hub-and-spoke network, allowing internal DNS names resolved by the CoreDNS server to be used to access network resources through the hub. We’ll follow these steps:
Example Network
We’ll use the following WireGuard network as an example, configured like the Site Gateway as a Spoke scenario of the Multi-Hop WireGuard article:
The central WireGuard Hub (configured like Host C from the “Site Gateway as a Spoke” scenario) allows a WireGuard Client (configured like Endpoint A from the “Site Gateway as a Spoke” scenario) to connect to the NY Office network through the NY Gateway (configured like Host β from the “Site Gateway as a Spoke” scenario); and also to the Engineering Cloud network through the Eng Gateway, and to other peers on the WireGuard network like the Chat Server.
We’ll set up a CoreDNS server on the Hub so that a user using the Client can access the Chat Server via a friendly DNS name of chat.wg.corp
, instead of its WireGuard IP address of 10.0.0.11
; and access the NY Office Printer via a friendly DNS name of printer.ny.corp
, instead of its LAN IP address of 192.168.200.22
; and access the engineering Fileserver via a friendly DNS name of files.eng.corp
, instead of its Engineering Cloud network IP address of 10.10.10.43
.
For the Fileserver, we’ll have the CoreDNS server on the WireGuard Hub delegate to a private DNS server running in cloud network at 10.0.0.44
that’s already set up to resolve DNS names for the eng.corp
domain. For the Chat Server and the Printer, we’ll configure our CoreDNS server itself to resolve DNS names for the wg.corp
and ny.corp
domains.
Disable Stub Resolver
The first step to running CoreDNS on the Hub is to turn off any existing DNS server on the Hub (to free up port 53
).
If you’re running systemd on the Hub, you’ll need to disable systemd’s own stub resolver. Edit the /etc/systemd/resolved.conf
file, and set its DNSStubListener
field to no
:
DNSStubListener=no
Then apply your changes by running the following command:
$ sudo systemctl restart systemd-resolved
Tip
|
If you want the Hub itself to use CoreDNS for its own DNS lookups, also modify the DNS=127.0.0.1 Domains=~. But make sure to change these two settings only after you’ve finished installing and configuring CoreDNS, and have verified that it’s resolving external domain names successfully. |
Install CoreDNS
CoreDNS runs as single executable. You can simply download the latest release archive from the CoreDNS releases page on GitHub, extract the archive (which contains a single executable file), set permissions on the executable, and run it. Alternatively, you can pull the latest CoreDNS Docker image to run CoreDNS as a Docker container; or you can build a deb or RPM file to install CoreDNS as a systemd service on Debian- or Fedora-based Linux distributions.
Via Docker
You can launch CoreDNS via Docker Compose using the following docker-compose.yml
file:
# /srv/coredns/docker-compose.yml
coredns:
image: coredns/coredns
command: -conf /etc/coredns/Corefile
ports:
- 53:53/udp
- 53:53/tcp
volumes:
- ./conf:/etc/coredns
For example, create a /srv/coredns/
directory on the Hub, and place the above docker-compose.yml
file in it. Then create a conf/
subdirectory of /srv/coredns/
, and place the following Corefile
into it:
# /srv/coredns/conf/Corefile . { whoami log }
Start up Docker Compose from the /srv/coredns
directory:
$ cd /srv/coredns
$ sudo docker-compose up
Pulling coredns (coredns/coredns:)...
latest: Pulling from coredns/coredns
9731739b2823: Pull complete
4dfb45b72a09: Pull complete
Digest: sha256:017727efcfeb7d053af68e51436ce8e65edbc6ca573720afb4f79c8594036955
Status: Downloaded newer image for coredns/coredns:latest
Creating coredns_coredns_1 ... done
Attaching to coredns_coredns_1
coredns_1 | .:53
coredns_1 | CoreDNS-1.10.0
coredns_1 | linux/arm64, go1.19.1, 596a9f9
Test it out by running the following command on the Hub:
$ dig @127.0.0.1 example.com
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22048
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 9b3888d9496d270e (echoed)
;; QUESTION SECTION:
;example.com. IN A
;; ADDITIONAL SECTION:
example.com. 0 IN A 172.17.0.1
_udp.example.com. 0 IN SRV 0 0 41596 .
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:13:03 UTC 2023
;; MSG SIZE rcvd: 114
Because we’ve configured CoreDNS with the whoami
plugin (for testing purposes), the response in the “additional section” will show the IP address (172.17.0.1
) and UDP port (41596
) from which you queried CoreDNS — not the actual DNS information of example.com
.
You’ll see a corresponding log entry for the query in CoreDNS’s output:
coredns_1 | [INFO] 172.17.0.1:41596 - 22048 "A IN example.com. udp 52 false 1232" NOERROR qr,aa,rd 91 0.000098518s
Via Deb
On Debian (or a Debian-based Linux distribution like Ubuntu), you can install CoreDNS through a deb package. This will set up a convenient systemd service for you.
First, clone the CoreDNS Deployment repo:
$ git clone https://github.com/coredns/deployment.git coredns/deployment
Cloning into 'coredns/deployment'...
remote: Enumerating objects: 970, done.
remote: Counting objects: 100% (111/111), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 970 (delta 57), reused 70 (delta 35), pack-reused 859
Receiving objects: 100% (970/970), 270.48 KiB | 8.20 MiB/s, done.
Resolving deltas: 100% (525/525), done.
Enter the cloned repo, and run the following command to build a deb of the latest CoreDNS release:
$ cd coredns/deployment
$ dpkg-buildpackage -us -uc -b
Command 'dpkg-buildpackage' not found, but can be installed with:
sudo apt install dpkg-dev
You may have to install the following dependencies to successfully build the deb:
$ sudo apt install dpkg-dev debhelper jq
Reading package lists... Done
...
$ dpkg-buildpackage -us -uc -b
dpkg-buildpackage: info: source package coredns
...
After the build command succeeds, navigate up a directory, and you should have a brand new CoreDNS deb package:
$ cd ..
ls -1
coredns_0-0_arm64.buildinfo
coredns_0-0_arm64.changes
coredns_1.10.0-0~22.040_arm64.deb
deployment
Install the deb with the following command:
$ sudo dpkg -i coredns*.deb
Selecting previously unselected package coredns.
(Reading database ... 71988 files and directories currently installed.)
Preparing to unpack coredns_1.10.0-0~22.040_arm64.deb ...
Unpacking coredns (1.10.0-0~22.040) ...
Setting up coredns (1.10.0-0~22.040) ...
Created symlink /etc/systemd/system/multi-user.target.wants/coredns.service → /lib/systemd/system/coredns.service.
Processing triggers for man-db (2.10.2-1) ...
This will install CoreDNS as a systemd service, listening on UDP and TCP port 53
. By default, it will be configured with a test Corefile
similar to the one we used for the Docker container above:
$ cat /etc/coredns/Corefile
# Default Corefile, see https://coredns.io for more information.
# Answer every below the root, with the whoami plugin. Log all queries
# and errors on standard output.
. {
whoami # coredns.io/plugins/whoami
log # coredns.io/plugins/log
errors # coredns.io/plugins/errors
}
And we’ll see a similar result as with the Docker container above if we run a test query against it:
$ dig @127.0.0.1 example.com
...
;; ADDITIONAL SECTION:
example.com. 0 IN A 127.0.0.1
_udp.example.com. 0 IN SRV 0 0 39581 .
...
And we’ll see similar log output from journald for it:
$ journalctl -u coredns.service
Jan 19 20:33:57 hub systemd[1]: Started CoreDNS DNS server.
Jan 19 20:33:57 hub coredns[42762]: .:53
Jan 19 20:33:57 hub coredns[42762]: CoreDNS-1.10.0
Jan 19 20:33:57 hub coredns[42762]: linux/arm64, go1.19.1, 596a9f9
Jan 19 20:37:01 hub coredns[42762]: [INFO] 127.0.0.1:39581 - 58758 "A IN example.com. udp 52 false 1232" NOERROR qr,aa,rd 91 0.024847392s
Configure CoreDNS
Now we can update the test Corefile
(at /srv/coredns/conf/Corefile
if you installed Via Docker, or /etc/coredns/Corefile
if you installed Via Deb) with some useful configuration for our internal domains.
Via Inline Hosts
The simplest way to have CoreDNS resolve our internal DNS names is to list them out with the traditional /etc/hosts
file format inside the Corefile
itself. For example, we can list the three DNS names we want to resolve (chat.wg.corp
, printer.ny.corp
, and files.eng.corp
) and their respective IP addresses directly in our Corefile
:
# /etc/coredns/Corefile . { hosts { 10.0.0.11 chat.wg.corp 192.168.200.22 printer.ny.corp 10.10.10.43 files.eng.corp } errors }
If we update our Corefile
to the above and restart CoreDNS, CoreDNS can now resolve those three DNS names — but only those three DNS names:
$ dig +short @127.0.0.1 chat.wg.corp
10.0.0.11
$ dig +short @127.0.0.1 printer.ny.corp
192.168.200.22
$ dig +short @127.0.0.1 files.eng.corp
10.10.10.43
$ dig +short @127.0.0.1 repo.eng.corp
$ dig +short @127.0.0.1 example.com
What we really want to do for all the domains not listed in our Corefile
is forward all eng.corp
queries to the Eng DNS server at 10.10.10.44
, and forward all other queries to a public DNS server like Quad9. We can do that by adjusting our Corefile
to add a fallthrough
setting to the hosts
plugin, plus add two forward
plugins — one for our Eng DNS resolver, and the other for the Quad9 resolvers:
# /etc/coredns/Corefile . { hosts { 10.0.0.11 chat.wg.corp 192.168.200.22 printer.ny.corp fallthrough } forward eng.corp 10.10.10.44 forward . 9.9.9.9 149.112.112.112 errors }
Restart CoreDNS, and now we can resolve both the hardcoded DNS names from our hosts
plugin, as well as any DNS name from our private Eng DNS server (like repo.eng.corp
) — and all public domains (like example.com
), too:
$ dig +short @127.0.0.1 chat.wg.corp
10.0.0.11
$ dig +short @127.0.0.1 printer.ny.corp
192.168.200.22
$ dig +short @127.0.0.1 files.eng.corp
10.10.10.43
$ dig +short @127.0.0.1 repo.eng.corp
10.10.10.49
$ dig +short @127.0.0.1 example.com
93.184.216.34
Tip
|
You can selectively “overwrite” public DNS entries with CoreDNS host files — just like you can with the # /etc/coredns/Corefile . { hosts { 10.0.0.11 www.example.com fallthrough } forward . 9.9.9.9 149.112.112.112 errors } With the above configuration, when using CoreDNS as our DNS resolver (like it will be when a client’s WireGuard interface is up),
|
Via Single Hosts File
Instead of defining custom DNS names directly in our Corefile
, we can pull them out into a separate file. The advantage of this is a) we could use the Hub’s own /etc/hosts
file directly if we wanted, and b) we can can configure CoreDNS to periodically check this file for updates (instead of having to restart CoreDNS every time we make a change to a DNS name).
For example, we could pull out our custom DNS names into a file called /etc/coredns/hosts
:
# /etc/coredns/hosts 10.0.0.11 chat.wg.corp 192.168.200.22 printer.ny.corp
And then update our Corefile
to load DNS names from it (and reload it every 60 seconds):
# /etc/coredns/Corefile . { hosts /etc/coredns/hosts { reload 60s fallthrough } forward eng.corp 10.10.10.44 forward . 9.9.9.9 149.112.112.112 errors }
Via Separate Hosts Files
We can also pull out separate domains into separate host files, for our own administrative convenience. For example, we
could list our wg.corp
DNS entries in an /etc/coredns/hosts/wg.corp
file, and our ny.corp
DNS entries in an /etc/coredns/hosts/ny.corp
file:
# /etc/coredns/hosts/wg.corp 10.0.0.11 chat.wg.corp
# /etc/coredns/hosts/ny.corp 192.168.200.22 printer.ny.corp
The hosts
plugin can only be used once per top-level block in a Corefile
however, so we now have to structure our Corefile
with separate top-level blocks for our wg.corp
and ny.corp
domains (and if we do that, we might as well also pull our eng.corp
domain into its own separate top-level block, as well):
# /etc/coredns/Corefile wg.corp { hosts /etc/coredns/hosts/wg.corp { reload 60s } errors } ny.corp { hosts /etc/coredns/hosts/ny.corp { reload 60s } errors } eng.corp { forward . 10.10.10.44 errors } . { forward . 9.9.9.9 149.112.112.112 errors }
Via Separate Zone Files
We can also use traditional RFC 1035-style zone files to define our custom DNS entries. This allows us to take full advantage of DNS features, like using CNAME
or SRV
records, or applying different TTL (Time To Live) values to different DNS entries.
For example, we could create the following zone file for the wg.corp
domain:
; /etc/coredns/zones/db.wg.corp
$ORIGIN wg.corp.
$TTL 1h
@ IN SOA (
ns ; primary nameserver
. ; zone-admin email
1 ; serial number
24h ; refresh interval
2h ; retry interval
1000h ; expire interval
10m ; negative TTL
)
IN NS ns
chat IN A 10.0.0.11
hub IN A 10.0.0.3
ns 5m IN A 10.0.0.3
_irc._tcp IN SRV 10 10 6667 chat.wg.corp.
And the following zone file for the ny.corp
domain:
; /etc/coredns/zones/db.ny.corp
$ORIGIN ny.corp.
$TTL 1h
@ IN SOA (
ns.wg.corp. ; primary nameserver
. ; zone-admin email
1 ; serial number
24h ; refresh interval
2h ; retry interval
1000h ; expire interval
10m ; negative TTL
)
IN NS ns.wg.corp.
canon650i IN A 192.168.200.22
printer IN CNAME canon650i
Tip
|
A few important things to remember when editing zone files:
|
If we placed the two above files in the /etc/coredns/zones
directory as db.wg.corp
and db.ny.corp
, we can apply them via CoreDNS’s auto
plugin:
# /etc/coredns/Corefile . { auto { directory /etc/coredns/zones reload 60s } forward eng.corp 10.10.10.44 forward . 9.9.9.9 149.112.112.112 errors }
Tip
|
CoreDNS’s |
After restarting, we should be able to lookup our IRC (Internet Relay Chat) SRV
record:
$ dig @127.0.0.1 SRV _irc._tcp.wg.corp
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 SRV _irc._tcp.wg.corp
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60358
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 50115fc0e15cc095 (echoed)
;; QUESTION SECTION:
;_irc._tcp.wg.corp. IN SRV
;; ANSWER SECTION:
_irc._tcp.wg.corp. 3600 IN SRV 10 10 6667 chat.wg.corp.
;; AUTHORITY SECTION:
wg.corp. 3600 IN NS ns.wg.corp.
;; ADDITIONAL SECTION:
chat.wg.corp. 3600 IN A 10.0.0.11
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:20:38 UTC 2023
;; MSG SIZE rcvd: 166
As well as the CNAME
record for the NY Office Printer:
$ dig @127.0.0.1 printer.ny.corp
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 printer.ny.corp
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29728
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 71a27f43c8748c85 (echoed)
;; QUESTION SECTION:
;printer.ny.corp. IN A
;; ANSWER SECTION:
printer.ny.corp. 3600 IN CNAME canon650i.ny.corp.
canon650i.ny.corp. 3600 IN A 192.168.200.22
;; AUTHORITY SECTION:
ny.corp. 3600 IN NS ns.wg.corp.
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:20:49 UTC 2023
;; MSG SIZE rcvd: 166
Configure WireGuard Client
Now that we have our CoreDNS server up and running, the only thing left to do is configure each WireGuard client to use it as its DNS server when the WireGuard interface is started up.
Since the CoreDNS server is running on our WireGuard Hub, we’ll use the Hub’s WireGuard IP address (10.0.0.3
) to identify it. We simply have to add this IP address as the DNS
setting to the [Interface]
section of each WireGuard client’s config:
# /etc/wireguard/wg0.conf # local settings for the WireGuard Client [Interface] PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE= Address = 10.0.0.1/32 ListenPort = 51821 DNS = 10.0.0.3 # remote settings for the WireGuard Hub [Peer] PublicKey = jUd41n3XYa3yXBzyBvWqlLhYgRef5RiBD7jwo70U+Rw= Endpoint = 192.0.2.3:51823 AllowedIPs = 10.0.0.0/24, 10.10.10.0/24, 192.168.200.0/24
Important
|
Make sure the client has an |
Tip
|
If the client is using Linux with systemd, use the following
See the WireGuard DNS Configuration for Systemd article for details. |
Restart WireGuard on the client after making this change.
Now we should be able to use our new friendly DNS names on the WireGuard Client:
$ dig +short chat.wg.corp
10.0.0.11