{ config, lib, pkgs, options, ... }: with lib; with types; let cfg = config.qois.meta.network; mkStr = description: (mkOption { type = str; inherit description; }); mkOptStr = description: (mkOption { type = nullOr str; default = null; inherit description; }); mkNetworkIdOpts = v: assert v == 4 || v == 6; submodule { options = { id = mkOption { type = types.str; description = '' IPv${toString v} ID ''; }; prefixLength = mkOption { type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128)); description = '' Subnet mask of the ip, specified as the number of bits in the prefix (${if v == 4 then "24" else "64"}). ''; }; gateway = mkOption { default = null; type = nullOr str; description = '' Upstream Gateway IP ''; }; nameservers = mkOption { default = null; type = nullOr (listOf str); description = "Nameserver IP"; }; }; }; mkFqdn = host: domain: mkOption { type = str; default = "${config.qois.meta.hosts.${host}.hostName}.${domain}"; description = '' The fully qualified domain name (FYDN) of this host inside of this specific network. Defaults to the host attribute key and net domain. ''; }; in { options.qois.meta.network.physical = mkOption { description = "Physical network configuration"; type = attrsOf ( submodule ( { name, ... }: let networkName = name; in { options = { v4 = mkOption { type = (mkNetworkIdOpts 4); }; v6 = mkOption { type = nullOr (mkNetworkIdOpts 6); }; domain = mkStr "Network DNS Domain suffix"; hosts = mkOption { type = attrsOf ( submodule ( { name, ... }: let host = name; in { options = { v4 = mkOption { type = submodule { options.ip = mkStr "The V4 host IP address"; }; }; v6 = mkOption { default = null; type = nullOr (submodule { options.ip = mkStr "The V6 host IP address"; }); }; fqdn = mkFqdn host cfg.physical.${networkName}.domain; }; } ) ); }; }; } ) ); default = { }; }; options.qois.meta.network.virtual = mkOption { description = "Virtual network configuration"; type = types.attrsOf ( types.submodule ( { name, ... }: let networkName = name; in { options = { v4 = mkOption { type = (mkNetworkIdOpts 4); }; v6 = mkOption { default = null; type = nullOr (mkNetworkIdOpts 6); }; domain = mkStr "Network DNS Domain suffix"; hosts = mkOption { type = attrsOf ( submodule ( { name, ... }: let host = name; in { options = { v4 = mkOption { type = submodule { options.ip = mkStr "The V4 host IP address"; }; }; v6 = mkOption { default = null; type = nullOr (submodule { options.ip = mkStr "The V6 host IP address"; }); }; # Taken from https://github.com/NixOS/nixpkgs/blob/nixos-21.11/nixos/modules/services/networking/wireguard.nix: publicKey = mkOption { example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; type = str; description = "The base64 public key of the peer."; }; persistentKeepalive = mkOption { default = null; type = nullOr int; example = 25; description = '' This is optional and is by default off, because most users will not need it. It represents, in seconds, between 1 and 65535 inclusive, how often to send an authenticated empty packet to the peer, for the purpose of keeping a stateful firewall or NAT mapping valid persistently. For example, if the interface very rarely sends traffic, but it might at anytime receive traffic from a peer, and it is behind NAT, the interface might benefit from having a persistent keepalive interval of 25 seconds; however, most users will not need this.''; }; # Endpoint Configuration: endpoint = mkOption { description = '' FQDN and port of this vpn-endpoint. This option indicates this host is a VPN server. ''; default = null; type = nullOr (submodule { options = { fqdn = mkFqdn host cfg.virtual.${networkName}.domain; port = mkOption { type = types.addCheck types.int (n: n > 0 && n < 65536); description = '' The port on which the wireguard endpoint receives packages. ''; }; }; }); }; }; } ) ); }; }; } ) ); default = { }; }; config = { programs.ssh.knownHosts = let # hostname -> single network cfg attr -> ["known host's names"] getHostNamesFromNetwork = hostname: network: if network.hosts ? ${hostname} && network.hosts.${hostname} != null then let hostCfg = network.hosts.${hostname}; in [ "${hostname}.${network.domain}" hostCfg.v4.ip ] ++ (if hostCfg.v6 != null then [ hostCfg.v6.ip ] else [ ]) else [ ]; # hostname -> attr of network defs -> ["known host's names"] getHostNamesForNetworks = hostname: networks: lib.flatten (map (getHostNamesFromNetwork hostname) (lib.attrValues networks)); # hostname -> ["known host's names"] getHostNames = hostname: (getHostNamesForNetworks hostname cfg.virtual) ++ (getHostNamesForNetworks hostname cfg.physical); hostsWithPublicKey = lib.filterAttrs ( hostName: hostConfig: hostConfig.sshKey != null ) config.qois.meta.hosts; in mapAttrs (name: hostCfg: { extraHostNames = getHostNames name; }) hostsWithPublicKey; }; }