diff --git a/hosts/plover/config/coredns/foodogsquared.one.zone b/hosts/plover/config/coredns/foodogsquared.one.zone new file mode 100644 index 00000000..2f813f23 --- /dev/null +++ b/hosts/plover/config/coredns/foodogsquared.one.zone @@ -0,0 +1,58 @@ +; This is trying to be discrete with certain information. This should be copied +; and replaced with more confidential information somewhere. + +; Take note we're not making the +$TTL 2h +$ORIGIN @domain@ + +@ IN SOA @dnsSubdomain@.@domain@ @email@ ( + 2023020800 ; serial number + 3h ; refresh + 15m ; update retry + 3w ; expiry + 3h ; nx = nxdomain ttl + ) + +; Setting up the mail-related DNS entries. +; For future references, please the see the following document at +; https://kb.mailbox.org/en/private/e-mail-article/using-e-mail-addresses-of-your-domain +@ IN MX 10 mxext1.mailbox.org + IN MX 10 mxext2.mailbox.org + IN MX 20 mxext3.mailbox.org + IN TXT v=spf1 include:mailbox.org ~all +_dmarc IN TXT v=DMARC1;p=none;rua=mailto:postmaster@foodogsquared.one +mbo0001._domainkey IN CNAME mbo0001._domainkey.mailbox.org. +mbo0002._domainkey IN CNAME mbo0002._domainkey.mailbox.org. +mbo0003._domainkey IN CNAME mbo0003._domainkey.mailbox.org. +mbo0004._domainkey IN CNAME mbo0004._domainkey.mailbox.org. +#mailboxSecurityKey# IN TXT #mailboxSecurityKeyRecord# + +; My websites that are deployed by somewhere else. +@ IN ALIAS apex-loadbalancer.netlify.com. +www IN CNAME foodogsquared.netlify.app. +wiki IN CNAME foodogsquared-wiki.netlify.app. + +; Public-facing services from this server. +auth IN A @publicIPv4@ +auth IN AAAA @publicIPv6@ + +pass IN A @publicIPv4@ +pass IN AAAA @publicIPv6@ + +code IN A @publicIPv4@ +code IN AAAA @publicIPv6@ + +; Other things. +_github-pages-challenge-foo-dogsquared IN TXT 673febae1ea0095e76d1e02a7a1709 + +; Setting up SendGrid. +; This is for rewriting tracking links to my domain. +url2871 IN CNAME sendgrid.net +30339354 IN CNAME sendgrid.net + +; This is for SendGrid sender authentication. +em1172 IN CNAME u30339354.wl105.sendgrid.net +s1._domainkey IN CNAME s1.domainkey.u30339354.wl105.sendgrid.net +s2._domainkey IN CNAME s2.domainkey.u30339354.wl105.sendgrid.net + +; vim: expandtab! tabstop=8 shiftwidth=8 filetype=dns diff --git a/hosts/plover/default.nix b/hosts/plover/default.nix index 626e1e4e..426cbbfc 100644 --- a/hosts/plover/default.nix +++ b/hosts/plover/default.nix @@ -22,6 +22,9 @@ in # Hardened profile from nixpkgs. "${modulesPath}/profiles/hardened.nix" + # The primary DNS server that is completely hidden. + ./modules/services/coredns.nix + ./modules/services/nginx.nix # The database of choice which is used by most self-managed services on diff --git a/hosts/plover/modules/hardware/hetzner-cloud-cx21.nix b/hosts/plover/modules/hardware/hetzner-cloud-cx21.nix index e1087d4c..86edb5d6 100644 --- a/hosts/plover/modules/hardware/hetzner-cloud-cx21.nix +++ b/hosts/plover/modules/hardware/hetzner-cloud-cx21.nix @@ -50,15 +50,6 @@ in dhcpcd.enable = false; }; - # The internal DNS server of choice. - services.dnsmasq = { - enable = true; - settings = { - listen-address = with interfaces.internal; [ IPv4.address IPv6.address ]; - port = 3908; - }; - }; - # The main DNS server (not exactly by choice). services.resolved = { enable = true; @@ -103,6 +94,11 @@ in IPv4.gateway IPv6.gateway ]; + + networkConfig.DNS = [ + "127.0.0.1" + "::1" + ]; }; }; }; diff --git a/hosts/plover/modules/hardware/networks.nix b/hosts/plover/modules/hardware/networks.nix index 742dc8ae..d3f34bec 100644 --- a/hosts/plover/modules/hardware/networks.nix +++ b/hosts/plover/modules/hardware/networks.nix @@ -6,6 +6,16 @@ let in rec { privateIPv6Prefix = "fdee:b0de:5685"; + + clientNetworks = [ + "172.24.0.0/13" + "10.128.0.0/9" + ]; + serverNetworks = [ + "172.16.0.0/13" + "10.0.0.0/9" + ]; + interfaces = let ploverInternalNetworkGateway = "172.16.0.1"; widdeerLan = "10.0.0.1"; @@ -79,4 +89,19 @@ rec { IPv6 = "${wireguardIPv6Prefix}3"; }; }; + + secondaryNameServers = { + "ns1.first-ns.de." = { + IPv4 = [ "213.239.242.238" ]; + IPv6 = [ "2a01:4f8:0:a101::a:1" ]; + }; + "robotns2.second-ns.de." = { + IPv4 = [ "213.133.105.6" ]; + IPv6 = [ "2a01:4f8:d0a:2004::2" ]; + }; + "robotns3.second-ns.com." = { + IPv4 = [ "193.47.99.3" ]; + IPv6 = [ "2001:67c:192c::add:a3" ]; + }; + }; } diff --git a/hosts/plover/modules/services/atuin.nix b/hosts/plover/modules/services/atuin.nix index 48cdc9bc..6e80d5a5 100644 --- a/hosts/plover/modules/services/atuin.nix +++ b/hosts/plover/modules/services/atuin.nix @@ -30,9 +30,6 @@ in ''; }; - # Attaching the domain name to the DNS server. - services.dnsmasq.settings.address = [ "/${atuinInternalDomain}/${host}" ]; - # Putting it altogether in the reverse proxy of choice. services.nginx.virtualHosts."${atuinInternalDomain}" = { locations."/" = { diff --git a/hosts/plover/modules/services/coredns.nix b/hosts/plover/modules/services/coredns.nix new file mode 100644 index 00000000..ffc8b335 --- /dev/null +++ b/hosts/plover/modules/services/coredns.nix @@ -0,0 +1,189 @@ +{ config, options, lib, pkgs, ... }: + +# Take note we're also running with systemd-resolved which shouldn't really +# conflict much with established DNS servers default configuration considering +# it lives in 127.0.0.53 (not 127.0.0.1). So if you found some errors, that's +# on you. Either that or we can easily move the resolver somewhere else. +let + inherit (config.networking) domain fqdn; + inherit (import ../hardware/networks.nix) interfaces clientNetworks serverNetworks secondaryNameServers; + + dnsSubdomain = "ns1"; + dnsDomainName = "${dnsSubdomain}.${domain}"; + certs = config.security.acme.certs; + dnsEmail = "hostmaster.${domain}"; + + # This is the part of the SOA record. You'll have to modify it here instead + # of modifying a zone file since it does not play well with a dynamically + # configured server it seems. + dnsSerialNumber = "2023020800"; + dnsRefresh = "3h"; + dnsUpdateRetry = "15m"; + dnsExpiry = "3w"; + dnsNxTTL = "3h"; + + corednsServiceName = "coredns"; + + domainZoneFile = pkgs.substituteAll { + src = ../../config/coredns/${domain}.zone; + inherit domain dnsSubdomain; + email = dnsEmail; + publicIPv4 = interfaces.main'.IPv4.address; + publicIPv6 = interfaces.main'.IPv6.address; + }; + + secondaryNameserverDomains = lib.attrNames secondaryNameServers; + secondaryNameServersIPv4 = lib.foldl' + (total: addresses: total ++ addresses.IPv4) + [ ] + (lib.attrValues secondaryNameServers); + secondaryNameServersIPv6 = lib.foldl' + (total: addresses: total ++ addresses.IPv6) + [ ] + (lib.attrValues secondaryNameServers); + secondaryNameServersIPs = secondaryNameServersIPv4 ++ secondaryNameServersIPv6; + + # The final location of the thing. + domainZoneFile' = "/etc/coredns/zones/${domain}.zone"; +in +{ + sops.secrets = let + getKey = key: { + inherit key; + sopsFile = ../../secrets/secrets.yaml; + }; + getSecrets = secrets: + lib.mapAttrs' + (secret: config: + lib.nameValuePair + "plover/${secret}" + ((getKey secret) // config)) + secrets; + in + getSecrets { + "dns/mailbox-security-key" = { }; + "dns/mailbox-security-key-record" = { }; + }; + + # Generating a certificate for the DNS-over-TLS feature. + security.acme.certs."${dnsDomainName}".postRun = '' + systemctl restart ${corednsServiceName}.service + ''; + + # Setting up the firewall to make less things to screw up in case anything is + # screwed up. + networking.firewall.extraInputRules = '' + meta l4proto {tcp, udp} th dport 53 ip saddr { ${lib.concatStringsSep ", " secondaryNameServersIPv4} } accept comment "Accept DNS queries from secondary nameservers" + meta l4proto {tcp, udp} th dport 53 ip6 saddr { ${lib.concatStringsSep ", " secondaryNameServersIPv6} } accept comment "Accept DNS queries from secondary nameservers" + ''; + + # The main DNS server. + services.coredns = { + enable = true; + + # NOTE: Currently, Hetzner DNS servers does not support DNSSEC. Will need + # to visit the following document periodically to see if they support but + # it is doubtful since they are doubting the benefits of supporting it. :( + # + # From what I can tell, it seems like DNSSEC is not much embraced by the + # major organizations yet so I'll wait with them on this one. + # + # https://docs.hetzner.com/dns-console/dns/general/dnssec + config = '' + (common) { + forward . /etc/resolv.conf + log + cache + errors + } + + ${fqdn} { + import common + + bind ${interfaces.internal.IPv4.address} ${interfaces.internal.IPv6.address} + + local + + acl { + allow net ${lib.concatStringsSep " " (clientNetworks ++ serverNetworks)} + block + } + + template ANY ANY { + authority "{{ .Zone }} IN SOA {{ .Zone }} ${dnsEmail} (1 60 60 60 60)" + fallthrough + } + + template IN A { + answer "{{ .Zone }} IN 60 A ${interfaces.internal.IPv4.address}" + answer "{{ .Zone }} IN 60 A ${interfaces.internal.IPv4.address}" + } + + template IN AAAA { + answer "{{ .Zone }} IN 60 AAAA ${interfaces.internal.IPv6.address}" + answer "{{ .Zone }} IN 60 AAAA ${interfaces.internal.IPv6.address}" + } + } + + ${domain} { + import common + + bind lo { + # These are already taken from systemd-resolved. + except 127.0.0.53 127.0.0.54 + } + + acl { + # We're setting this up as a "hidden" primary server. + allow type AXFR net ${lib.concatStringsSep " " secondaryNameServersIPs} + allow type IXFR net ${lib.concatStringsSep " " secondaryNameServersIPs} + block type AXFR + block type IXFR + } + + template IN NS { + ${lib.concatStringsSep "\n " + (lib.lists.map + (ns: ''answer "{{ .Zone }} IN NS ${ns}"'') + secondaryNameserverDomains)} + } + + file ${domainZoneFile'} + + transfer { + to * + } + } + + tls://${domain} { + import common + + tls {$CREDENTIALS_DIRECTORY}/cert.pem {$CREDENTIALS_DIRECTORY}/key.pem {$CREDENTIALS_DIRECTORY}/fullchain.pem + } + ''; + }; + + # This is based from the Gitea pre-start script. + systemd.services.${corednsServiceName} = { + requires = [ "acme-finished-${dnsDomainName}.target" ]; + preStart = + let + secretsPath = path: config.sops.secrets."plover/${path}".path; + replaceSecretBin = "${lib.getBin pkgs.replace-secret}/bin/replace-secret"; + in + lib.mkBefore '' + install -Dm0644 ${domainZoneFile} ${domainZoneFile'} + + ${replaceSecretBin} '#mailboxSecurityKey#' '${secretsPath "dns/mailbox-security-key"}' '${domainZoneFile'}' + ${replaceSecretBin} '#mailboxSecurityKeyRecord#' '${secretsPath "dns/mailbox-security-key-record"}' '${domainZoneFile'}' + ''; + serviceConfig.LoadCredential = let + certDirectory = certs."${dnsDomainName}".directory; + in + [ + "cert.pem:${certDirectory}/cert.pem" + "key.pem:${certDirectory}/key.pem" + "fullchain.pem:${certDirectory}/fullchain.pem" + ]; + }; +} diff --git a/hosts/plover/modules/services/keycloak.nix b/hosts/plover/modules/services/keycloak.nix index 0f257b3c..c834f127 100644 --- a/hosts/plover/modules/services/keycloak.nix +++ b/hosts/plover/modules/services/keycloak.nix @@ -12,7 +12,7 @@ let keycloakDbName = if config.services.keycloak.database.createLocally then keycloakUser else config.services.keycloak.database.username; certs = config.security.acme.certs; - host = interfaces.internal.IPv4.address; + host = "127.0.0.1"; in { # Hey, the hub for your application sign-in. @@ -69,9 +69,6 @@ in ''; }; - # Attach an domain name to the DNS server. - services.dnsmasq.settings.address = [ "/${authInternalDomain}/${host}" ]; - # Attaching it to the reverse proxy of choice. services.nginx.virtualHosts = { "${authDomain}" = { diff --git a/hosts/plover/modules/services/wireguard.nix b/hosts/plover/modules/services/wireguard.nix index 97c972de..f7f08cde 100644 --- a/hosts/plover/modules/services/wireguard.nix +++ b/hosts/plover/modules/services/wireguard.nix @@ -58,11 +58,9 @@ in matchConfig.Name = wireguardIFName; networkConfig = { - DNS = with interfaces.internal; let - internalDNSPort = config.services.dnsmasq.settings.port; - in [ - "${IPv4.address}:${toString internalDNSPort}" - "${IPv6.address}:${toString internalDNSPort}" + DNS = with interfaces.internal; [ + "127.0.0.1" + "::1" ]; Domains = lib.concatStringsSep " " internalDomains; DNSDefaultRoute = false; diff --git a/hosts/plover/secrets/secrets.yaml b/hosts/plover/secrets/secrets.yaml index 37c92b68..5795f742 100644 --- a/hosts/plover/secrets/secrets.yaml +++ b/hosts/plover/secrets/secrets.yaml @@ -36,6 +36,9 @@ wireguard: preshared-keys: ni: ENC[AES256_GCM,data:NAgNnVtPKCaaSagWCIet5pd5ZehymJPmhQShoO/ktqa1pl6MtzJsygbTktk=,iv:2/sOdNN6QX1Rou5xnq87t/m/kguPTthOXD8oXJfvM90=,tag:F/I2CYR9O1LAlLs/9LaXGg==,type:str] phone: ENC[AES256_GCM,data:3wIv8mE7eYhvSjwcE9fwsUZhh2Svmzg+RFjJzvjvMyB9V3uvBYG8vmB751w=,iv:iSm4dXNVqFa52eq0Hhct1MGSoq4x7FFzWdjXHlkGTW8=,tag:Lr463ee5r/ZhEC78uYyzfQ==,type:str] +dns: + mailbox-security-key: ENC[AES256_GCM,data:e1/y+JNNUxdf5D0OVhTD8hsoPlvV2Jpp3/+nHBktZH02/ZOfa9W5oA==,iv:tR9aVFHuMekr2uz4MaGAddlRsAh9XctS25EO+yMyvhE=,tag:BImSGpx4ltuU1qPTrmioKg==,type:str] + mailbox-security-key-record: ENC[AES256_GCM,data:bP1kKQczfjOQyokOa+cScNs7jKLaXamUHYqzW7k0QRXnin5Nsj7G9w==,iv:l8NtJcYll8rdQJLsuxPIj3Ch3Tc/ESs1wUwPrGRTI7Y=,tag:RWD9mvinKW0xv6GDw/4jkQ==,type:str] sops: kms: [] gcp_kms: [] @@ -51,8 +54,8 @@ sops: ZCtNbnFqdzNkVlBtNjVCdE4yNHMrRjQKfFV4GaReO0UO81xsTB0EuN5ibVsafXJY miBgZAZWbJjSBcM4X+Fym/DlxHRoB1a6iFEFN9yg+Z9WI8PfjKnbsA== -----END AGE ENCRYPTED FILE----- - lastmodified: "2023-01-17T09:10:51Z" - mac: ENC[AES256_GCM,data:wzxOoyd+Zq6Fna1GxCZ6Uha6rdW3xVRx3uJniyuJ6TpGCpMoCzv9O0F7JgYyK7je2GR1RomynECEGu84re46yK9AkwV9KKWXVAs8jaI9QeZ6usEbFdr7KmyLFZbO/OMP72OIV03wxJ7Hguz2snF5ONjjhbAfZ2n+xXTofFJqBZY=,iv:cMZdVJQ4Vpf1FB70De3Rf/iMqp2Uv1QARp6897bew1A=,tag:rRx+ufoCVqqOGWhURxo6aQ==,type:str] + lastmodified: "2023-02-08T09:58:04Z" + mac: ENC[AES256_GCM,data:VJiZJclfId8pwfKNKyRWtMVX3e6yAjrQmEmQPbW/0nQCxfSe6ixapmoEheMEaF1WbJe0sOiKV8i3GZbxW+aLX9RN6+fOB/lKqSUigQOLgFjs9fgDG9WN4HCzeWPWm+vkfsL3+A5T3ucKHyqjEFJ91BpcA1x+fa5qNAnnaXCifJM=,iv:/oPeVaLbu+yWXF86b8Dc16JuuhCDlJKluK+GrQl0VMU=,tag:0ox7Qf8FdPtOY9GXkqAhkQ==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3