This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| computing:bind9dns [2025/12/27 22:35] – oemb1905 | computing:bind9dns [2025/12/28 08:43] (current) – oemb1905 | ||
|---|---|---|---|
| Line 29: | Line 29: | ||
| * Configuring your Registrar' | * Configuring your Registrar' | ||
| - | This tutorial presumes you already have a working and sufficiently hardened | + | This tutorial presumes you already have three working and sufficiently hardened |
| ------------------------------------------- | ------------------------------------------- | ||
| Line 104: | Line 104: | ||
| </ | </ | ||
| - | As you can see, ns2 and ns3 restrict all access on 80/443 to ns1. This is because there is no need to access these nodes directly, as both bind9 and later webmin will directly instruct these slaves with their configurations. Ns1, on the other hand, should be publicly accessible by design. We will secure it later with a strong password (25 characters or more), a reverse proxy, and fail2ban. One can additionally, | + | As you can see, ns2 and ns3 restrict all access on 80/443 to ns1. This is because there is no need to access these nodes directly, as both bind9 and later webmin will directly instruct these slaves with their configurations. Ns1, on the other hand, should be publicly accessible by design. We will secure it later with a strong password (25 characters or more), a reverse proxy, and fail2ban. One can additionally, |
| + | |||
| + | {{ : | ||
| + | |||
| + | Once the basics of your nodes and your registrar have the prerequisite configurations in place, we can move on to configuring the Bind9 server. | ||
| + | |||
| + | === Part 2 - Setting up the Bind9 sever(s) === | ||
| + | |||
| + | We can now establish the root zone on the master (ns1) and then tie the two slaves to it. Let's open ''/ | ||
| <code bash> | <code bash> | ||
| Line 138: | Line 146: | ||
| </ | </ | ||
| - | Now that we've created the base server entry and created the master zone, we should restart the service and add a few items to the zone record file it creates for us. Let's do '' | + | Now that we've created the base server entry and created the master zone, we should restart the service and add a few items to the zone record file it creates for us. Let's do '' |
| <code bash> | <code bash> | ||
| Line 163: | Line 171: | ||
| @ | @ | ||
| @ | @ | ||
| + | |||
| + | @ IN TXT " | ||
| </ | </ | ||
| Line 176: | Line 186: | ||
| </ | </ | ||
| - | This way, once I configured the slaves, | + | Now that the family and cat website |
| + | |||
| + | <code bash> | ||
| + | @ IN SOA ns1.haacksnetworking.com. hostmaster.haacksnetworking.com. ( | ||
| + | 2025122311 | ||
| + | 3600 | ||
| + | 1800 | ||
| + | 604800 | ||
| + | 86400 | ||
| + | ) | ||
| + | |||
| + | @ IN NS ns1.haacksnetworking.com. | ||
| + | @ IN NS ns2.haacksnetworking.com. | ||
| + | @ IN NS ns3.haacksnetworking.com. | ||
| + | |||
| + | @ IN A 8.28.86.119 | ||
| + | @ IN AAAA 2604: | ||
| + | |||
| + | @ IN TXT " | ||
| + | |||
| + | _dmarc | ||
| + | </ | ||
| + | |||
| + | Now that we've created our base server and two zones, | ||
| <code bash> | <code bash> | ||
| Line 191: | Line 224: | ||
| </ | </ | ||
| + | This needs to be done on each node, i.e., ns2 and ns3. At this point, your base server and a handful of records are established so that also means it's a good time to begin testing. Before testing your nodes, always remember to reload changes w/ '' | ||
| + | |||
| + | host haacksnetworking.com ns1.haacksnetworking.com | ||
| + | host haacksnetworking.com ns2.haacksnetworking.com | ||
| + | host haacksnetworking.com ns3.haacksnetworking.com | ||
| + | host felinefantasy.club ns1.haacksnetworking.com | ||
| + | host felinefantasy.club ns2.haacksnetworking.com | ||
| + | host felinefantasy.club ns3.haacksnetworking.com | ||
| + | | ||
| + | These should all provide whatever A records we established above, however, during debugging or testing, it might be helpful to manually trigger '' | ||
| + | | ||
| + | rndc retransfer haacksnetworking.com | ||
| + | rndc retransfer felinefantasy.club | ||
| + | |||
| + | At this point, we're still dealing strictly with bind9 and have not setup webmin or the automated clustering features. We will do that soon, but there' | ||
| + | |||
| + | <code bash> | ||
| + | cd / | ||
| + | dnssec-keygen -a ED25519 -b 256 -n ZONE haacksnetworking.com | ||
| + | dnssec-keygen -a ED25519 -b 256 -n ZONE -f KSK haacksnetworking.com | ||
| + | SALT=$(openssl rand -hex 8) | ||
| + | dnssec-signzone -S -K / | ||
| + | </ | ||
| + | |||
| + | You can of course just run the '' | ||
| + | |||
| + | <code bash> | ||
| + | zone " | ||
| + | type master; | ||
| + | file "/ | ||
| + | allow-transfer { 8.28.86.114; | ||
| + | also-notify { 8.28.86.114; | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Once that's done, head over to your registrar and enter the values accordingly. For me, I use Dynadot, so it looks like this for a new entry: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | First, let's confirm our key details with: | ||
| + | |||
| + | cat dsset-felinefantasy.club. | ||
| + | < | ||
| + | | ||
| + | After that, and if you chose the same type of key as me, then your key tag is the '' | ||
| + | |||
| + | A1B2C3D4E5F67890123456789ABCDEF0123456789ABCDEF0123456789ABCDEF | ||
| + | | ||
| + | Enter all of these values, adjusting as needed, into your registrar' | ||
| + | |||
| + | <code bash> | ||
| + | ; <<>> | ||
| + | ;; global options: +cmd | ||
| + | ;; Got answer: | ||
| + | ;; ->> | ||
| + | ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 | ||
| + | ;; WARNING: recursion requested but not available | ||
| + | |||
| + | ;; OPT PSEUDOSECTION: | ||
| + | ; EDNS: version: 0, flags: do; udp: 1232 | ||
| + | ; COOKIE: 411d85836fb93c0a010000006950b1756dbe071f8d5e5758 (good) | ||
| + | ;; QUESTION SECTION: | ||
| + | ; | ||
| + | |||
| + | ;; ANSWER SECTION: | ||
| + | felinefantasy.club. 86400 IN DNSKEY 256 3 15 gsJX9BVwpBouYbMo1LcGAazEdI2dLR7jRoUvf0xghlU= | ||
| + | felinefantasy.club. 86400 IN DNSKEY 257 3 15 X89v0cFjUj9aJ7iowuBsWB+kWlU/ | ||
| + | felinefantasy.club. 86400 IN RRSIG DNSKEY 15 2 86400 20260123015214 20251224015214 5092 felinefantasy.club. M5mol/ | ||
| + | felinefantasy.club. 86400 IN RRSIG DNSKEY 15 2 86400 20260123015214 20251224015214 31408 felinefantasy.club. 8AFRfVowlWV9Gi65eZu+zWW6yg0ADCbUE/ | ||
| + | |||
| + | ;; Query time: 100 msec | ||
| + | ;; SERVER: 8.28.86.113# | ||
| + | ;; WHEN: Sat Dec 27 21:26:29 MST 2025 | ||
| + | ;; MSG SIZE rcvd: 399 | ||
| + | </ | ||
| + | |||
| + | ------------------------------------------- | ||
| + | |||
| + | === Part 3 - Setting up Webmin === | ||
| + | |||
| + | Okay, let's start by installing webmin: | ||
| + | |||
| + | curl -o webmin-setup-repo.sh https:// | ||
| + | sh webmin-setup-repo.sh | ||
| + | apt update | ||
| + | apt install webmin -y | ||
| + | |||
| + | Once webmin installs, it should already be listening on port 10000 and bound to localhost, but double check those entries and the required lines below in ''/ | ||
| + | |||
| + | bind=127.0.0.1 #only on ns1, leave this off ns2/ns3 | ||
| + | listen=10000 | ||
| + | redirect_ssl=1 | ||
| + | redirect_host=ns1.haacksnetworking.com #ns2/ns3 on other nodes | ||
| + | |||
| + | The slaves need to query the master' | ||
| + | |||
| + | host=ns1.haacksnetworking.com | ||
| + | | ||
| + | Remember, you are configuring this on each node. Recall that we already pre-built all firewall rules in advance, allowing the slaves, in my case .114 and .115, to receive 80/443 and 10000-10010 requests from the master at .113. These are rules not present on the master .113, for example. Make sure to recall and make sense of those rules as we build the cluster. Before we move on, let's make sure all of our webmin changes are active by restarting the service: | ||
| + | |||
| + | / | ||
| + | |||
| + | The next step is to create our Let's Encrypt certs for each domain. To do this, build the certs normally on the default '' | ||
| + | |||
| + | a2enmod proxy proxy_http proxy_wstunnel rewrite ssl headers | ||
| + | apache2ctl configtest | ||
| + | systemctl reload apache2 | ||
| + | sudo apt install certbot letsencrypt python3-certbot-apache | ||
| + | sudo certbot --authenticator standalone --installer apache -d nsX.haacksnetworking.com --pre-hook " | ||
| + | |||
| + | At this point, you should now have a cert for nsX.haacksnetworking.com. We can now swap the vhosts with our reverse proxy configurations and they will just work like magic when we restart the service. First, let's change our http vhost in ''/ | ||
| + | |||
| + | <code bash> | ||
| + | < | ||
| + | ServerName ns1.haacksnetworking.com | ||
| + | |||
| + | RewriteEngine On | ||
| + | RewriteCond %{HTTPS} off | ||
| + | RewriteRule ^ https:// | ||
| + | |||
| + | ProxyPass / | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | And, for https vhost in ''/ | ||
| + | |||
| + | <code bash> | ||
| + | < | ||
| + | |||
| + | ServerName ns1.haacksnetworking.com | ||
| + | |||
| + | SSLEngine on | ||
| + | SSLProxyEngine on | ||
| + | |||
| + | SSLCertificateFile / | ||
| + | SSLCertificateKeyFile / | ||
| + | Include / | ||
| + | |||
| + | SSLProxyCheckPeerCN off | ||
| + | SSLProxyCheckPeerName off | ||
| + | SSLProxyCheckPeerExpire off | ||
| + | |||
| + | ProxyPass / | ||
| + | ProxyPass / https:// | ||
| + | ProxyPassReverse / https:// | ||
| + | |||
| + | RewriteEngine On | ||
| + | RewriteCond %{HTTP: | ||
| + | RewriteCond %{HTTP: | ||
| + | RewriteRule ^/?(.*) wss:// | ||
| + | |||
| + | RequestHeader set X-Forwarded-Proto " | ||
| + | RequestHeader set X-Forwarded-Port " | ||
| + | |||
| + | </ | ||
| + | </ | ||
| + | |||
| + | Make sure to restart apache2 '' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | As you can see above, we are using the "Login via Webmin with" option with our root user's UNIX credentials, | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Once you've registered each node with the master' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | This one took a bit for me to figure out! The global bind9 settings, which are used to populate newly added zones - and which cannot be altered on the fly during zone creation - must be managed via the cog in the upper right of the Bind9 Server' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Inside that panel, you have five dropdowns that edit a global bind 9 config, which webmin uses to populate new zone builds. You want to edit two areas to be consistent with your bind9 CLI configurations. Specifically, | ||
| + | |||
| + | {{ : | ||
| + | {{ : | ||
| + | |||
| + | Before we create our first master zone using webmin' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Once you do that, we can now create a new master zone. Here's what that looks like. For me, all the default values are fine: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Doing this creates the zone on ns1. It looks a little different structurally than what I created on the CLI, but syntactically, | ||
| + | |||
| + | <code bash> | ||
| + | zone " | ||
| + | type master; | ||
| + | file "/ | ||
| + | allow-transfer { | ||
| + | 8.28.86.114; | ||
| + | 8.28.86.115; | ||
| + | 2604: | ||
| + | 2604: | ||
| + | }; | ||
| + | also-notify { | ||
| + | 8.28.86.114; | ||
| + | 8.28.86.115; | ||
| + | 2604: | ||
| + | 2604: | ||
| + | }; | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | The IPv6 addresses above did not populate despite me having those entered in webmin under Bind9 Server > Zone defaults. You can see them in that area below and you can see that a test.club domain lacks them despite being populated there as seen in the screenshot below: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | This glitch is a bit annoying, but it is not strictly required for the zone to function as only IPv4 is sufficient. If, however, you want to add the IPv6 entries, you do so by navigating to Bind9 Server > Zone Name > Edit Zone Options and simply add them: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | It is not required to change anything on the slaves because these are master node entries and the slaves don't require any of these blocks. The slaves, on the other hand, have created corresponding zone entries like follows in ''/ | ||
| + | |||
| + | <code bash> | ||
| + | zone " | ||
| + | type slave; | ||
| + | masters { | ||
| + | 8.28.86.113; | ||
| + | 2604: | ||
| + | }; | ||
| + | allow-transfer { | ||
| + | 8.28.86.113; | ||
| + | 2604: | ||
| + | }; | ||
| + | file "/ | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Webmin redundantly creates the transfer rule, but that won't hurt or change anything because it is moot due to the '' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Of course, you could also shell into the slaves and remove those transfer rules via the CLI, this is just to show that both methods work and are dealing with the exact same bind9 underbelly. Once we do that, we can !!FINALLY!! create A, AAAA, dmarc, spf, and or any other records we need. Here's what the zone's landing page looks like and what the record pages within it look like: | ||
| + | |||
| + | {{ : | ||
| + | {{ : | ||
| + | {{ : | ||
| + | {{ : | ||
| + | |||
| + | Now, we do some host testing again. Repeat host commands above | ||
| + | |||
| + | host cloudcommunity.club ns1.haacksnetworking.com | ||
| + | host cloudcommunity.club ns2.haacksnetworking.com | ||
| + | host cloudcommunity.club ns3.haacksnetworking.com | ||
| + | | ||
| + | Once that's working, let's setup DNSSEC using the webmin gui on master ns1. To do that navigate to Bind9 DNS Server > Zone > Setup DNS Key: | ||
| + | |||
| + | {{ : | ||
| + | | ||
| + | Once the DNSSEC key is created and the zone signed, you will, just like above, have to navigate over to your registrar and enter in the algorithm, digest, digest tag, and key tag. To see those values, just select Bind9 DNS Server > Zone > Setup DNS Key and instead of showing you the option to create it anew, it now shows the key you just created: | ||
| + | |||
| + | {{ : | ||
| + | | ||
| + | As a final step, we can use the dig command to verify the record against all nodes: | ||
| + | |||
| + | dig cloudcommunity.club DNSKEY +dnssec @8.28.86.113 | ||
| + | dig cloudcommunity.club DNSKEY +dnssec @8.28.86.114 | ||
| + | dig cloudcommunity.club DNSKEY +dnssec @8.28.86.115 | ||
| + | | ||
| + | Each node should report the following: | ||
| + | |||
| + | <code bash> | ||
| + | ; <<>> | ||
| + | ;; global options: +cmd | ||
| + | ;; Got answer: | ||
| + | ;; ->> | ||
| + | ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 | ||
| + | ;; WARNING: recursion requested but not available | ||
| + | |||
| + | ;; OPT PSEUDOSECTION: | ||
| + | ; EDNS: version: 0, flags: do; udp: 1232 | ||
| + | ; COOKIE: f9d0bef39e0c56da010000006950c6e270c6a99b26e97171 (good) | ||
| + | ;; QUESTION SECTION: | ||
| + | ; | ||
| + | |||
| + | ;; ANSWER SECTION: | ||
| + | cloudcommunity.club. 3600 IN DNSKEY 256 3 15 yCykkNhKUB0H3F7B+F1ydS6lmTaQAhRkVLgq6Fy6xWo= | ||
| + | cloudcommunity.club. 3600 IN DNSKEY 257 3 15 g61Yq+dJTUxZpQDvQfqqK59CUv3IsDXyO8Sy229YVic= | ||
| + | cloudcommunity.club. 3600 IN RRSIG DNSKEY 15 2 3600 20260123020922 20251224020922 188 cloudcommunity.club. sbVDXR4RZpR0s2eXn3wyyJ4JGO2AZpX/ | ||
| + | cloudcommunity.club. 3600 IN RRSIG DNSKEY 15 2 3600 20260123020922 20251224020922 30893 cloudcommunity.club. AjoUcCUneoXo/ | ||
| + | |||
| + | ;; Query time: 100 msec | ||
| + | ;; SERVER: 8.28.86.113# | ||
| + | ;; WHEN: Sat Dec 27 22:57:54 MST 2025 | ||
| + | ;; MSG SIZE rcvd: 402 | ||
| + | </ | ||
| + | |||
| + | === Part 4 - Optional Unbound Recursive Resolver === | ||
| + | |||
| + | Now that you can create any record you please and sign your zones/ | ||
| + | |||
| + | sudo apt install unbound | ||
| + | | ||
| + | The full unbound tutorial, including lan-side setups, can be found [[https:// | ||
| + | |||
| + | server: | ||
| + | # Bind to localhost only | ||
| + | interface: 127.0.0.1 | ||
| + | interface: ::1 | ||
| + | port: 5335 | ||
| + | do-ip4: yes | ||
| + | do-ip6: yes | ||
| + | prefer-ip6: yes | ||
| + | access-control: | ||
| + | access-control: | ||
| + | access-control: | ||
| + | # Optimize for 8 cores | ||
| + | num-threads: | ||
| + | msg-cache-slabs: | ||
| + | rrset-cache-slabs: | ||
| + | infra-cache-slabs: | ||
| + | key-cache-slabs: | ||
| + | # Cache settings for high query volume | ||
| + | cache-max-ttl: | ||
| + | cache-min-ttl: | ||
| + | rrset-cache-size: | ||
| + | msg-cache-size: | ||
| + | key-cache-size: | ||
| + | neg-cache-size: | ||
| + | # Enable prefetch and expired responses | ||
| + | prefetch: yes | ||
| + | prefetch-key: | ||
| + | serve-expired: | ||
| + | serve-expired-ttl: | ||
| + | # DNSSEC validation for DANE | ||
| + | #do-dnssec: yes | ||
| + | harden-dnssec-stripped: | ||
| + | harden-referral-path: | ||
| + | harden-below-nxdomain: | ||
| + | harden-algo-downgrade: | ||
| + | # Performance tweaks | ||
| + | #so-rcvbuf: 4m | ||
| + | #so-sndbuf: 4m | ||
| + | edns-buffer-size: | ||
| + | outgoing-range: | ||
| + | num-queries-per-thread: | ||
| + | jostle-timeout: | ||
| + | # | ||
| + | # Logging (minimal) | ||
| + | verbosity: 1 | ||
| + | log-queries: | ||
| + | log-replies: | ||
| + | use-syslog: yes | ||
| + | # Security and privacy | ||
| + | hide-identity: | ||
| + | hide-version: | ||
| + | use-caps-for-id: | ||
| + | qname-minimisation: | ||
| + | harden-large-queries: | ||
| + | harden-glue: | ||
| + | aggressive-nsec: | ||
| + | # Protocol settings | ||
| + | do-tcp: yes | ||
| + | do-udp: yes | ||
| + | # Disable subnetcache | ||
| + | module-config: | ||
| + | | ||
| + | It's crucial to bind unbound to 5335 since bind9 is already listening on 53. In order to have local recursive queries use unbound, we need to hijack all outbound resolver queries to ''/ | ||
| + | |||
| + | nameserver ::1 | ||
| + | nameserver 127.0.0.1 | ||
| + | |||
| + | After that, adjust the ufw rules to hijack all 53 url queries and send them to 5335. Open ''/ | ||
| + | |||
| + | <code bash> | ||
| + | # === Added NAT table for local DNS redirection to Unbound on port 5335 === | ||
| + | *nat | ||
| + | :PREROUTING ACCEPT [0:0] | ||
| + | :OUTPUT ACCEPT [0:0] | ||
| + | : | ||
| + | |||
| + | # Redirect local DNS queries sent to 127.0.0.1: | ||
| + | -A OUTPUT -d 127.0.0.1/ | ||
| + | -A OUTPUT -d 127.0.0.1/ | ||
| + | |||
| + | COMMIT | ||
| + | # === End of added section === | ||
| + | </ | ||
| + | |||
| + | Make sure you can ping a common website on both ipv4 and ipv6 and you should be good to go: | ||
| + | |||
| + | ping4 google.com | ||
| + | ping6 google.com | ||
| + | | ||
| + | If you are interested in setting up one of these authoritative Bind9 DNS clusters that are configured to use either CLI or the convenient webmin interface, just hit me up on Matrix. I'm available here: | ||
| + | * [[https:// | ||
| - | --- // | + | --- // |