From 9ed4c08d8c03f8d12586c25cddc33da92a20c218 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Tue, 22 Feb 2022 10:48:18 +0100 Subject: surtr: tls/dns: rfc2136 for rheperire.org --- hosts/surtr/default.nix | 2 +- hosts/surtr/dns/default.nix | 44 +++++++- hosts/surtr/dns/keys/.sops.yaml | 3 + hosts/surtr/dns/keys/rheperire.org_acme.yaml | 38 +++++++ hosts/surtr/tls.nix | 120 --------------------- hosts/surtr/tls/default.nix | 156 +++++++++++++++++++++++++++ hosts/surtr/tls/tsig_keys/rheperire.org | 26 +++++ 7 files changed, 266 insertions(+), 123 deletions(-) create mode 100644 hosts/surtr/dns/keys/.sops.yaml create mode 100644 hosts/surtr/dns/keys/rheperire.org_acme.yaml delete mode 100644 hosts/surtr/tls.nix create mode 100644 hosts/surtr/tls/default.nix create mode 100644 hosts/surtr/tls/tsig_keys/rheperire.org (limited to 'hosts/surtr') diff --git a/hosts/surtr/default.nix b/hosts/surtr/default.nix index 30a9e165..add50653 100644 --- a/hosts/surtr/default.nix +++ b/hosts/surtr/default.nix @@ -2,7 +2,7 @@ { imports = with flake.nixosModules.systemProfiles; [ qemu-guest openssh rebuild-machines zfs - ./zfs.nix ./dns ./tls.nix ./http.nix ./bifrost + ./zfs.nix ./dns ./tls ./http.nix ./bifrost ]; config = { diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix index feb56195..9a72a2c6 100644 --- a/hosts/surtr/dns/default.nix +++ b/hosts/surtr/dns/default.nix @@ -1,5 +1,18 @@ -{ pkgs, ... }: -{ +{ pkgs, lib, ... }: + +with lib; + +let + acmeChallengeZonefile = domain: let + reverseDomain = concatStringsSep "." (reverseList ("_acme-challenge" ++ splitString "." domain)); + in pkgs.writeText "${reverseDomain}.zone" '' + $ORIGIN ${domain}. + @ 3600 IN SOA _acme-challenge.${domain}. root.yggdrasil.li. 2022022102 7200 3600 86400 300 + $TTL 300 + + IN NS ns.yggdrasil.li. + ''; +in { config = { fileSystems."/var/lib/knot" = { device = "surtr/safe/var-lib-knot"; @@ -10,6 +23,9 @@ services.knot = { enable = true; + keyFiles = [ + config.sops.secrets."acme_rheperire.org_key".path + ]; extraConfig = '' server: listen: 127.0.0.1@53 @@ -27,6 +43,9 @@ - id: inwx_acl address: 185.181.104.96 action: transfer + - id: rheperire.org_acme_acl + key: rheperire.org_acme_key + action: update mod-rrl: - id: default @@ -71,6 +90,15 @@ dnssec-policy: ed25519 notify: [inwx_notify] acl: [inwx_acl] + - id: acme_zone + storage: /var/lib/knot + zonefile-sync: -1 + zonefile-load: difference-no-serial + serial-policy: dateserial + journal-content: all + semantic-checks: on + dnssec-signing: on + dnssec-policy: ed25519 zone: - domain: yggdrasil.li @@ -104,9 +132,21 @@ - domain: rheperire.org template: inwx_zone file: ${./zones/org.rheperire.soa} + - domain: _acme-challenge.rheperire.org + template: acme_zone + acl: [ rheperire.org_acme_acl ] + file: ${acmeChallengeZonefile "rheperire.org"} ''; }; + sops.secrets = { + "rheperire.org_acme_key.yaml" = { + format = "yaml"; + owner = "knot"; + sopsFile = ./keys/rheperire.org_acme.yaml; + }; + }; + fileSystems."/var/lib/unbound" = { device = "surtr/local/var-lib-unbound"; diff --git a/hosts/surtr/dns/keys/.sops.yaml b/hosts/surtr/dns/keys/.sops.yaml new file mode 100644 index 00000000..4f536273 --- /dev/null +++ b/hosts/surtr/dns/keys/.sops.yaml @@ -0,0 +1,3 @@ +creation_rules: + - path_regex: "\\.yaml$" + unencrypted_regex: "^(id|algorithm)$" diff --git a/hosts/surtr/dns/keys/rheperire.org_acme.yaml b/hosts/surtr/dns/keys/rheperire.org_acme.yaml new file mode 100644 index 00000000..16f6d19e --- /dev/null +++ b/hosts/surtr/dns/keys/rheperire.org_acme.yaml @@ -0,0 +1,38 @@ +#ENC[AES256_GCM,data:exIBsQRSUnOhewl0P3WCqktpjsdFFIJ610rodabSsbKK/XF/0WwRU2ErAyv3wlmtXUJMY3jSugkzbRmnND9GIrj6n8M20BVoOeXzUA==,iv:SJBizi+kSa80964nQ78+43sapNDTGifSiV1kOheuujk=,tag:j4eowYRr4cmwUzXGwm3CAA==,type:comment] +key: + - id: rheperire.org_acme + algorithm: hmac-sha256 + secret: ENC[AES256_GCM,data:rgw4nQczDhEeI5JMl1fJA3HX5ZVBpjTQEEk2pkA3c9M1CWYpFvzFRtCAxe8=,iv:Y0G3+A161Lefpwknm+S2jj8rTfm/jlrP+pnR3vR6/mk=,tag:IHsCnkIU2p3hCmRokecbtw==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-02-22T09:17:59Z" + mac: ENC[AES256_GCM,data:tYWQT6iDQGsYm4zCtNbqvZhIYIMm3+Q9faRbqVpeERdq3oJlEvKIL3MAP2fj6789EbCKf/6zdah5HzYK9k4RsZWxtPfqxYXZp7gWvWwKwm5MRZfQtYzR7ThhD/8QANJKLVffl+PknJqhUYsUq9aeYTbLnyuR2AHY1WkR/fPwcLg=,iv:wGmozslNHE1dc4tpmNVGQJw2hhojB4L3gf7qu963ItA=,tag:4WrPw6biusQDV1OTWmXv6Q==,type:str] + pgp: + - created_at: "2022-02-22T09:17:59Z" + enc: | + -----BEGIN PGP MESSAGE----- + + hF4DyFKFNkTVG5oSAQdAcxKwhh0Poivpl/A7YU53ab+rMWLWKRpeUSwehL6LPEMw + zv+AtmWUPtAL1GpyruFTYoT0P7CJ/PJLYYUDZPH/4oNcaU+5XiBi6sj3svWH5HQE + 0l4BBkvxjYvgPNYSw68AJz/AlzRig4SL7q1VwaYH9w+UWnpwK2CeIZSn11lzDdcj + 8jUZK34aJFFcGWBM2ZKEtQDm3n5B2nRxwb5kLjqwith5zczJ289VNPDmnlVRU4BA + =i8zc + -----END PGP MESSAGE----- + fp: 7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8 + - created_at: "2022-02-22T09:17:59Z" + enc: | + -----BEGIN PGP MESSAGE----- + + hF4DXxoViZlp6dISAQdALxbhftpZmVeTmFU8ujPPR5w0Z8ljkZbI8SHAWmC2QEIw + iTS491iicbH7kzF+l3SZZ1XAFn9p4ZjQyZNeOHXD/q1KXxCWGn3UTRSbXlgzzmKZ + 0l4BSZpnpgmEgLospl5mS6smVEO58Q3XXjVTQVKAjQaxD9Oe1DRCgW4kOq4xKGWS + xF55QHP3bPt5ziF2nwF+Gs28HW4UzAFVcr7r7Bz9CxwHixFx5qjvzAWh+Pp+TdY0 + =hSUL + -----END PGP MESSAGE----- + fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51 + unencrypted_regex: ^(id|algorithm)$ + version: 3.7.1 diff --git a/hosts/surtr/tls.nix b/hosts/surtr/tls.nix deleted file mode 100644 index 2ff26e35..00000000 --- a/hosts/surtr/tls.nix +++ /dev/null @@ -1,120 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.security.acme; - knotCfg = config.services.knot; - - knotDNSCredentials = domain: let - zone = if cfg.domains.${domain}.zone == null then domain else cfg.domains.${domain}.zone; - in pkgs.writeText "lego-credentials" '' - EXEC_PATH=${knotDNSExec zone}/bin/update-dns.sh - EXEC_PROPAGATION_TIMEOUT=300 - EXEC_POLLING_INTERVAL=5 - ''; - knotDNSExec = zone: pkgs.writeScriptBin "update-dns.sh" '' - #!${pkgs.zsh}/bin/zsh -xe - - mode=$1 - fqdn=$2 - challenge=$3 - - owner=''${fqdn%".${zone}."} - - commited= - function abort() { - [[ -n "''${commited}" ]] || ${knotCfg.cliWrappers}/bin/knotc zone-abort "${zone}" - } - - ${knotCfg.cliWrappers}/bin/knotc zone-begin "${zone}" - trap abort EXIT - - case "''${mode}" in - present) - if ${knotCfg.cliWrappers}/bin/knotc zone-get ${zone} "''${owner}" TXT; then - ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT '""' - fi - ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT "''${challenge}" - ;; - cleanup) - ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT "''${challenge}" - ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT '""' - ;; - *) - exit 2 - ;; - esac - - ${knotCfg.cliWrappers}/bin/knotc zone-commit "${zone}" - commited=yes - ''; - - domainOptions = { - options = { - wildcard = mkOption { - type = types.bool; - default = false; - }; - zone = mkOption { - type = types.nullOr types.str; - default = null; - }; - certCfg = mkOption { - type = types.attrs; - default = {}; - }; - }; - }; -in { - options = { - security.acme = { - domains = mkOption { - type = types.attrsOf (types.submodule domainOptions); - default = {}; - }; - }; - }; - - config = { - security.acme.domains = genAttrs ["dirty-haskell.org" "141.li" "xmpp.li" "yggdrasil.li" "praseodym.org" "rheperire.org" "kleen.li" "nights.email"] (domain: { wildcard = true; }); - - fileSystems."/var/lib/acme" = - { device = "surtr/safe/var-lib-acme"; - fsType = "zfs"; - }; - - security.acme = { - acceptTerms = true; - preliminarySelfsigned = true; # DNS challenge is slow - defaults = { - email = "phikeebaogobaegh@141.li"; - keyType = "rsa4096"; # we don't like NIST curves - # extraLegoFlags = ["--preferred-chain" "ISRG Root X1"]; - }; - certs = - let - domainAttrset = domain: { - inherit domain; - extraDomainNames = optional cfg.domains.${domain}.wildcard "*.${domain}"; - dnsProvider = "exec"; - credentialsFile = knotDNSCredentials domain; - dnsResolver = "1.1.1.1:53"; - } // cfg.domains.${domain}.certCfg; - in genAttrs (attrNames cfg.domains) domainAttrset; - }; - - systemd.services = - let - serviceAttrset = domain: { - after = [ "knot.service" ]; - bindsTo = [ "knot.service" ]; - serviceConfig = { - ReadWritePaths = ["/run/knot/knot.sock"]; - SupplementaryGroups = ["knot"]; - RestrictAddressFamilies = ["AF_UNIX"]; - }; - }; - in mapAttrs' (domain: nameValuePair "acme-${domain}") (genAttrs (attrNames config.security.acme.certs) serviceAttrset); - }; -} diff --git a/hosts/surtr/tls/default.nix b/hosts/surtr/tls/default.nix new file mode 100644 index 00000000..a1548feb --- /dev/null +++ b/hosts/surtr/tls/default.nix @@ -0,0 +1,156 @@ +{ config, lib, customUtils, pkgs, ... }: + +with lib; + +let + inherit (customUtils) mapFilterAttrs; + + tsigSecretName = domain: "${domain}_tsig-secret"; + + cfg = config.security.acme; + knotCfg = config.services.knot; + + knotDNSCredentials = domain: let + zone = if cfg.domains.${domain}.zone == null then domain else cfg.domains.${domain}.zone; + in pkgs.writeText "lego-credentials" '' + EXEC_PATH=${knotDNSExec zone}/bin/update-dns.sh + EXEC_PROPAGATION_TIMEOUT=300 + EXEC_POLLING_INTERVAL=5 + ''; + knotDNSExec = zone: pkgs.writeScriptBin "update-dns.sh" '' + #!${pkgs.zsh}/bin/zsh -xe + + mode=$1 + fqdn=$2 + challenge=$3 + + owner=''${fqdn%".${zone}."} + + commited= + function abort() { + [[ -n "''${commited}" ]] || ${knotCfg.cliWrappers}/bin/knotc zone-abort "${zone}" + } + + ${knotCfg.cliWrappers}/bin/knotc zone-begin "${zone}" + trap abort EXIT + + case "''${mode}" in + present) + if ${knotCfg.cliWrappers}/bin/knotc zone-get ${zone} "''${owner}" TXT; then + ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT '""' + fi + ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT "''${challenge}" + ;; + cleanup) + ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT "''${challenge}" + ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT '""' + ;; + *) + exit 2 + ;; + esac + + ${knotCfg.cliWrappers}/bin/knotc zone-commit "${zone}" + commited=yes + ''; + + domainOptions = { + options = { + wildcard = mkOption { + type = types.bool; + default = false; + }; + zone = mkOption { + type = types.nullOr types.str; + default = null; + }; + certCfg = mkOption { + type = types.attrs; + default = {}; + }; + }; + }; +in { + options = { + security.acme = { + domains = mkOption { + type = types.attrsOf (types.submodule domainOptions); + default = {}; + }; + }; + }; + + config = { + security.acme.domains = genAttrs ["dirty-haskell.org" "141.li" "xmpp.li" "yggdrasil.li" "praseodym.org" "rheperire.org" "kleen.li" "nights.email"] (domain: { wildcard = true; }); + + fileSystems."/var/lib/acme" = + { device = "surtr/safe/var-lib-acme"; + fsType = "zfs"; + }; + + security.acme = { + acceptTerms = true; + preliminarySelfsigned = true; # DNS challenge is slow + defaults = { + email = "phikeebaogobaegh@141.li"; + keyType = "rsa4096"; # we don't like NIST curves + # extraLegoFlags = ["--preferred-chain" "ISRG Root X1"]; + }; + certs = + let + domainAttrset = domain: let + tsigPath = ./tsig_keys + "/${domain}"; + tsigSecret = config.sops.secrets.${tsigSecretName domain}; + isTsig = pathExists tsigPath; + shared = { + inherit domain; + extraDomainNames = optional cfg.domains.${domain}.wildcard "*.${domain}"; + dnsResolver = "127.0.0.1:5353"; + }; + mkKnotc = shared // { + dnsProvider = "exec"; + credentialsFile = knotDNSCredentials domain; + }; + mkRFC2136 = let + tsigInfo = readYaml tsigPath; + in shared // { + dnsProvider = "rfc2136"; + credentialsFile = pkgs.writeText "${domain}_credentials.env" '' + RFC2136_NAMESERVER=127.0.0.1:53 + RFC2136_TSIG_ALGORITHM=hmac-sha256. + RFC2136_TSIG_KEY=${domain}_acme + RFC2136_TSIG_SECRET_FILE=${tsigSecret.path} + RFC2136_PROPAGATION_TIMEOUT=300 + RFC2136_POLLING_INTERVAL=5 + RFC2136_TTL=300 + ''; + }; + in // cfg.domains.${domain}.certCfg; + in genAttrs (attrNames cfg.domains) domainAttrset; + }; + + sops.secrets = let + toTSIGSecret = n: v: + if v == "regular" || v == "symlink" + then nameValuePair (tsigSecretName n) { + format = "binary"; + owner = if config.security.acme.useRoot then "root" else "acme"; + group = "acme"; + sopsFile = ./tsig_keys + "/${n}"; + } else null; + in mapFilterAttrs (_: v: v != null) toTSIGSecret (readDir ./tsig_keys); + + systemd.services = + let + serviceAttrset = domain: { + after = [ "knot.service" ]; + bindsTo = [ "knot.service" ]; + serviceConfig = { + ReadWritePaths = ["/run/knot/knot.sock"]; + SupplementaryGroups = ["knot"]; + RestrictAddressFamilies = ["AF_UNIX"]; + }; + }; + in mapAttrs' (domain: nameValuePair "acme-${domain}") (genAttrs (attrNames config.security.acme.certs) serviceAttrset); + }; +} diff --git a/hosts/surtr/tls/tsig_keys/rheperire.org b/hosts/surtr/tls/tsig_keys/rheperire.org new file mode 100644 index 00000000..1f3dc4a4 --- /dev/null +++ b/hosts/surtr/tls/tsig_keys/rheperire.org @@ -0,0 +1,26 @@ +{ + "data": "ENC[AES256_GCM,data:Lb1IWtwPdBxeXGrOr74MlR7lKBMIg2ix3enRMVN4MPEDh7CIFv1nhAfLCDU=,iv:TcyGtcKEL7yUXgHrfJ5pDjl8r5V7ltYQw2wcnsFN1bc=,tag:gBaYj6MkQKvD/uO+Pudnzg==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2022-02-22T09:46:27Z", + "mac": "ENC[AES256_GCM,data:vtymuSymOeh34ZbAumxCEcemtI0UFBoQwj4axpKf7AzVnQOubWYxPI5x23CiOE12Y+FThg1dYEnDWlkkQjMVWQvcJzfP1g25S98MuZ42E9R0nBxGzTrIaKS7kgNAriYDy8ib7Z3DUbajEGfvLLazCdHu3g9gE0dLewKcUTHaXwM=,iv:j8vS2Ej58HLf+CP9fe1rN+4UuniGlv/0M+Zzt3nlk5I=,tag:xn33GGm2knsIdH+EJ1zDcA==,type:str]", + "pgp": [ + { + "created_at": "2022-02-22T09:46:26Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DyFKFNkTVG5oSAQdA1++YsCSiRkVGoVF6uvYeY7JjZjsdjAUwubCF3fLDtS8w\nHFSQltgrqkul1Ei1fUFQqFbSpMSfBQhk0kMH0Xuk5SoDd1lWumqTP9gaJj/CM+yz\n0l4BHiQ4Ktyc01f127Az36nQZGKdJvL9etQAceNtNM7Y4XkLQ78OXFJoERrf81s/\n4wm5za7xqEBE1oE0QWAelzyjBqxI1iCjDl3p6heUdhqlcvAifXgqAk0qxq/4T4FG\n=2Bwl\n-----END PGP MESSAGE-----\n", + "fp": "7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8" + }, + { + "created_at": "2022-02-22T09:46:26Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdA4jg570qc5Zu3yAJPmjf11r20eYMyjAeEWw/RreZga3Uw\nmTy2jo/de11m3r/aIxp0Q/vFZ1m4JGeoIzIgQxGnhX0s0NpfCbCih0P7UMjZRatC\n0l4BCzWq7c0A2GbjPq+aKF2IYfQV5gdwjgCAJO3Oylb/SAltwnyGChANw4ckUCjV\n9DWYIcerMP2scxpjf5nJXpHNMjpGR7fcqS71PoVoJoActMqrG0deOMUSUEOVP8nR\n=+ux+\n-----END PGP MESSAGE-----\n", + "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51" + } + ], + "unencrypted_suffix": "_unencrypted", + "version": "3.7.1" + } +} \ No newline at end of file -- cgit v1.2.3