Objective
I wanted to have few goals met when connecting to my VPN:
1. Devices connected to VPN must use custom 10.10.10.10 DNS server
2. Some kind of filtering must be available to exclude malicious domains
3. Custom domains must be easily configurable, e.g. jenkins.jarosik.vpn
Building blocks
- PowerDNS – Authoritative DNS server (https://www.powerdns.com/ ) is opensource DNS server
- PowerDNS – Recursor DNS server ( https://www.powerdns.com/recursor.html )
- PowerDNS-Admin – https://github.com/ngoduykhanh/PowerDNS-Admin
- OpenVPN – community edition
- List of domains to filter (e.g. https://hosts-file.net/?s=Download )
PowerDNS Authoritative server
First, we have to have a authoritative DNS server running, which will allow us to set up custom domains. We can follow setup for PowerDNS and run it on port 5300. Then, install and configure PowerDNS-admin to achieve something similar to the screenshot below. It’s all we to do to have custom domains. But we need more pieces to access the internet, as our current DNS server knows only about our custom domains. This is why we need PowerDNS’s Recursor server.
Recursor
Recursor is a DNS server which goes first to root DNS servers to find an answer to a DNS query. Let’s run it on 10.10.10.10 and port 53. And by itself (with default configuration) it will resolve all internet domains, but not our custom domains. Because of that, we must somehow configure it together with the Authoritative server. Fortunately, all we need to do is to add simple change to /etc/powerdns/recursor.conf:
forward-zones=jarosik.vpn=127.0.0.1:5300
The Recursor is running on port 53, Authoritative server is running on localhost:5300. Query to Recursor on port 53 will resolve both: internet and custom domains.
root@prod-nvme-dns:~# dig @localhost +noall +answer jenkins.jarosik.vpn
jenkins.jarosik.vpn. 3520 IN A 10.123.45.246
root@prod-nvme-dns:~# dig @localhost +noall +answer www.google.com
www.google.com. 170 IN A 172.217.20.196
Filtering
Now let’s add some filtering. There are many sources online for phishing or malicious domains, usually in the format <ip> <domain> to be included in /etc/hosts file. To try that from Recursor, we can use export-etc-hosts=on in the recursor.conf file. However, if you want to include thousands of domains, the Recursor will be slow to restart / respond. To make the Recursor efficient, we can use lua script by specifying it in recursor.conf:
lua-dns-script=/etc/powerdns/powerdns-filtering.lua
The lua script is provided below (based on this source: https://blog.powerdns.com/2016/01/19/efficient-optional-filtering-of-domains-in-recursor-4-0-0/
full_list_13-11-2019.lua in my case contains array of over 700k domains and the Recursor works fine. Remember, that the format of domain list must be valid lua, as described in the comments of the script.
If you have /etc/hosts like file format, you must convert it. You might use awk or python3 to do it, as you like.
Optionally, to remove non utf8 characters:
iconv -f utf-8 -t utf-8 -c full_list_13-11-2019.txt > full_list_13-11-2019-utf8.txt
VPN push 10.10.10.10 to clients
Setting up the VPN from scratch is out-of-scope for this post, but you can find a great tutorial on how to do it here:
https://www.digitalocean.com/community/tutorials/how-to-set-up-an-openvpn-server-on-ubuntu-18-04
The change we need is this simple change to /etc/openvpn/server.conf:
push "dhcp-option DNS 10.10.10.10"
Test the changes
# tjarosik @ tjarosik-P51 in ~ [16:24:54] C:1
$ dig @10.10.10.10 applesupport.co
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @10.10.10.10 applesupport.co
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56332
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;applesupport.co. IN A
;; AUTHORITY SECTION:
applesupport.co. 3600 IN SOA fake.applesupport.co. fake.applesupport.co. 1 7200 900 1209600 86400
;; Query time: 35 msec
;; SERVER: 10.10.10.10#53(10.10.10.10)
;; WHEN: Sun Nov 17 16:25:04 GMT 2019
;; MSG SIZE rcvd: 85
Also, if you try that page in a web browser, you will see ERR_NAME_NOT_RESOLVED