feat: Add dnsdist as backend
All checks were successful
Build Docker / Build Docker (push) Successful in 24s
All checks were successful
Build Docker / Build Docker (push) Successful in 24s
This commit is contained in:
parent
59c5fa9b75
commit
279fd4c6df
13
README.md
13
README.md
@ -9,12 +9,15 @@ You will need a static IP address that you can host the container on.
|
|||||||
Nathan will then add your IP to the domain which will let you create a certificate for the domain.
|
Nathan will then add your IP to the domain which will let you create a certificate for the domain.
|
||||||
|
|
||||||
|
|
||||||
## Run with docker
|
## Install script
|
||||||
```bash
|
```sh
|
||||||
docker run -d --name hns_doh git.woodburn.au/nathanwoodburn/hns_doh:latest
|
git clone https://git.woodburn.au/nathanwoodburn/hns_doh_loadbalancer.git
|
||||||
|
cd hns_doh_loadbalancer
|
||||||
|
sudo ./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then setup your favourite reverse proxy to the container on port 80
|
|
||||||
|
|
||||||
|
|
||||||
## Nodes
|
## Nodes
|
||||||
Load balancing to the following DNS-over-HTTPS providers:
|
Load balancing to the following DNS-over-HTTPS providers:
|
||||||
@ -23,13 +26,13 @@ Load balancing to the following DNS-over-HTTPS providers:
|
|||||||
| Nathan.Woodburn/ | https://doh.hnshosting.au/dns-query | Yes | Yes | Yes | Yes | Yes |
|
| Nathan.Woodburn/ | https://doh.hnshosting.au/dns-query | Yes | Yes | Yes | Yes | Yes |
|
||||||
| HNS DNS | https://doh.hnsdns.com/dns-query | Yes | Yes | No | Yes | Yes |
|
| HNS DNS | https://doh.hnsdns.com/dns-query | Yes | Yes | No | Yes | Yes |
|
||||||
| HNS NS | https://hnsns.net/dns-query | Yes | Yes | No | No | Yes |
|
| HNS NS | https://hnsns.net/dns-query | Yes | Yes | No | No | Yes |
|
||||||
|
| Impervious | https://hs.dnssec.dev/dns-query | No | Yes | Yes | No | Yes |
|
||||||
|
|
||||||
|
|
||||||
## Maybe future nodes
|
## Maybe future nodes
|
||||||
| Provider | Reason to not be added | URL | DoH JSON | DoH Wire | DoT | DNS | HIP05 |
|
| Provider | Reason to not be added | URL | DoH JSON | DoH Wire | DoT | DNS | HIP05 |
|
||||||
| ---------------- | -------------------------- | ---------------------------------------- | -------- | -------- | --- | --- | ----- |
|
| ---------------- | -------------------------- | ---------------------------------------- | -------- | -------- | --- | --- | ----- |
|
||||||
| EasyHandshake | Doesn't have HIP5 support | https://easyhandshake.com:8053/dns-query | Yes | Yes | No | No | No |
|
| EasyHandshake | Doesn't have HIP5 support | https://easyhandshake.com:8053/dns-query | Yes | Yes | No | No | No |
|
||||||
| Impervious | Doesn't support JSON DoH | https://hs.dnssec.dev/dns-query | No | Yes | Yes | No | Yes |
|
|
||||||
| HDNS | Only supports NB domains | https://hdns.io | No | Yes | No | Yes | No |
|
| HDNS | Only supports NB domains | https://hdns.io | No | Yes | No | Yes | No |
|
||||||
|
|
||||||
|
|
||||||
|
126
cert.py
Normal file
126
cert.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
### EDIT THESE: Configuration values ###
|
||||||
|
# CONTACT NATHAN FOR AUTH
|
||||||
|
AUTH = "your-auth-here"
|
||||||
|
### DO NOT EDIT BELOW THIS POINT ###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# URL to acme-dns instance
|
||||||
|
ACMEDNS_URL = "https://nathan.woodburn.au/hnsdoh-acme"
|
||||||
|
# Path for acme-dns credential storage
|
||||||
|
STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
|
||||||
|
# Whitelist for address ranges to allow the updates from
|
||||||
|
# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"]
|
||||||
|
ALLOW_FROM = []
|
||||||
|
# Force re-registration. Overwrites the already existing acme-dns accounts.
|
||||||
|
FORCE_REGISTER = False
|
||||||
|
|
||||||
|
DOMAIN = os.environ["CERTBOT_DOMAIN"]
|
||||||
|
if DOMAIN.startswith("*."):
|
||||||
|
DOMAIN = DOMAIN[2:]
|
||||||
|
VALIDATION_DOMAIN = "_acme-challenge."+DOMAIN
|
||||||
|
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]
|
||||||
|
|
||||||
|
|
||||||
|
class AcmeDnsClient(object):
|
||||||
|
"""
|
||||||
|
Handles the communication with ACME-DNS API
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, acmedns_url):
|
||||||
|
self.acmedns_url = acmedns_url
|
||||||
|
|
||||||
|
def update_txt_record(self, txt):
|
||||||
|
"""Updates the TXT challenge record to ACME-DNS subdomain."""
|
||||||
|
update = {"txt": txt, "auth": AUTH}
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
res = requests.post(self.acmedns_url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(update))
|
||||||
|
if res.status_code == 200:
|
||||||
|
# Successful update
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
msg = ("Encountered an error while trying to update TXT record in "
|
||||||
|
"acme-dns. \n"
|
||||||
|
"------- Request headers:\n{}\n"
|
||||||
|
"------- Request body:\n{}\n"
|
||||||
|
"------- Response HTTP status: {}\n"
|
||||||
|
"------- Response body: {}")
|
||||||
|
s_headers = json.dumps(headers, indent=2, sort_keys=True)
|
||||||
|
s_update = json.dumps(update, indent=2, sort_keys=True)
|
||||||
|
s_body = json.dumps(res.json(), indent=2, sort_keys=True)
|
||||||
|
print(msg.format(s_headers, s_update, res.status_code, s_body))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
class Storage(object):
|
||||||
|
def __init__(self, storagepath):
|
||||||
|
self.storagepath = storagepath
|
||||||
|
self._data = self.load()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""Reads the storage content from the disk to a dict structure"""
|
||||||
|
data = dict()
|
||||||
|
filedata = ""
|
||||||
|
try:
|
||||||
|
with open(self.storagepath, 'r') as fh:
|
||||||
|
filedata = fh.read()
|
||||||
|
except IOError as e:
|
||||||
|
if os.path.isfile(self.storagepath):
|
||||||
|
# Only error out if file exists, but cannot be read
|
||||||
|
print("ERROR: Storage file exists but cannot be read")
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
data = json.loads(filedata)
|
||||||
|
except ValueError:
|
||||||
|
if len(filedata) > 0:
|
||||||
|
# Storage file is corrupted
|
||||||
|
print("ERROR: Storage JSON is corrupted")
|
||||||
|
sys.exit(1)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Saves the storage content to disk"""
|
||||||
|
serialized = json.dumps(self._data)
|
||||||
|
try:
|
||||||
|
with os.fdopen(os.open(self.storagepath,
|
||||||
|
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
|
||||||
|
fh.truncate()
|
||||||
|
fh.write(serialized)
|
||||||
|
except IOError as e:
|
||||||
|
print("ERROR: Could not write storage file.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
"""Puts the configuration value to storage and sanitize it"""
|
||||||
|
# If wildcard domain, remove the wildcard part as this will use the
|
||||||
|
# same validation record name as the base domain
|
||||||
|
if key.startswith("*."):
|
||||||
|
key = key[2:]
|
||||||
|
self._data[key] = value
|
||||||
|
|
||||||
|
def fetch(self, key):
|
||||||
|
"""Gets configuration value from storage"""
|
||||||
|
try:
|
||||||
|
return self._data[key]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Init
|
||||||
|
client = AcmeDnsClient(ACMEDNS_URL)
|
||||||
|
storage = Storage(STORAGE_PATH)
|
||||||
|
|
||||||
|
# Update the TXT record in acme-dns instance
|
||||||
|
client.update_txt_record(VALIDATION_TOKEN)
|
||||||
|
# Wait for the DNS to propagate for 60 seconds
|
||||||
|
time.sleep(60)
|
7
cert.sh
Normal file
7
cert.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Tell dnsdist to reload the config
|
||||||
|
dnsdist -c -e 'reloadAllCertificates()'
|
||||||
|
|
||||||
|
# Save last run time
|
||||||
|
date +%s > last_cert_reload.txt
|
27
dnsdist.conf
Normal file
27
dnsdist.conf
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
newServer({address="194.50.5.26", name="Nathan.Woodburn/ 1"})
|
||||||
|
newServer({address="194.50.5.27", name="Nathan.Woodburn/ 2"})
|
||||||
|
newServer({address="194.50.5.28", name="Nathan.Woodburn/ 3"})
|
||||||
|
newServer({address="139.144.68.241", name="HNSDNS 1"})
|
||||||
|
newServer({address="139.144.68.242", name="HNSDNS 2"})
|
||||||
|
newServer({address="192.198.87.44:443", tls="openssl", subjectName="hnsns.net", dohPath="/dns-query", validateCertificates=true, name="HNSNS"})
|
||||||
|
newServer({address="178.128.128.181:443", tls="openssl", subjectName="hs.dnssec.dev", dohPath="/dns-query", validateCertificates=true, name="Impervious"})
|
||||||
|
|
||||||
|
|
||||||
|
-- Uncomment to add IPv6 servers
|
||||||
|
-- newServer({address="2a01:7e01:e002:c300::", name="HNSDNS 3"})
|
||||||
|
-- newServer({address="2a01:7e01:e002:c300::", name="HNSDNS 4"})
|
||||||
|
|
||||||
|
addDOHLocal('0.0.0.0', '/etc/letsencrypt/live/hnsdoh.com/fullchain.pem', '/etc/letsencrypt/live/hnsdoh.com/privkey.pem')
|
||||||
|
addTLSLocal('0.0.0.0', '/etc/letsencrypt/live/hnsdoh.com/fullchain.pem', '/etc/letsencrypt/live/hnsdoh.com/privkey.pem')
|
||||||
|
setLocal('0.0.0.0:53')
|
||||||
|
|
||||||
|
addACL('0.0.0.0/0')
|
||||||
|
|
||||||
|
--TODO fix this to redirect to welcome page
|
||||||
|
-- map = { newDOHResponseMapEntry("^/$", 307, "https://welcome.hnsdoh.com") }
|
||||||
|
-- dohFE = getDOHFrontend(0)
|
||||||
|
-- dohFE:setResponsesMap(map)
|
||||||
|
|
||||||
|
-- Feel free to change the control socket key
|
||||||
|
setKey("csl2icaGACsP3+M9tx55c8+dBxVCnlnqAHEC92P55eo=")
|
||||||
|
controlSocket('127.0.0.1:5199')
|
48
dnsdist.service
Normal file
48
dnsdist.service
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=DNS Loadbalancer
|
||||||
|
Documentation=man:dnsdist(1)
|
||||||
|
Documentation=https://dnsdist.org
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/usr/bin/dnsdist --check-config
|
||||||
|
# Note: when editing the ExecStart command, keep --supervised and --disable-syslog
|
||||||
|
ExecStart=/usr/bin/dnsdist --supervised --disable-syslog
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Type=notify
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
TimeoutStopSec=5
|
||||||
|
StartLimitInterval=0
|
||||||
|
|
||||||
|
# Tuning
|
||||||
|
LimitNOFILE=16384
|
||||||
|
TasksMax=8192
|
||||||
|
|
||||||
|
# Sandboxing
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
LockPersonality=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
PrivateTmp=true
|
||||||
|
# Setting PrivateUsers=true prevents us from opening our sockets
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectSystem=full
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
31
install.sh
Executable file
31
install.sh
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Verify that script is being run as root
|
||||||
|
if [ "$EUID" -ne 0 ]
|
||||||
|
then echo "Please run as root to allow installation of dependencies."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x cert.sh
|
||||||
|
sudo apt-get install -y dnsdist
|
||||||
|
# Install apt-add-repository
|
||||||
|
sudo apt-get install -y software-properties-common
|
||||||
|
# Install certbot
|
||||||
|
sudo apt-add-repository ppa:certbot/certbot -y
|
||||||
|
sudo apt install certbot -y
|
||||||
|
sudo certbot certonly --manual --manual-auth-hook ./cert.py --preferred-challenges dns -d hnsdoh.com --deploy-hook ./cert.sh
|
||||||
|
|
||||||
|
sudo cp ./resolved.conf /etc/systemd/resolved.conf
|
||||||
|
sudo systemctl restart systemd-resolved
|
||||||
|
|
||||||
|
# Move the conf file to the correct location
|
||||||
|
sudo cp ./dnsdist.conf /etc/dnsdist/dnsdist.conf
|
||||||
|
# Replace the user and group in the dnsdist.service file to root
|
||||||
|
# Like this
|
||||||
|
# User=root
|
||||||
|
# Group=root
|
||||||
|
sudo cp ./dnsdist.service /lib/systemd/system/dnsdist.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# Restart dnsdist
|
||||||
|
sudo systemctl restart dnsdist
|
34
resolved.conf
Normal file
34
resolved.conf
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# This file is part of systemd.
|
||||||
|
#
|
||||||
|
# systemd is free software; you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Entries in this file show the compile time defaults. Local configuration
|
||||||
|
# should be created by either modifying this file, or by creating "drop-ins" in
|
||||||
|
# the resolved.conf.d/ subdirectory. The latter is generally recommended.
|
||||||
|
# Defaults can be restored by simply deleting this file and all drop-ins.
|
||||||
|
#
|
||||||
|
# Use 'systemd-analyze cat-config systemd/resolved.conf' to display the full config.
|
||||||
|
#
|
||||||
|
# See resolved.conf(5) for details.
|
||||||
|
|
||||||
|
[Resolve]
|
||||||
|
# Some examples of DNS servers which may be used for DNS= and FallbackDNS=:
|
||||||
|
# Cloudflare: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com
|
||||||
|
# Google: 8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google
|
||||||
|
# Quad9: 9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
|
||||||
|
DNS=1.1.1.1
|
||||||
|
#FallbackDNS=
|
||||||
|
#Domains=
|
||||||
|
#DNSSEC=no
|
||||||
|
#DNSOverTLS=no
|
||||||
|
#MulticastDNS=no
|
||||||
|
#LLMNR=no
|
||||||
|
#Cache=no-negative
|
||||||
|
#CacheFromLocalhost=no
|
||||||
|
DNSStubListener=no
|
||||||
|
#DNSStubListenerExtra=
|
||||||
|
#ReadEtcHosts=yes
|
||||||
|
#ResolveUnicastSingleLabel=no
|
Loading…
Reference in New Issue
Block a user