More changes than expected...

This commit is contained in:
niten 2022-01-05 17:57:32 -08:00
parent 00e8e764ac
commit 66edcf4fd3
5 changed files with 295 additions and 180 deletions

View File

@ -6,7 +6,7 @@ let
join-lines = concatStringsSep "\n";
domainOpts = { domain, ... }: {
domainOpts = { name, ... }: {
options = with types; {
dnssec = mkOption {
type = bool;
@ -14,77 +14,53 @@ let
default = true;
};
dmarc-report-address = mkOption {
type = nullOr str;
description = "The email to use to recieve DMARC reports, if any.";
example = "admin-user@domain.com";
default = null;
};
zone-definition = mkOption {
type = submodule (import ../types/zone-definition.nix);
description = "Definition of network zone to be served by local server.";
};
default-host = mkOption {
type = str;
description = "The host to which the domain should map by default.";
};
mx = mkOption {
type = listOf str;
description = "The hosts which act as the domain mail exchange.";
default = [];
};
gssapi-realm = mkOption {
type = nullOr str;
description = "The GSSAPI realm of this domain.";
default = null;
};
};
};
networkHostOpts = import ../types/network-host.nix { inherit lib; };
# networkHostOpts = import ../types/network-host.nix { inherit lib; };
hostRecords = hostname: nethost-data: let
# FIXME: RP doesn't work.
# generic-host-records = let
# host-data = if (hasAttr hostname config.fudo.hosts) then config.fudo.hosts.${hostname} else null;
# in
# if (host-data == null) then [] else (
# (map (sshfp: "${hostname} IN SSHFP ${sshfp}") host-data.ssh-fingerprints) ++ (optional (host-data.rp != null) "${hostname} IN RP ${host-data.rp}")
# );
sshfp-records = if (hasAttr hostname config.fudo.hosts) then
(map (sshfp: "${hostname} IN SSHFP ${sshfp}")
config.fudo.hosts.${hostname}.ssh-fingerprints)
else [];
a-record = optional (nethost-data.ipv4-address != null) "${hostname} IN A ${nethost-data.ipv4-address}";
aaaa-record = optional (nethost-data.ipv6-address != null) "${hostname} IN AAAA ${nethost-data.ipv6-address}";
description-record = optional (nethost-data.description != null) "${hostname} IN TXT \"${nethost-data.description}\"";
in
join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records);
# hostRecords = hostname: nethost-data: let
# # FIXME: RP doesn't work.
# # generic-host-records = let
# # host-data = if (hasAttr hostname config.fudo.hosts) then config.fudo.hosts.${hostname} else null;
# # in
# # if (host-data == null) then [] else (
# # (map (sshfp: "${hostname} IN SSHFP ${sshfp}") host-data.ssh-fingerprints) ++ (optional (host-data.rp != null) "${hostname} IN RP ${host-data.rp}")
# # );
# sshfp-records = if (hasAttr hostname config.fudo.hosts) then
# (map (sshfp: "${hostname} IN SSHFP ${sshfp}")
# config.fudo.hosts.${hostname}.ssh-fingerprints)
# else [];
# a-record = optional (nethost-data.ipv4-address != null) "${hostname} IN A ${nethost-data.ipv4-address}";
# aaaa-record = optional (nethost-data.ipv6-address != null) "${hostname} IN AAAA ${nethost-data.ipv6-address}";
# description-record = optional (nethost-data.description != null) "${hostname} IN TXT \"${nethost-data.description}\"";
# in
# join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records);
makeSrvRecords = protocol: type: records:
join-lines (map (record:
"_${type}._${protocol} IN SRV ${toString record.priority} ${
toString record.weight
} ${toString record.port} ${toString record.host}.") records);
# makeSrvRecords = protocol: type: records:
# join-lines (map (record:
# "_${type}._${protocol} IN SRV ${toString record.priority} ${
# toString record.weight
# } ${toString record.port} ${toString record.host}.") records);
makeSrvProtocolRecords = protocol: types:
join-lines (mapAttrsToList (makeSrvRecords protocol) types);
# makeSrvProtocolRecords = protocol: types:
# join-lines (mapAttrsToList (makeSrvRecords protocol) types);
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
# cnameRecord = alias: host: "${alias} IN CNAME ${host}";
mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs);
# mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs);
dmarcRecord = dmarc-email:
optionalString (dmarc-email != null) ''
_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
# dmarcRecord = dmarc-email:
# optionalString (dmarc-email != null) ''
# _dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
nsRecords = domain: ns-hosts:
join-lines
(mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts);
# nsRecords = domain: ns-hosts:
# join-lines
# (mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts);
in {
@ -92,16 +68,16 @@ in {
enable = mkEnableOption "Enable master DNS services.";
# FIXME: This should allow for AAAA addresses too...
nameservers = mkOption {
type = attrsOf (submodule networkHostOpts);
description = "Map of domain nameserver FQDNs to IP.";
example = {
"ns1.domain.com" = {
ipv4-address = "1.1.1.1";
description = "my fancy dns server";
};
};
};
# nameservers = mkOption {
# type = attrsOf (submodule networkHostOpts);
# description = "Map of domain nameserver FQDNs to IP.";
# example = {
# "ns1.domain.com" = {
# ipv4-address = "1.1.1.1";
# description = "my fancy dns server";
# };
# };
# };
identity = mkOption {
type = str;
@ -143,38 +119,40 @@ in {
in nameValuePair "${dom}." {
dnssec = dom-cfg.dnssec;
data = ''
$ORIGIN ${dom}.
$TTL 12h
data = pkgs.lib.dns.networkToZone dom dom-cfg;
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
${toString config.instance.build-timestamp}
30m
2m
3w
5m)
# data = ''
# $ORIGIN ${dom}.
# $TTL 12h
${optionalString (dom-cfg.default-host != null)
"@ IN A ${dom-cfg.default-host}"}
# @ IN SOA ns1.${dom}. hostmaster.${dom}. (
# ${toString config.instance.build-timestamp}
# 30m
# 2m
# 3w
# 5m)
${mxRecords dom-cfg.mx}
# ${optionalString (dom-cfg.default-host != null)
# "@ IN A ${dom-cfg.default-host}"}
$TTL 6h
# ${mxRecords dom-cfg.mx}
${optionalString (dom-cfg.gssapi-realm != null)
''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
# $TTL 6h
${nsRecords dom cfg.nameservers}
${join-lines (mapAttrsToList hostRecords cfg.nameservers)}
# ${optionalString (dom-cfg.gssapi-realm != null)
# ''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
${dmarcRecord dom-cfg.dmarc-report-address}
# ${nsRecords dom cfg.nameservers}
# ${join-lines (mapAttrsToList hostRecords cfg.nameservers)}
${join-lines
(mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
${join-lines net-cfg.verbatim-dns-records}
'';
# ${dmarcRecord dom-cfg.dmarc-report-address}
# ${join-lines
# (mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
# ${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
# ${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
# ${join-lines net-cfg.verbatim-dns-records}
# '';
}) cfg.domains;
};
};

View File

@ -67,6 +67,8 @@ let
hosts = attrNames cfg.sites;
acme.auto = false;
# By default, listen on all ips
listen = let
common = {
@ -89,7 +91,7 @@ let
cfg.sites;
host_config =
mapAttrs (site: siteOpts: siteOpts.hostname)
mapAttrs (site: siteOpts: siteOpts.site-config)
cfg.sites;
};
@ -208,10 +210,11 @@ in {
fudo = let
host-fqdn = config.instance.host-fqdn;
in {
acme.host-domains.${hostname} = mapAttrs' (_: siteOpts:
acme.host-domains.${hostname} = mapAttrs' (site: siteOpts:
nameValuePair siteOpts.hostname {
extra-domains =
(optional (siteOpts.hostname != host-fqdn) host-fqdn);
(optional (siteOpts.hostname != host-fqdn) host-fqdn) ++
[ "pubsub.${site}" ];
local-copies.ejabberd = {
user = cfg.user;
group = cfg.group;

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }:
{ config, lib, pkgs, ... } @ toplevel:
with lib;
let
@ -13,7 +13,9 @@ let
};
};
stageOpts = { ... }: {
stageOpts = { name, ... }: let
stage-name = name;
in {
options = with types; {
currencies = mkOption {
type = attrsOf (submodule currencyOpts);
@ -39,25 +41,26 @@ let
'';
};
jabber-jid = mkOption {
type = nullOr str;
description = "Jabber JID as which to connect.";
example = "chute-user@my.domain.org";
default = null;
};
jabber = {
jid = mkOption {
type = str;
description = "Jabber JID as which to connect.";
example = "chute-user@my.domain.org";
default = null;
};
jabber-target = mkOption {
type = nullOr str;
description = "User to which logs will be sent.";
example = "target@my.domain.org";
default = null;
};
resource = mkOption {
type = str;
description = "Jabber resource string.";
default = "${toplevel.config.instance.hostname}-${stage-name}";
};
jabber-server = mkOption {
type = nullOr str;
description = "Jabber server.";
example = "my-server.domain.org";
default = null;
target-jid = mkOption {
type = str;
description = "User to which logs will be sent.";
example = "target@my.domain.org";
default = null;
};
};
};
};
@ -65,7 +68,9 @@ let
concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
chute-job-definition = { stage, currency, stageOpts, currencyOpts }: {
chute-job-definition = { stage, currency, stageOpts, currencyOpts }: let
join-args = concatStringsSep " ";
in {
after = [ "network-online.target" ];
wantedBy = [ "chute.target" ];
partOf = [ "chute.target" ];
@ -73,11 +78,15 @@ let
environmentFile = stageOpts.environment-file;
execStart = let
jabber-string =
optionalString (stageOpts.jabber-jid != null &&
stageOpts.jabber-target != null &&
stageOpts.jabber-server != null)
"--jabber-jid=${stageOpts.jabber-jid} --target-jid=${stageOpts.jabber-target} --jabber-server=${stageOpts.jabber-server}";
in "${stageOpts.package}/bin/chute --currency=${currency} --stop-at-percent=${toString currencyOpts.stop-percentile} ${jabber-string}";
optionalString (stageOpts.jabber != null)
(join-args ["--jabber-jid=${stageOpts.jabber.jid}"
"--target-jid=${stageOpts.jabber.target-jid}"
"--jabber-resource=${stageOpts.jabber.resource}-${currency}"]);
in join-args ["${stageOpts.package}/bin/chute"
"--currency=${currency}"
"--stop-at-percent=${toString currencyOpts.stop-percentile}"
jabber-string];
privateNetwork = false;
addressFamilies = [ "AF_INET" ];
memoryDenyWriteExecute = false; # Needed becuz Clojure

View File

@ -45,6 +45,48 @@ let
};
};
hostRecords = hostname: nethost-data: let
sshfp-records = optionals (hasAttr hosttname config.fudo.hosts)
(map (sshfp: "${hostname} IN SSHFP ${sshfp}")
config.fudo.hosts.${hostname}.ssh-fingerprints);
a-record = optional (nethost-data.ipv4-address != null)
"${hostname} IN A ${nethost-data.ipv4-address}";
aaaa-record = optional (nethost-data.ipv6-address != null)
"${hostname} IN AAAA ${nethost-data.ipv6-address}";
description-record = optional (nethost-data.description != null)
''${hostname} IN TXT "${nethost-data.description}"'';
in
join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records);
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
dmarcRecord = dmarc-email:
optionalString (dmarc-email != null)
''_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
mxRecords = mxs: map (mx: "@ IN MX 10 ${mx}.") mxs;
nsRecords = domain: ns-hosts:
mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts;
flatmapAttrsToList = f: attrs:
foldr (a: b: a ++ b) [] (mapAttrsToList f attrs);
nsARecords = _: ns-hosts: let
a-record = host: hostOpts: optional (hostOpts.ipv4-address != null)
"${host} IN A ${hostOpts.ipv4-address}";
aaaa-record = host: hostOpts: optional (hostOpts.ipv6-address != null)
"${host} IN A ${hostOpts.ipv6-address}";
description-record = host: hostOpts: (hostOpts.description != null)
''${host} IN TXT "${hostOpts.description}"'';
in flatmapAttrsToList
(host: hostOpts:
(a-record host hostOpts) ++
(aaaa-record host hostOpts) ++
(description-record host hostOpts))
ns-hosts;
srvRecordPair = domain: protocol: service: record: {
"_${service}._${protocol}.${domain}" =
"${toString record.priority} ${toString record.weight} ${
@ -52,6 +94,36 @@ let
} ${record.host}.";
};
domain-record = dom: domCfg: ''
$ORIGIN ${dom}.
$TTL ${domCfg.default-ttl}
${optionalString (domCfg.default-host != null)
"@ IN A ${domCfg.default-host}"}
${mxRecords domCfg.mx}
${optionalString (domCfg.gssapi-realm != null)
''_kerberos IN TXT "${domCfg.gssapi-realm}"''}
$TTL ${domCfg.host-record-ttl}
${nsRecords dom domCfg.nameservers}
${nsARecords dom domCfg.nameservers}
${dmarcRecord domCfg.dmarc-report-address}
${join-lines (mapAttrsToList makeSrvProtocolRecords domCfg.srv-records)}
${join-lines (mapAttrsToList hostRecords domCfg.hosts)}
${join-lines (mapAttrsToList cnameRecord domCfg.aliases)}
${join-lines domCfg.verbatim-dns-records}
${join-lines (mapAttrsToList
(subdom: subdomCfg: subdomain-record "${subdom}.${dom}" subdomCfg)
domCfg.subdomains)}
'';
in rec {
srvRecords = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
@ -67,4 +139,18 @@ in rec {
concatMapAttrs
(service: records: map (srvRecordPair domain protocol service) records) services)
srvRecords);
networkToZone = dom: domCfg: pkgs.writeText "zone-${dom}" ''
$ORIGIN ${dom}
$TTL ${domCfg.default-ttl}
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
${toString config.instance.build-timestamp}
30m
2m
3w
5m)
${domain-record dom domCfg}
'';
}

View File

@ -33,76 +33,115 @@ let
networkHostOpts = import ./network-host.nix { inherit lib; };
in {
options = with types; {
hosts = mkOption {
type = attrsOf (submodule networkHostOpts);
description = "Hosts on the local network, with relevant settings.";
example = {
my-host = {
ipv4-address = "192.168.0.1";
mac-address = "aa:aa:aa:aa:aa";
};
};
default = { };
};
srv-records = mkOption {
type = attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
description = "SRV records for the network.";
example = {
tcp = {
kerberos = {
port = 88;
host = "krb-host.my-domain.com";
zoneOpts = { ... }: with types; {
options = {
hosts = mkOption {
type = attrsOf (submodule networkHostOpts);
description = "Hosts on the local network, with relevant settings.";
example = {
my-host = {
ipv4-address = "192.168.0.1";
mac-address = "aa:aa:aa:aa:aa";
};
};
default = { };
};
default = { };
};
aliases = mkOption {
type = attrsOf str;
default = { };
description =
"A mapping of host-alias -> hostnames to add to the domain record.";
example = {
mail = "my-mail-host";
music = "musicall-host.other-domain.com.";
nameservers = mkOption {
type = attrsOf (submodule networkHostOpts);
description = "Map of domain nameservers to host data.";
example = {
"ns1" = {
ipv4-address = "1.1.1.1";
ipv6-address = "1::1";
};
};
default = {};
};
};
verbatim-dns-records = mkOption {
type = listOf str;
description = "Records to be inserted verbatim into the DNS zone.";
example = [ "some-host IN CNAME base-host" ];
default = [ ];
};
srv-records = mkOption {
type = attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
description = "SRV records for the network.";
example = {
tcp = {
kerberos = {
port = 88;
host = "krb-host.my-domain.com";
};
};
};
default = { };
};
dmarc-report-address = mkOption {
type = nullOr str;
description = "The email to use to recieve DMARC reports, if any.";
example = "admin-user@domain.com";
default = null;
};
aliases = mkOption {
type = attrsOf str;
default = { };
description =
"A mapping of host-alias -> hostnames to add to the domain record.";
example = {
mail = "my-mail-host";
music = "musicall-host.other-domain.com.";
};
};
default-host = mkOption {
type = nullOr str;
description =
"IP of the host which will act as the default server for this domain, if any.";
default = null;
};
verbatim-dns-records = mkOption {
type = listOf str;
description = "Records to be inserted verbatim into the DNS zone.";
example = [ "some-host IN CNAME base-host" ];
default = [ ];
};
mx = mkOption {
type = listOf str;
description = "A list of mail servers serving this domain.";
default = [ ];
};
dmarc-report-address = mkOption {
type = nullOr str;
description = "The email to use to recieve DMARC reports, if any.";
example = "admin-user@domain.com";
default = null;
};
gssapi-realm = mkOption {
type = nullOr str;
description = "Kerberos GSSAPI realm of the zone.";
default = null;
default-host = mkOption {
type = nullOr str;
description =
"IP of the host which will act as the default server for this domain, if any.";
default = null;
};
mx = mkOption {
type = listOf str;
description = "A list of mail servers serving this domain.";
default = [ ];
};
gssapi-realm = mkOption {
type = nullOr str;
description = "Kerberos GSSAPI realm of the zone.";
default = null;
};
default-ttl = mkOption {
type = str;
description = "Default time-to-live for this zone.";
default = "3h";
};
host-record-ttl = mkOption {
type = str;
description = "Default time-to-live for records in this zone";
default = "1h";
};
description = mkOption {
type = str;
description = "Description of this zone.";
};
subdomains = mkOption {
type = attrsOf (submodule zoneOpts);
description = "Subdomains of the current zone.";
default = {};
};
};
};
in {
options = zoneOpts;
}