diff --git a/build_and_push.sh b/build_and_push.sh index 5d41f10..9614bb3 100755 --- a/build_and_push.sh +++ b/build_and_push.sh @@ -4,6 +4,7 @@ IMAGES=( "eduardosilva/wireguard_webadmin:latest" "eduardosilva/wireguard_webadmin_cron:latest" "eduardosilva/wireguard_webadmin_nginx:latest" + "eduardosilva/wireguard_webadmin_dns:latest" ) build_images() { diff --git a/containers/dnsmasq/Dockerfile-dnsmasq b/containers/dnsmasq/Dockerfile-dnsmasq new file mode 100644 index 0000000..8ea0614 --- /dev/null +++ b/containers/dnsmasq/Dockerfile-dnsmasq @@ -0,0 +1,13 @@ +FROM ubuntu:latest + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y dnsmasq nano inotify-tools psmisc && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/containers/dnsmasq/entrypoint.sh b/containers/dnsmasq/entrypoint.sh new file mode 100644 index 0000000..a188de7 --- /dev/null +++ b/containers/dnsmasq/entrypoint.sh @@ -0,0 +1,40 @@ +#!/bin/bash +CONFIG_FILE="/etc/dnsmasq/wireguard_webadmin_dns.conf" +DEFAULT_CONFIG_CONTENT=" +no-dhcp-interface= +server=1.1.1.1 +server=1.0.0.1 + +listen-address=0.0.0.0 +bind-interfaces +" + +create_default_config() { + if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found, creating a new one..." + echo "$DEFAULT_CONFIG_CONTENT" > "$CONFIG_FILE" + fi +} + +start_dnsmasq() { + dnsmasq -C "$CONFIG_FILE" & + while inotifywait -e modify "$CONFIG_FILE"; do + echo "Configuration changed, reloading dnsmasq..." + pkill dnsmasq + sleep 5 + dnsmasq -C "$CONFIG_FILE" & + done +} + + +handle_sigint() { + echo "SIGINT received. Stopping inotifywait and dnsmasq..." + pkill inotifywait + pkill dnsmasq + exit 0 +} + +trap handle_sigint SIGINT + +create_default_config +start_dnsmasq diff --git a/dns/forms.py b/dns/forms.py index deb66f6..bf4acb4 100644 --- a/dns/forms.py +++ b/dns/forms.py @@ -21,6 +21,7 @@ class DNSSettingsForm(forms.ModelForm): self.helper = FormHelper() self.fields['dns_primary'].label = 'Primary Resolver' self.fields['dns_secondary'].label = 'Secondary Resolver' + self.fields['dns_primary'].required = True self.helper.form_method = 'post' self.helper.layout = Layout( Fieldset( diff --git a/dns/functions.py b/dns/functions.py index 52cd70c..a341189 100644 --- a/dns/functions.py +++ b/dns/functions.py @@ -25,9 +25,51 @@ server: local-zone: "local." static do-not-query-localhost: {do_not_query_localhost} verbosity: 1 - recursion: yes ''' unbound_config += forward_zone - for static_host in static_hosts: - unbound_config += f'local-data: "{static_host.hostname}. IN A {static_host.ip_address}"\n' + + if static_hosts: + unbound_config += '\nlocal-zone: "." transparent\n' + for static_host in static_hosts: + unbound_config += f' local-data: "{static_host.hostname}. IN A {static_host.ip_address}"\n' return unbound_config + + +def generate_dnsdist_config(): + dns_settings = DNSSettings.objects.get(name='dns_settings') + static_hosts = StaticHost.objects.all() + dnsdist_config = "setLocal('0.0.0.0:53')\n" + dnsdist_config += "setACL('0.0.0.0/0')\n" + + if dns_settings.dns_primary: + dnsdist_config += f"newServer({{address='{dns_settings.dns_primary}', pool='upstreams'}})\n" + if dns_settings.dns_secondary: + dnsdist_config += f"newServer({{address='{dns_settings.dns_secondary}', pool='upstreams'}})\n" + + if static_hosts: + dnsdist_config += "addAction(makeRule(''), PoolAction('staticHosts'))\n" + for static_host in static_hosts: + dnsdist_config += f"addLocal('{static_host.hostname}', '{static_host.ip_address}')\n" + + return dnsdist_config + + +def generate_dnsmasq_config(): + dns_settings = DNSSettings.objects.get(name='dns_settings') + static_hosts = StaticHost.objects.all() + dnsmasq_config = f''' +no-dhcp-interface= +listen-address=0.0.0.0 +bind-interfaces + +''' + if dns_settings.dns_primary: + dnsmasq_config += f'server={dns_settings.dns_primary}\n' + if dns_settings.dns_secondary: + dnsmasq_config += f'server={dns_settings.dns_secondary}\n' + + if static_hosts: + dnsmasq_config += '\n' + for static_host in static_hosts: + dnsmasq_config += f'address=/{static_host.hostname}/{static_host.ip_address}\n' + return dnsmasq_config diff --git a/dns/views.py b/dns/views.py index f3467f1..b11c68a 100644 --- a/dns/views.py +++ b/dns/views.py @@ -4,7 +4,7 @@ from django.contrib import messages from user_manager.models import UserAcl from .models import DNSSettings, StaticHost from .forms import StaticHostForm, DNSSettingsForm -from .functions import generate_unbound_config +from .functions import generate_dnsmasq_config from django.conf import settings @@ -13,9 +13,9 @@ def view_apply_dns_config(request): dns_settings, _ = DNSSettings.objects.get_or_create(name='dns_settings') dns_settings.pending_changes = False dns_settings.save() - unbound_config = generate_unbound_config() - with open(settings.UNBOUND_CONFIG, 'w') as f: - f.write(unbound_config) + dnsmasq_config = generate_dnsmasq_config() + with open(settings.DNS_CONFIG_FILE, 'w') as f: + f.write(dnsmasq_config) messages.success(request, 'DNS settings applied successfully') return redirect('/dns/') @@ -48,12 +48,7 @@ def view_manage_dns_settings(request):
All DNS queries will be forwarded to the primary resolver. If the primary resolver is not available, the secondary resolver will be used.
- - Local DNS Resolution - -- If no forwarders are specified, the system will locally resolve DNS queries. This can lead to slower DNS resolution times, but can be useful in certain scenarios. -
+ ''' context = { diff --git a/docker-compose-build.yml b/docker-compose-build.yml index 4d4ab41..66ce72c 100644 --- a/docker-compose-build.yml +++ b/docker-compose-build.yml @@ -11,6 +11,12 @@ services: context: ./cron dockerfile: Dockerfile-cron + wireguard-webadmin-dns: + image: eduardosilva/wireguard_webadmin_dns:latest + build: + context: ./containers/dnsmasq + dockerfile: Dockerfile-dnsmasq + wireguard-webadmin-nginx: image: eduardosilva/wireguard_webadmin_nginx:latest build: diff --git a/docker-compose-no-nginx-dev.yml b/docker-compose-no-nginx-dev.yml index c292ac9..1d3fc47 100644 --- a/docker-compose-no-nginx-dev.yml +++ b/docker-compose-no-nginx-dev.yml @@ -8,10 +8,12 @@ services: environment: - SERVER_ADDRESS=127.0.0.1 - DEBUG_MODE=True + - COMPOSE_VERSION=02b volumes: - wireguard:/etc/wireguard - static_volume:/app_static_files/ - .:/app + - dnsmasq_conf:/etc/dnsmasq ports: # Do not directly expose the Django port to the internet, use some kind of reverse proxy with SSL. - "127.0.0.1:8000:8000" @@ -38,6 +40,16 @@ services: depends_on: - wireguard-webadmin + wireguard-webadmin-dns: + container_name: wireguard-webadmin-dns + restart: unless-stopped + build: + context: ./containers/dnsmasq + dockerfile: Dockerfile-dnsmasq + volumes: + - dnsmasq_conf:/etc/dnsmasq/ + volumes: static_volume: wireguard: + dnsmasq_conf: \ No newline at end of file diff --git a/docker-compose-no-nginx.yml b/docker-compose-no-nginx.yml index 64328ab..a79a011 100644 --- a/docker-compose-no-nginx.yml +++ b/docker-compose-no-nginx.yml @@ -7,9 +7,11 @@ services: environment: - SERVER_ADDRESS=127.0.0.1 - DEBUG_MODE=True + - COMPOSE_VERSION=02b volumes: - wireguard:/etc/wireguard - static_volume:/app_static_files/ + - dnsmasq_conf:/etc/dnsmasq ports: # Do not directly expose the Django port to the internet, use some kind of reverse proxy with SSL. - "127.0.0.1:8000:8000" @@ -33,6 +35,14 @@ services: depends_on: - wireguard-webadmin + wireguard-webadmin-dns: + container_name: wireguard-webadmin-dns + restart: unless-stopped + image: eduardosilva/wireguard_webadmin_dns:latest + volumes: + - dnsmasq_conf:/etc/dnsmasq/ + volumes: static_volume: wireguard: + dnsmasq_conf: diff --git a/docker-compose.yml b/docker-compose.yml index 1ae4c7a..03ea77b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,11 @@ services: environment: - SERVER_ADDRESS=${SERVER_ADDRESS} - DEBUG_MODE=${DEBUG_MODE} + - COMPOSE_VERSION=02b volumes: - wireguard:/etc/wireguard - static_volume:/app_static_files/ + - dnsmasq_conf:/etc/dnsmasq ports: # Do not directly expose the Django port to the internet, use the reverse proxy below instead # - "127.0.0.1:8000:8000" @@ -33,6 +35,13 @@ services: depends_on: - wireguard-webadmin + wireguard-webadmin-dns: + container_name: wireguard-webadmin-dns + restart: unless-stopped + image: eduardosilva/wireguard_webadmin_dns:latest + volumes: + - dnsmasq_conf:/etc/dnsmasq/ + wireguard-webadmin-nginx: container_name: wireguard-webadmin-nginx restart: unless-stopped @@ -48,3 +57,4 @@ volumes: static_volume: https_cert: wireguard: + dnsmasq_conf: diff --git a/entrypoint.sh b/entrypoint.sh index bf25696..cdbd17b 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,6 +2,11 @@ set -e +if [[ "$COMPOSE_VERSION" != "02b" ]]; then + echo "ERROR: Please upgrade your docker compose file. Exiting." + exit 1 +fi + if [ -z "$SERVER_ADDRESS" ]; then echo "SERVER_ADDRESS environment variable is not set. Exiting." exit 1 diff --git a/firewall/tools.py b/firewall/tools.py index c1daf63..1c91e06 100644 --- a/firewall/tools.py +++ b/firewall/tools.py @@ -143,6 +143,19 @@ def export_user_firewall(): return "".join(iptables_rules) +def generate_redirect_dns_rules(): + wireguard_instance_list = WireGuardInstance.objects.all() + firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') + dns_redirect_rules = '' + for wireguard_instance in wireguard_instance_list: + dns_redirect_rules += f"# DNS Redirect for instance wg{wireguard_instance.instance_id}\n" + dns_redirect_rules += f"iptables -t nat -A WGWADM_PREROUTING -i wg{wireguard_instance.instance_id} -d {wireguard_instance.address} -p udp --dport 53 -j DNAT --to $DNS_IP:53\n" + dns_redirect_rules += f"iptables -t nat -A WGWADM_PREROUTING -i wg{wireguard_instance.instance_id} -d {wireguard_instance.address} -p tcp --dport 53 -j DNAT --to $DNS_IP:53\n" + dns_redirect_rules += f"iptables -t nat -A WGWADM_POSTROUTING -i wg{wireguard_instance.instance_id} -o {firewall_settings.wan_interface} -d $DNS_IP -j MASQUERADE\n" + dns_redirect_rules += f"iptables -t filter -A WGWADM_FORWARD -i wg{wireguard_instance.instance_id} -o {firewall_settings.wan_interface} -d $DNS_IP -j ACCEPT\n" + return dns_redirect_rules + + def generate_firewall_header(): firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') header = f'''#!/bin/bash @@ -151,6 +164,10 @@ def generate_firewall_header(): # # This script was generated by WireGuard_WebAdmin on {timezone.now().strftime('%Y-%m-%d %H:%M:%S %Z')} # +DNS_IP=$(host wireguard-webadmin-dns | grep -oP 'has address \K[\d\.]+') +if [ -z "$DNS_IP" ]; then + DNS_IP="127.0.0.250" +fi iptables -t nat -N WGWADM_POSTROUTING >> /dev/null 2>&1 iptables -t nat -N WGWADM_PREROUTING >> /dev/null 2>&1 diff --git a/templates/wireguard/wireguard_manage_server.html b/templates/wireguard/wireguard_manage_server.html index 46d21c4..71cdff0 100644 --- a/templates/wireguard/wireguard_manage_server.html +++ b/templates/wireguard/wireguard_manage_server.html @@ -98,11 +98,11 @@