commit 738e987d8e52ff9d2818a5bc4b73e5d62296a4c0
parent 09dda84f5e0ed892a25de80eb8d719df12a1df03
Author: Katja (ctucx) <git@ctu.cx>
Date: Wed, 21 May 2025 13:57:59 +0200
parent 09dda84f5e0ed892a25de80eb8d719df12a1df03
Author: Katja (ctucx) <git@ctu.cx>
Date: Wed, 21 May 2025 13:57:59 +0200
config/nixos/modules/services: add `knotACME`
1 file changed, 126 insertions(+), 0 deletions(-)
A
|
126
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/config/nixos/modules/services/knotACME.nix b/config/nixos/modules/services/knotACME.nix @@ -0,0 +1,126 @@ +{ + inputs, + povSelf, + pkgs, + lib, + config, + dnsNix, + ... +}: +let + inherit (lib) types; + cfg = lib.getAttrFromPath povSelf config; + +in +{ + + options = { + enable = { + type = types.bool; + default = false; + }; + nameServers = { + type = types.listOf types.str; + }; + zone = { + type = types.str; + }; + zones = { + type = types.listOf types.str; + }; + keyFile = { + type = types.str; + }; + }; + + config = lib.mkIf cfg.enable (let + generateACMERecord = recordName: ( + (builtins.hashString "sha1" recordName) + ".${cfg.zone}." + ); + + nodesWithACMERecords = ( + inputs.self.nixosConfigurations + |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.security.acme.certs != {}) + ); + + getAllDomainsPerNode = hostName: ( + inputs.self.nixosConfigurations.${hostName}.config.security.acme.certs + |> lib.mapAttrsToList (domain: cfg: [ domain ] ++ cfg.extraDomainNames) + |> lib.flatten + ); + + getACMERecordsPerNode = hostName: ( + hostName + |> getAllDomainsPerNode + |> builtins.map (recordName: (generateACMERecord recordName)) + ); + + generateACMERecordsPerZone = zoneName: ( + nodesWithACMERecords + |> lib.mapAttrsToList (hostName: _: (getAllDomainsPerNode hostName)) + |> lib.flatten + |> builtins.filter (lib.hasSuffix zoneName) + |> builtins.map (recordName: { + name = "_acme-challenge${if zoneName != recordName then "." else ""}${lib.removeSuffix "${if zoneName != recordName then "." else ""}${zoneName}" recordName}"; + value = { + CNAME = [ (generateACMERecord recordName) ]; + }; + }) + |> builtins.listToAttrs + ); + + in { + dns.allZones = ( + cfg.zones + |> lib.map (element: + lib.nameValuePair element { + subdomains = generateACMERecordsPerZone element; + } + ) + |> lib.listToAttrs + ); + + modules.services.knot = { + keyFiles = [ cfg.keyFile ]; + zones = { + "${cfg.zone}" = { + file = toString (pkgs.writeTextFile { + name = "${cfg.zone}.zone"; + text = dnsNix.types.zoneToString cfg.zone (dnsNix.evalZone cfg.zone (with dnsNix.combinators; { + NS = cfg.nameServers; + SOA = { + nameServer = lib.elemAt cfg.nameServers 0; + adminEmail = "dns@${cfg.zone}"; # Email address with a real `@`! + serial = 0; + }; + })); + }); + + zonefile-sync = -1; + zonefile-load = "difference"; + + journal-content = "changes"; + + acl = lib.mkIf ((lib.attrNames nodesWithACMERecords) != []) ( + nodesWithACMERecords + |> lib.mapAttrsToList (hostName: _: "acme-nix-${hostName}") + ); + }; + }; + extraACL = ( + nodesWithACMERecords + |> lib.mapAttrs' (hostName: _: { + name = "acme-nix-${hostName}"; + value = { + key = [ "acme-nix-${hostName}" ]; + action = "update"; + update-owner = "name"; + update-owner-match = "equal"; + update-owner-name = getACMERecordsPerNode hostName; + }; + }) + ); + }; + }); + +}