From 088daafdff6e7c5b32c4663af75771e60ca65482 Mon Sep 17 00:00:00 2001 From: Jakub Czajka Date: Wed, 15 Jul 2026 22:52:27 +0000 Subject: [PATCH] [vps] Add dnscrypt-proxy. Package dnscrypt-proxy 2.1.7 with Shepherd service and resolv-conf-service-type that extends etc-service-type. Wire into vps-operating-system (dedicated user/group, resolver pointing at 127.0.0.1). Test dnscrypt-proxy service, port 53 TCP, resolv.conf, and NTP daemon. Co-Authored-By: Claude --- conf/vps/dnscrypt.scm | 121 +++++ dnscrypt-proxy/etc/blocked-names.txt | 5 + dnscrypt-proxy/etc/dnscrypt-proxy.toml | 724 +++++++++++++++++++++++++ tests/dnscrypt.scm | 28 + tests/ntp.scm | 17 + tests/vps-base.scm | 33 +- vps-system.scm | 29 +- 7 files changed, 942 insertions(+), 15 deletions(-) create mode 100644 conf/vps/dnscrypt.scm create mode 100644 dnscrypt-proxy/etc/blocked-names.txt create mode 100644 dnscrypt-proxy/etc/dnscrypt-proxy.toml create mode 100644 tests/dnscrypt.scm create mode 100644 tests/ntp.scm diff --git a/conf/vps/dnscrypt.scm b/conf/vps/dnscrypt.scm new file mode 100644 index 0000000..43c081b --- /dev/null +++ b/conf/vps/dnscrypt.scm @@ -0,0 +1,121 @@ +;;; Copyright (c) 2026 Jakub Czajka +;;; License: GPL-3.0 or later. +;;; +;;; dnscrypt.scm — dnscrypt-proxy 2.x DNS proxy for the VPS. + +(define-module (conf vps dnscrypt) + #:use-module (guix packages) + #:use-module (guix download) + #:use-module (guix gexp) + #:use-module (guix build-system trivial) + #:use-module ((guix licenses) + #:prefix license:) + #:use-module (gnu packages base) + #:use-module (gnu packages compression) + #:use-module (gnu services) + #:use-module (gnu services shepherd) + #:export (dnscrypt-proxy dnscrypt-proxy-shepherd-service + %dnscrypt-resolv-conf resolv-conf-service-type)) + +(define toml-config + (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH") + "/dnscrypt-proxy/etc/dnscrypt-proxy.toml")) + "dnscrypt-proxy.toml")) + +(define blocked-names + (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH") + "/dnscrypt-proxy/etc/blocked-names.txt")) + "blocked-names.txt")) + +(define dnscrypt-proxy + (package + (name "dnscrypt-proxy") + (version "2.1.7") + (source + (origin + (method url-fetch) + (uri (string-append "https://github.com/DNSCrypt/dnscrypt-proxy/" + "releases/download/" + version + "/dnscrypt-proxy-linux_x86_64-" + version + ".tar.gz")) + (sha256 + (base32 "1sgc3yc6zg5fzmi23g0aa6sr123mpir7jj3r42fspyngx0dbmfi5")))) + (build-system trivial-build-system) + (arguments + (list + #:modules '((guix build utils)) + #:builder + #~(begin + (use-modules (guix build utils)) + (let* ((out #$output) + (sbin (string-append out "/sbin")) + (etc (string-append out "/etc")) + (tmp "/tmp/dnscrypt-proxy-build")) + ;; Unpack the release tarball. + (mkdir-p tmp) + (setenv "PATH" + (string-append #$(file-append gzip "/bin"))) + (invoke #$(file-append tar "/bin/tar") + "xzf" + #$source + "-C" + tmp + "--strip-components=1") + ;; Binary -- Go, statically linked, no patchelf needed. + (mkdir-p sbin) + (copy-file (string-append tmp "/dnscrypt-proxy") + (string-append sbin "/dnscrypt-proxy")) + (chmod (string-append sbin "/dnscrypt-proxy") #o755) + ;; Config files. + (mkdir-p etc) + (copy-file #$toml-config + (string-append etc "/dnscrypt-proxy.toml")) + (copy-file #$blocked-names + (string-append etc "/blocked-names.txt")) + ;; Point the TOML at the blocked-names.txt in the store, + ;; replacing the human-readable /etc/dns/ placeholder. + (substitute* (string-append etc "/dnscrypt-proxy.toml") + (("'/etc/dns/blocked-names\\.txt'") + (string-append "'" etc "/blocked-names.txt'"))))))) + (inputs (list tar gzip)) + (home-page "https://github.com/DNSCrypt/dnscrypt-proxy") + (synopsis "Encrypted DNS proxy (v2)") + (description + "dnscrypt-proxy encrypts and authenticates DNS traffic between +your machine and upstream resolvers that support the DNSCrypt or DoH +protocols. This is version 2.x, the Go rewrite with built-in caching, +domain blocking and schedule support.") + (license license:isc))) + +(define dnscrypt-proxy-shepherd-service + (let ((bin (file-append dnscrypt-proxy "/sbin/dnscrypt-proxy")) + (cfg (file-append dnscrypt-proxy "/etc/dnscrypt-proxy.toml"))) + (shepherd-service (provision '(dnscrypt-proxy)) + (requirement '(user-processes networking)) + (respawn? #t) + ;; Binary drops privileges via user_name in the TOML. + ;; Do not pass #:user -- it must start as root. + (start #~(make-forkexec-constructor (list #$bin + "-config" + #$cfg) + #:log-file "/var/log/dnscrypt-proxy.log")) + (stop #~(make-kill-destructor)) + (documentation "dnscrypt-proxy encrypted DNS proxy.")))) + +(define %dnscrypt-resolv-conf + (plain-file "resolv.conf" "nameserver 127.0.0.1\noptions edns0\n")) + +;; Extends etc-service-type to install the resolv.conf. Uses its +;; own service type rather than simple-service to avoid creating +;; a second etc-service-type instance (which fold-services would +;; reject). +(define resolv-conf-service-type + (let ((etc-entry (list (list "resolv.conf" %dnscrypt-resolv-conf)))) + (service-type (name 'resolv-conf) + (extensions (list (service-extension etc-service-type + (const etc-entry)))) + (default-value #f) + (description + "Point /etc/resolv.conf to dnscrypt-proxy on localhost.")))) diff --git a/dnscrypt-proxy/etc/blocked-names.txt b/dnscrypt-proxy/etc/blocked-names.txt new file mode 100644 index 0000000..817d98d --- /dev/null +++ b/dnscrypt-proxy/etc/blocked-names.txt @@ -0,0 +1,5 @@ +# Blocked domain names for dnscrypt-proxy. +# Lines starting with # are comments. +# Add one domain per line. OISD is a good source: +# https://oisd.nl/ + diff --git a/dnscrypt-proxy/etc/dnscrypt-proxy.toml b/dnscrypt-proxy/etc/dnscrypt-proxy.toml new file mode 100644 index 0000000..ea78085 --- /dev/null +++ b/dnscrypt-proxy/etc/dnscrypt-proxy.toml @@ -0,0 +1,724 @@ +############################################## +# # +# dnscrypt-proxy configuration # +# # +############################################## + +## This is an example configuration file. +## You should adjust it to your needs, and save it as "dnscrypt-proxy.toml" +## +## Online documentation is available here: https://dnscrypt.info/doc + + + +################################## +# Global settings # +################################## + +## List of servers to use +## +## Servers from the "public-resolvers" source (see down below) can +## be viewed here: https://dnscrypt.info/public-servers +## +## The proxy will automatically pick working servers from this list. +## Note that the require_* filters do NOT apply when using this setting. +## +## By default, this list is empty and all registered servers matching the +## require_* filters will be used instead. +## +## Remove the leading # first to enable this; lines starting with # are ignored. + +# server_names = ['scaleway-fr', 'google', 'yandex', 'cloudflare'] + + +## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6. +## Example with both IPv4 and IPv6: +## listen_addresses = ['127.0.0.1:53', '[::1]:53'] +## +## To listen to all IPv4 addresses, use `listen_addresses = ['0.0.0.0:53']` +## To listen to all IPv4+IPv6 addresses, use `listen_addresses = ['[::]:53']` + +listen_addresses = ['0.0.0.0:53'] + + +## Maximum number of simultaneous client connections to accept + +max_clients = 250 + + +## Switch to a different system user after listening sockets have been created. +## Note (1): this feature is currently unsupported on Windows. +## Note (2): this feature is not compatible with systemd socket activation. +## Note (3): when using -pidfile, the PID file directory must be writable by the new user + +user_name = 'dnscrypt-proxy' + + +## Require servers (from remote sources) to satisfy specific properties + +# Use servers reachable over IPv4 +ipv4_servers = true + +# Use servers reachable over IPv6 -- Do not enable if you don't have IPv6 connectivity +ipv6_servers = false + +# Use servers implementing the DNSCrypt protocol +dnscrypt_servers = true + +# Use servers implementing the DNS-over-HTTPS protocol +doh_servers = true + +# Use servers implementing the Oblivious DoH protocol +odoh_servers = false + + +## Require servers defined by remote sources to satisfy specific properties + +# Server must support DNS security extensions (DNSSEC) +require_dnssec = true + +# Server must not log user queries (declarative) +require_nolog = true + +# Server must not enforce its own blocklist (for parental control, ads blocking...) +require_nofilter = true + +## Require servers (from remote sources) to satisfy specific properties + +server_names = ['dnscrypt.eu-nl', 'dnscrypt.uk-ipv4', 'ffmuc.net', 'meganerd', 'publicarray-au-doh', 'scaleway-ams', 'scaleway-fr', 'v.dnscrypt.uk-ipv4'] + +# Server names to avoid even if they match all criteria +disabled_server_names = [] + + +## Always use TCP to connect to upstream servers. +## This can be useful if you need to route everything through a SOCKS v5 +## proxy, or if your network drops fragmented UDP packets. Otherwise, +## leave it disabled — DNS over TCP is slower and less efficient. + +force_tcp = false + + +## Enable *experimental* support for HTTP/3 (DoH3, HTTP over QUIC). +## This requires a DoH server that supports HTTP/3, such as AdGuard DNS. +## Note that, like DNSCrypt but unlike other HTTP/3 implementations, +## DoH3 has perfect forward secrecy. + +http3 = false + + +## SOCKS proxy +## Uncomment the following line to route all TCP connections through a +## SOCKS v5 proxy. The proxy must support TCP connections on port 53. +## The SOCKS proxy may throw an error if the domain name can't be +## resolved; in that case, use IP addresses or add the domain to +## the `forwarding_rules` file. + +# proxy = 'socks5://127.0.0.1:9050' + + +## HTTP/HTTPS proxy +## Uncomment the following line to route all TCP connections through an +## HTTP/HTTPS proxy. This is only useful for DoH servers. + +# http_proxy = 'http://127.0.0.1:8888' + + +## How long a DNS query will wait for a response, in milliseconds. +## This can be reduced to 2500 if all used servers are nearby. + +timeout = 5000 + + +## Keepalive for HTTP/HTTPS queries, in seconds. + +keepalive = 30 + + +## Response for blocked queries. Options are `refused`, `hinfo` (which +## causes browsers to stop trying to resolve the name) and `force`. +## `force` returns the DNSSEC-signed response defined in the `[blocked_names]` +## section, or a generic DNS response if there isn't one. + +# blocked_query_response = 'refused' + + +## Load-balancing strategy: 'p2' (default), 'ph', 'p', 'random' or 'fastest'. +## The 'fastest' strategy requires the lb_estimator to be enabled. + +# lb_strategy = 'p2' + + +## Enable to estimate the optimal load balancing strategy for the current +## network conditions. + +# lb_estimator = true + + +## Log level (0-6, default: 2 — 0 is very verbose, 6 only contains +## fatal errors). + +# log_level = 2 + + +## Log file for the application, as an alternative to sending logs to +## the standard error. Leave empty to use the default, +## which varies according to the operating system. + +# log_file = 'dnscrypt-proxy.log' + + +## When using a log file, only keep logs from the most recent launch. + +# log_file_latest = true + + +## Use the system logger (syslog on Unix, Event Log on Windows). + +# use_syslog = true + + +## Delay, in minutes, after which certificates are reloaded. + +cert_refresh_delay = 240 + + +## DNSCrypt: Create a new, unique key for every single DNS query. +## This may improve privacy but can also have a significant impact on +## performance. Enable only if you are using a server that doesn't +## limit the number of queries per key. + +# dnscrypt_ephemeral_keys = false + + +## DoH: Disable TLS session tickets — increases privacy but may also +## increase latency. + +# tls_disable_session_tickets = false + + +## DoH: Use a specific cipher suite instead of the server's preferred one. +## 49199 = TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256. +## In some cases, this can significantly improve performance. + +# tls_cipher_suite = [52392, 49199] + + +## DoH: Log TLS secret keys to a file for use with Wireshark. +## Never enable this in a production environment. + +# tls_key_log_file = '/tmp/keylog.txt' + + +## Bootstrap resolvers. +## +## These are normal DNS resolvers that will be initially used to resolve +## the IP addresses of the DoH/ODoH/DoT resolvers you will be using, +## and the IP addresses of DNS relays. +## +## These are only required for DNS stamps using DNS-over-HTTPS +## (DoH) or DNS-over-TLS (DoT) for initial bootstrap. DNSCrypt +## stamps don't require them since they include IP addresses. +## +## Even if you don't use DoH/DoT, it's still useful to leave these +## set to widely available resolvers, for reliability. + +bootstrap_resolvers = ['9.9.9.11:53', '8.8.8.8:53'] + + +## Always use the bootstrap resolver before the system DNS settings. +## In some configurations, this is required to prevent the system DNS +## configuration from being used before dnscrypt-proxy is ready. + +ignore_system_dns = true + + +## The maximum time, in seconds, to wait for the network to be available +## before giving up. + +netprobe_timeout = 60 + + +## Address and port to try initializing a UDP socket to, to check if the +## network is up. + +netprobe_address = '9.9.9.9:53' + + +## Automatic log files rotation. + +# Maximum log file size, in MiB. The default is 10 MiB. +log_files_max_size = 10 + +# How long to keep log files, in days. +log_files_max_age = 7 + +# Maximum number of log files to keep. +log_files_max_backups = 1 + + + +######################### +# Filters # +######################### + +## Note: if you are using dnsmasq, disable all `block_*` filters — they +## are applied before dnsmasq gets the queries. + +## Immediately respond to IPv6-related queries with an empty response. +## This reduces load on internal resolvers. But if you have IPv6, or +## use Tor, disable it. + +block_ipv6 = false + +## Immediately respond to unqualified (single-label) names with an empty +## response. This prevents leakage on LLMNR and other protocols that +## might not be under your control. + +block_unqualified = true + +## Immediately respond to undelegated (private) TLDs with an empty +## response. + +block_undelegated = true + +## TTL for negative (blocked) responses. The default is 10 seconds. + +reject_ttl = 10 + + +############################### +# Cloaking rules # +############################### + +## Cloaking returns a static IP address for a specific, configured set of +## host names. This can be used for parental control systems, to +## redirect specific domains to trusted servers, or to reduce traffic +## to known advertising trackers. + +# forwarding_rules = 'forwarding-rules.txt' + + +## Cloaking rules file. One rule per line, format: +## . +## The resolver IP must be a public DNS resolver reachable from the proxy. + +# cloaking_rules = 'cloaking-rules.txt' + +## TTL used for cloaked entries (default 600 seconds). +# cloak_ttl = 600 + +## Set to `true` to enable automatic reverse cloaking: if a DNS query +## returns an IP address that is in the cloaking rules, the corresponding +## domain name is returned instead. + +# cloak_ptr = false + + +########################### +# DNS cache # +########################### + +## Enable a DNS cache to reduce latency and outgoing traffic. + +cache = true + +## Cache size, in number of entries. + +cache_size = 4096 + +## Minimum TTL for cached entries, in seconds. + +cache_min_ttl = 2400 + +## Maximum TTL for cached entries, in seconds. + +cache_max_ttl = 86400 + +## Minimum TTL for negatively cached entries, in seconds. + +cache_neg_min_ttl = 60 + +## Maximum TTL for negatively cached entries, in seconds. + +cache_neg_max_ttl = 600 + + +######################################## +# Captive portal handling # +######################################## + +[captive_portals] + +## A map file, one host name or regex per line, of sites that must be +## accessible even if a schedule would otherwise block them. +## This can be useful to allow access to a captive portal page when +## connected to a public WiFi network. + +# map_file = 'example-captive-portals.txt' + + +################################## +# Local DoH server # +################################## + +[local_doh] + +## dnscrypt-proxy can act as a local DoH server. If this section is +## present, a local DoH server will be started. + +## The address(es) and port(s) the local DoH server should listen to. +## Only TCP is supported. + +# listen_addresses = ['127.0.0.1:3000'] + + +## Path of the DoH URL. This is optional — if not specified, the +## path will be '/dns-query'. + +# path = '/dns-query' + + +## Certificate and key files for the local DoH server. These must be +## provided if the server is listening on a port different from 127.0.0.1. + +# cert_file = 'localhost.pem' +# cert_key_file = 'localhost.pem' + + +############################### +# Query logging # +############################### + +[query_log] + + ## Path to the query log file. Leave empty to disable query logging. + ## The format is one query per line, in the format specified below. + + # file = 'query.log' + + + ## Query log format: 'tsv', 'ltsv', or 'json'. + + format = 'tsv' + + + ## Do not log queries for these query types. + ## Use this to reduce the log size if you don't need to log all queries. + + # ignored_qtypes = ['DNSKEY', 'NS'] + + +############################################ +# Suspicious queries logging # +############################################ + +[nx_log] + + ## Path to the log file for suspicious queries. Leave empty to disable. + + # file = 'nx.log' + + + ## Query log format: 'tsv', 'ltsv', or 'json'. + + format = 'tsv' + + +###################################################### +# Pattern-based blocking (blocklists) # +###################################################### + +## Blocklists are made of one pattern per line. A pattern is a domain +## name, or a domain name with a wildcard prefix. +## Example: `*.example.com` matches `www.example.com` and all other +## subdomains of `example.com`. +## +## The path to the file is resolved from the dnscrypt-proxy working +## directory when it is a relative path. + +[blocked_names] + + ## Path to the file containing blocked names. + + blocked_names_file = '/etc/dns/blocked-names.txt' + + + ## Optional path to a file logging blocked queries. + + # log_file = 'blocked-names.log' + + + ## Optional log format: 'tsv', 'ltsv', or 'json'. + + # log_format = 'tsv' + + +########################################################### +# Pattern-based IP blocking (IP blocklists) # +########################################################### + +[blocked_ips] + + ## Path to the file containing blocked IP addresses. + + # blocked_ips_file = 'blocked-ips.txt' + + + ## Optional path to a file logging blocked queries. + + # log_file = 'blocked-ips.log' + + + ## Optional log format: 'tsv', 'ltsv', or 'json'. + + # log_format = 'tsv' + + +###################################################### +# Pattern-based allow lists (blocklists bypass) # +###################################################### + +[allowed_names] + + ## Path to the file containing allowed names. + + # allowed_names_file = 'allowed-names.txt' + + + ## Optional path to a file logging allowed queries. + + # log_file = 'allowed-names.log' + + + ## Optional log format: 'tsv', 'ltsv', or 'json'. + + # log_format = 'tsv' + + +######################################################### +# Pattern-based allowed IPs lists (blocklists bypass) # +######################################################### + +[allowed_ips] + + ## Path to the file containing allowed IP addresses. + + # allowed_ips_file = 'allowed-ips.txt' + + + ## Optional path to a file logging allowed queries. + + # log_file = 'allowed-ips.log' + + + ## Optional log format: 'tsv', 'ltsv', or 'json'. + + # log_format = 'tsv' + + +########################################## +# Time access restrictions # +########################################## + +[schedules] + + ## Schedule of the day during which DNS queries should be blocked. + ## Times are in UTC. + + [schedules.time-to-sleep] + mon = [{after='21:00', before='5:00'}] + tue = [{after='21:00', before='5:00'}] + wed = [{after='21:00', before='5:00'}] + thu = [{after='21:00', before='5:00'}] + fri = [{after='21:00', before='5:00'}] + sat = [{after='21:00', before='5:00'}] + sun = [{after='21:00', before='5:00'}] + + # [schedules.work] + # mon = [{after='9:00', before='18:00'}] + # tue = [{after='9:00', before='18:00'}] + # wed = [{after='9:00', before='18:00'}] + # thu = [{after='9:00', before='18:00'}] + # fri = [{after='9:00', before='17:00'}] + + +######################### +# Servers # +######################### + +## Remote servers (DNS stamps, from DNSCrypt, DoH and DoT servers) +## are loaded from remote sources. + +[sources] + + ## An example of a remote source. + + [sources.public-resolvers] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md'] + cache_file = 'public-resolvers.md' + minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + refresh_delay = 73 + prefix = '' + + ## An example of a remote source for relays. + + [sources.relays] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md'] + cache_file = 'relays.md' + minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + refresh_delay = 73 + prefix = '' + + ## ODoH (Oblivious DoH) servers. + + # [sources.odoh-servers] + # urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md'] + # cache_file = 'odoh-servers.md' + # minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + # refresh_delay = 73 + # prefix = '' + + ## ODoH (Oblivious DoH) relays. + + # [sources.odoh-relays] + # urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md'] + # cache_file = 'odoh-relays.md' + # minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + # refresh_delay = 73 + # prefix = '' + + ## Quad9 + + # [sources.quad9-resolvers] + # urls = ['https://www.quad9.net/quad9-resolvers.md'] + # minisign_key = 'RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJNMKqv=m7wzB2pLDkVF' + # cache_file = 'quad9-resolvers.md' + # refresh_delay = 73 + # prefix = 'quad9-' + + ## Planet Freeze + + # [sources.dnscry-pt-resolvers] + # urls = ['https://www.dnscry.pt/resolvers.md'] + # cache_file = 'dnscry-pt-resolvers.md' + # refresh_delay = 73 + # prefix = 'dnscry-pt-' + + ## Parental Control + + # [sources.parental-control] + # urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/parental-control.md', 'https://download.dnscrypt.info/resolvers-list/v3/parental-control.md'] + # cache_file = 'parental-control.md' + # minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + # refresh_delay = 73 + # prefix = '' + + +######################################### +# Servers with known bugs # +######################################### + +[broken_implementations] + + ## Cisco servers cannot handle fragmented queries larger than 1500 + ## bytes, which is way below the expected minimal MTU. + + fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'cisco-familyshield-ipv6', 'cisco-sandbox', 'cleanbrowsing-adult', 'cleanbrowsing-adult-ipv6', 'cleanbrowsing-family', 'cleanbrowsing-family-ipv6', 'cleanbrowsing-security', 'cleanbrowsing-security-ipv6'] + + +################################################################# +# Certificate-based client authentication for DoH # +################################################################# + +## Use X.509 certificates to authenticate DNS-over-HTTPS clients. + +[doh_client_x509_auth] + + ## Enable DNS over HTTPS client authentication via X.509 certificates. + ## This is optional — if not present, all clients will be allowed. + + # creds = [ + # { + # client_id = 'client1' + # server_name = 'server1.com' + # cert_file = 'client1.pem' + # key_file = 'client1.key' + # ca_file = 'ca.pem' + # } + # ] + + +################################ +# Anonymized DNS # +################################ + +## Anonymized DNS relays. +## +## DNS queries can be sent to a DNS relay, that will forward them to a +## DNS server. This is useful to hide your IP address from the DNS +## server, and to bypass IP-based rate limiting. +## +## An Anonymized DNS relay is a server that implements the Anonymized +## DNSCrypt protocol. It is not a DNS resolver. + +[anonymized_dns] + + ## Routes are indirect ways to reach a DNS server. Each route is + ## a list of relay names, followed by the server name. + ## + ## A route can be as simple as a single relay, or can be a chain of + ## relays. If a chain of relays is used, the last relay in the chain + ## will connect to the DNS server. + ## + ## Empty routes are ignored. + + # routes = [ + # { server_name='example-server', via=['anon-example-1', 'anon-example-2'] } + # ] + + + ## Skip resolvers incompatible with anonymization instead of using them + ## without the relay. + + skip_incompatible = false + + ## If a relay is not available, try to connect to the DNS server + ## directly. This is the default. + + # direct_cert_fallback = false + + +############################### +# DNS64 # +############################### + +## DNS64 enables IPv6-only networks to reach IPv4-only destinations. +## This is useful for mobile networks, and for networks that are +## transitioning to IPv6. + +[dns64] + + ## IPv6 prefix to use for DNS64 synthesis. + + # prefix = ['64:ff9b::/96'] + + ## DNS64 resolver(s) to use. These are regular DNS resolvers that + ## will be used to resolve the IPv4 addresses. + + # resolver = ['[2606:4700:4700::64]:53', '[2001:4860:4860::64]:53'] + + +######################################## +# Static entries # +######################################## + +## Static entries can be used to define custom DNS records. + +[static] + + ## A static entry is a DNS record that is always returned for a + ## specific domain name, without querying an upstream server. + + # [static.myserver] + # stamp = 'sdns://...' diff --git a/tests/dnscrypt.scm b/tests/dnscrypt.scm new file mode 100644 index 0000000..a486ab2 --- /dev/null +++ b/tests/dnscrypt.scm @@ -0,0 +1,28 @@ +;;; Copyright (c) 2026 Jakub Czajka +;;; License: GPL-3.0 or later. +;;; +;;; dnscrypt-proxy test cases. + +(define-module (tests dnscrypt) + #:use-module (guix gexp) + #:export (dnscrypt-test-cases)) + +(define (dnscrypt-test-cases marionette) + "Return a gexp with dnscrypt-proxy test assertions." + #~(begin + (test-assert "dnscrypt: service running" + (marionette-eval '(begin + (use-modules (gnu services herd)) + (start-service 'dnscrypt-proxy)) + #$marionette)) + + (test-assert "dnscrypt: port 53 TCP" + (wait-for-tcp-port 53 + #$marionette)) + + ;; Verify resolv.conf was installed by the service. + ;; Its content is fixed (nameserver 127.0.0.1), so + ;; existence alone validates it. + (test-assert "dnscrypt: resolv.conf exists" + (marionette-eval '(file-exists? "/etc/resolv.conf") + #$marionette)))) diff --git a/tests/ntp.scm b/tests/ntp.scm new file mode 100644 index 0000000..8f3fc25 --- /dev/null +++ b/tests/ntp.scm @@ -0,0 +1,17 @@ +;;; Copyright (c) 2026 Jakub Czajka +;;; License: GPL-3.0 or later. +;;; +;;; NTP test cases. + +(define-module (tests ntp) + #:use-module (guix gexp) + #:export (ntp-test-cases)) + +(define (ntp-test-cases marionette) + "Return a gexp with NTP daemon test assertion." + #~(begin + (test-assert "ntp: ntpd running" + (marionette-eval '(begin + (use-modules (gnu services herd)) + (start-service 'ntpd)) + #$marionette)))) diff --git a/tests/vps-base.scm b/tests/vps-base.scm index 33fa71a..a074d19 100644 --- a/tests/vps-base.scm +++ b/tests/vps-base.scm @@ -21,11 +21,13 @@ #:use-module (gnu packages ssh) #:use-module (vps-system) #:use-module (vps-home) + #:use-module (tests sshd) + #:use-module (tests networking) + #:use-module (tests ntp) #:use-module (tests claude-code) #:use-module (tests dotfiles) #:use-module (tests paseo) - #:use-module (tests sshd) - #:use-module (tests networking) + #:use-module (tests dnscrypt) #:use-module (guix gexp) #:export (%test-vps %vps-test-os run-vps-test)) @@ -187,24 +189,27 @@ every function in TEST-CASES. Each element is a function #$marionette-gexp)))) ;;; -;;; System test record — test cases are added incrementally in -;;; subsequent commits. +;;; System test record. ;;; (define %test-vps - (let ((claude claude-code-test-cases) - (paseo paseo-test-cases) - (dots dotfiles-test-cases) - (home home-activation-test-cases) - (ssh (lambda (m) + (let ((ssh (lambda (m) (sshd-test-cases m (make-ssh-connect m)))) - (net networking-test-cases)) + (net networking-test-cases) + (ntp ntp-test-cases) + (dns dnscrypt-test-cases) + (claude claude-code-test-cases) + (paseo paseo-test-cases) + (dots dotfiles-test-cases) + (home home-activation-test-cases)) (system-test (name "vps") (description "VPS test suite.") - (value (run-vps-test (list claude + (value (run-vps-test (list ssh + net + ntp + dns + claude paseo dots - home - ssh - net)))))) + home)))))) diff --git a/vps-system.scm b/vps-system.scm index 7eb60b9..2258573 100644 --- a/vps-system.scm +++ b/vps-system.scm @@ -12,6 +12,7 @@ #:use-module (gnu packages bash) #:use-module (nongnu packages linux) #:use-module (conf vps sshd) + #:use-module (conf vps dnscrypt) #:export (vps-operating-system)) (use-service-modules networking shepherd) @@ -46,8 +47,18 @@ (supplementary-groups '("wheel")) (home-directory "/home/dak") (shell (file-append bash "/bin/bash"))) + (user-account + (name "dnscrypt-proxy") + (group "dnscrypt-proxy") + (system? #t) + (home-directory "/var/empty") + (shell (file-append shadow "/sbin/nologin"))) %base-user-accounts)) + (groups (cons (user-group + (name "dnscrypt-proxy") + (system? #t)) %base-groups)) + ;; Services (services @@ -58,7 +69,23 @@ %ssh-service ;; NTP — correct time is required for Guix substitutes - (service ntp-service-type)) %base-services)) + (service ntp-service-type) + + ;; dnscrypt-proxy — encrypted DNS proxy + (simple-service 'dnscrypt-proxy-daemon + shepherd-root-service-type + (list dnscrypt-proxy-shepherd-service)) + + ;; Point system resolver to localhost. + ;; Uses a dedicated service type that extends + ;; etc-service-type rather than being one. + (service resolv-conf-service-type) + + ;; TODO: nginx stream for DNS-over-TLS on port 853. + ;; See server.git dnscrypt-proxy/dnscrypt-proxy.conf + ;; and the metadata.git Ansible playbook for the + ;; envsubst-based deployment pattern (SSL certs needed). + ) %base-services)) ;; Sudoers: dak can reconfigure without a password -- 2.47.3