Move profiles from ./profile-config to ./profile

This commit is contained in:
2021-11-02 09:34:26 -07:00
parent 79b05be7d3
commit 566643e195
48 changed files with 1490 additions and 997 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{ lib, ... }:
{ pkgs, ... }:
with lib;
with pkgs.lib;
let
join-lines = concatStringsSep "\n";
+9 -7
View File
@@ -1,16 +1,18 @@
{ lib, ... }:
let
ip = import ./ip.nix { inherit lib; };
dns = import ./dns.nix { inherit lib; };
passwd = import ./passwd.nix { inherit lib; };
in
# NOTE: OBSOLETE! See overlay.nix
{
lib.overlays = [
(final: prev:
prev.lib // {
fudo = {
inherit ip dns passwd;
fudo = let
lib = prev.lib;
in {
ip = import ./ip.nix { inherit lib; };
dns = import ./dns.nix { inherit lib; };
passwd = import ./passwd.nix { inherit lib; };
lisp = import ./lisp.nix { inherit lib; };
};
})
];
+62 -23
View File
@@ -30,7 +30,9 @@ let
# };
# };
domainOpts = { domain, ... }: {
domainOpts = { name, ... }: let
domain = name;
in {
options = with types; {
email = mkOption {
type = str;
@@ -45,8 +47,12 @@ let
};
local-copies = let
localCopyOpts = { copy, ... }: {
options = with types; {
localCopyOpts = { name, ... }: let
copy = name;
in {
options = with types; let
target-path = "/var/run/${domain}/${copy}";
in {
user = mkOption {
type = str;
description = "User to which this copy belongs.";
@@ -58,17 +64,35 @@ let
default = null;
};
path = mkOption {
type = str;
description = "Path at which to store the local copy.";
default = "/var/run/${domain}/${copy}";
};
service = mkOption {
type = str;
description = "systemd job to copy certs.";
default = "fudo-${domain}-${copy}-certs.service";
};
certificate = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/cert.pem";
};
full-certificate = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/fullchain.pem";
};
chain = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/chain.pem";
};
private-key = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/key.pem";
};
};
};
in mkOption {
@@ -92,7 +116,7 @@ let
cfg.host-domains.${hostname} else {};
optionalStringOr = str: default:
if cond then str else default;
if (str != null) then str else default;
in {
options.fudo.acme = with types; {
@@ -113,26 +137,41 @@ in {
tmpfiles.rules = let
copies = concatMapAttrs (domain: domainOpts:
domainOpts.local-copies) localDomains;
perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500";
copy-paths = mapAttrsToList (copy: copyOpts:
"D '${path}' 0550 ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -")
copies;
in copy-paths;
let
dir-entry = copyOpts: file: "D '${dirOf file}' ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -";
in map (dir-entry copyOpts) [
copyOpts.certificate
copyOpts.full-certificate
copyOpts.chain
copyOpts.private-key
]) copies;
in unique copy-paths;
# TODO: Make this a Fudo service?
services = concatMapAttrs (domain: domainOpts:
mapAttrs' (copy: copyOpts: let
key-perms = copyOpts: if (copyOpts.group != null) then "0440" else "0400";
source = config.security.acme.certs.${domain}.directory;
target = copyOpts.path;
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${site}-certs.sh" ''
for cert in cert chain fullchain full key; do
cp ${source}/$cert.pem ${target}/$cert.pem
chmod 0440 ${source}/$cert.pem
done
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
cp cert.pem ${copyOpts.certificate}
chmod 0444 ${copyOpts.certificate}
cp full.pem ${copyOpts.full-certificate}
chmod 0444 ${copyOpts.full-certificate}
cp chain.pem ${copyOpts.chain}
chmod 0444 ${copyOpts.chain}
cp key.pem ${copyOpts.private-key}
chmod ${key-perms copyOpts} ${copyOpts.private-key}
'';
remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${site}-certs.sh" ''
for cert in cert chain fullchain full key; do
rm -rf ${target}/$cert.pem
done
remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" ''
rm -f ${copyOpts.private-key}
rm -f ${copyOpts.chainy}
rm -f ${copyOpts.full-certificate}
rm -f ${copyOpts.certificate}
'';
in nameValuePair
(rm-service-ext copyOpts.service) {
+2 -1
View File
@@ -1,5 +1,6 @@
{ ... }:
{ config, pkgs, lib, ... }:
with lib;
{
imports = [
./dns.nix
+117 -193
View File
@@ -1,63 +1,10 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.fudo.backplane.dns;
lisp-pkgs = with pkgs.localLispPackages; [
arrows
backplane-dns
backplane-server
cl-sasl
cl-xmpp
ip-utils
alexandria
babel
bordeaux-threads
cffi
cl-base64
cl-json
cl-postgres
cl-ppcre
cl-unicode
cl_plus_ssl
closer-mop
closure-common
cxml
flexi-streams
global-vars
introspect-environment
ironclad
iterate
lisp-namespace
md5
nibbles
postmodern
puri
s-sql
split-sequence
trivia
trivia_dot_balland2006
trivia_dot_level0
trivia_dot_level1
trivia_dot_level2
trivia_dot_trivial
trivial-cltl2
trivial-features
trivial-garbage
trivial-gray-streams
type-i
uax-15
usocket
];
backup-directory = "/var/lib/fudo/backplane/dns";
powerdns-home = "/var/lib/powerdns";
powerdns-conf-dir = "${powerdns-home}/conf.d";
powerdns-conf-dir = "${cfg.powerdns-home}/conf.d";
backplaneOpts = { ... }: {
options = {
@@ -117,53 +64,59 @@ let
};
in {
options.fudo.backplane.dns = {
options.fudo.backplane.dns = with types; {
enable = mkEnableOption "Enable backplane dynamic DNS server.";
port = mkOption {
type = types.port;
type = port;
description = "Port on which to serve authoritative DNS requests.";
default = 53;
};
listen-v4-addresses = mkOption {
type = with types; listOf str;
type = listOf str;
description = "IPv4 addresses on which to listen for dns requests.";
default = [ "0.0.0.0" ];
};
listen-v6-addresses = mkOption {
type = with types; listOf str;
type = listOf str;
description = "IPv6 addresses on which to listen for dns requests.";
example = [ "[abcd::1]" ];
default = [ ];
};
required-services = mkOption {
type = with types; listOf str;
type = listOf str;
description =
"A list of services required before the DNS server can start.";
};
user = mkOption {
type = types.str;
type = str;
description = "User as which to run DNS backplane listener service.";
default = "backplane-dns";
};
group = mkOption {
type = types.str;
type = str;
description = "Group as which to run DNS backplane listener service.";
default = "backplane-dns";
};
database = mkOption {
type = with types; submodule databaseOpts;
type = submodule databaseOpts;
description = "Database settings for the DNS server.";
};
powerdns-home = mkOption {
type = str;
description = "Directory at which to store powerdns configuration and state.";
default = "/run/backplane-dns/powerdns";
};
backplane = mkOption {
type = with types; submodule backplaneOpts;
type = submodule backplaneOpts;
description = "Backplane Jabber settings for the DNS server.";
};
};
@@ -177,7 +130,11 @@ in {
createHome = true;
home = "/var/home/${cfg.user}";
};
backplane-powerdns = { isSystemUser = true; };
backplane-powerdns = {
isSystemUser = true;
home = cfg.powerdns-home;
createHome = true;
};
};
groups = {
@@ -186,140 +143,107 @@ in {
};
};
fudo.system.services = {
backplane-powerdns-config-generator = {
description =
"Generate postgres configuration for backplane DNS server.";
requires = cfg.required-services;
type = "oneshot";
restartIfChanged = true;
partOf = [ "backplane-dns.target" ];
readWritePaths = [ powerdns-conf-dir ];
preStart = ''
mkdir -p ${powerdns-conf-dir}
chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir}
'';
# This builds the config in a bash script, to avoid storing the password
# in the nix store at any point
script = ''
if [ ! -d ${powerdns-conf-dir} ]; then
mkdir ${powerdns-conf-dir}
fi
TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t pdns-XXXXXXXXXX)
TMPCONF=$TMPDIR/pdns.local.gpgsql.conf
if [ ! -f ${cfg.database.password-file} ]; then
echo "${cfg.database.password-file} does not exist!"
exit 1
fi
touch $TMPCONF
chown backplane-powerdns:backplane-powerdns $TMPCONF
chmod go-rwx $TMPCONF
PASSWORD=$(cat ${cfg.database.password-file})
echo "launch+=gpgsql" >> $TMPCONF
echo "gpgsql-host=${cfg.database.host}" >> $TMPCONF
echo "gpgsql-dbname=${cfg.database.database}" >> $TMPCONF
echo "gpgsql-user=${cfg.database.username}" >> $TMPCONF
echo "gpgsql-password=$PASSWORD" >> $TMPCONF
echo "gpgsql-dnssec=yes" >> $TMPCONF
mv $TMPCONF ${powerdns-conf-dir}/pdns.local.gpgsql.conf
rm -rf $TMPDIR
exit 0
'';
};
backplane-powerdns = let
pdns-config-dir = pkgs.writeTextDir "pdns.conf" ''
local-address=${lib.concatStringsSep ", " cfg.listen-v4-addresses}
local-ipv6=${lib.concatStringsSep ", " cfg.listen-v6-addresses}
local-port=${toString cfg.port}
launch=
include-dir=${powerdns-conf-dir}/
'';
in {
description = "Backplane PowerDNS name server";
requires = [
"postgresql.service"
"backplane-powerdns-config-generator.service"
];
after = [ "network.target" ];
path = with pkgs; [ powerdns postgresql ];
execStart = "pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${cfg.powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${pdns-config-dir}";
};
backplane-dns = {
description = "Fudo DNS Backplane Server";
restartIfChanged = true;
path = with pkgs; [ backplane-dns-server ];
execStart = "launch-backplane-dns.sh";
pidFile = "/run/backplane-dns.$USERNAME.pid";
user = cfg.user;
group = cfg.group;
partOf = [ "backplane-dns.target" ];
requires = [ "postgresql.service" ];
environment = {
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host;
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role;
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file;
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host;
FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database;
FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
cfg.backplane.database.username;
FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
cfg.backplane.database.password-file;
CL_SOURCE_REGISTRY =
pkgs.lib.fudo.lisp.lisp-source-registry pkgs.backplane-dns-server;
};
};
};
systemd = {
targets = {
backplane-dns = {
description = "Fudo DNS backplane services.";
wantedBy = [ "multi-user.target" ];
};
};
services = {
backplane-powerdns = let
configDir = pkgs.writeTextDir "pdns.conf" ''
local-address=${lib.concatStringsSep ", " cfg.listen-v4-addresses}
local-ipv6=${lib.concatStringsSep ", " cfg.listen-v6-addresses}
local-port=${toString cfg.port}
launch=
include-dir=${powerdns-conf-dir}/
'';
psql-user = config.services.postgresql.superUser;
in {
unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)";
description = "Backplane PowerDNS name server";
requires = [
"postgresql.service"
"backplane-dns-config-generator.service"
"backplane-dns.target"
];
after = [ "network.target" "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ postgresql ];
serviceConfig = {
Restart = "on-failure";
RestartSec = "10";
StartLimitInterval = "0";
PrivateDevices = true;
# CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT";
# NoNewPrivileges=true;
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${powerdns-home}";
ExecStart =
"${pkgs.powerdns}/bin/pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}";
ProtectSystem = "full";
# ProtectHome=true;
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
};
};
backplane-dns-config-generator = {
description =
"Generate postgres configuration for backplane DNS server.";
requiredBy = [ "backplane-powerdns.service" ];
requires = cfg.required-services;
serviceConfig.Type = "oneshot";
restartIfChanged = true;
partOf = [ "backplane-dns.target" ];
preStart = ''
mkdir -p ${powerdns-conf-dir}
chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir}
'';
# This builds the config in a bash script, to avoid storing the password
# in the nix store at any point
script = ''
if [ ! -d ${powerdns-conf-dir} ]; then
mkdir ${powerdns-conf-dir}
fi
TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t pdns-XXXXXXXXXX)
TMPCONF=$TMPDIR/pdns.local.gpgsql.conf
if [ ! -f ${cfg.database.password-file} ]; then
echo "${cfg.database.password-file} does not exist!"
exit 1
fi
touch $TMPCONF
chown backplane-powerdns:backplane-powerdns $TMPCONF
chmod go-rwx $TMPCONF
PASSWORD=$(cat ${cfg.database.password-file})
echo "launch+=gpgsql" >> $TMPCONF
echo "gpgsql-host=${cfg.database.host}" >> $TMPCONF
echo "gpgsql-dbname=${cfg.database.database}" >> $TMPCONF
echo "gpgsql-user=${cfg.database.username}" >> $TMPCONF
echo "gpgsql-password=$PASSWORD" >> $TMPCONF
echo "gpgsql-dnssec=yes" >> $TMPCONF
mv $TMPCONF ${powerdns-conf-dir}/pdns.local.gpgsql.conf
rm -rf $TMPDIR
exit 0
'';
};
backplane-dns = {
description = "Fudo DNS Backplane Server";
restartIfChanged = true;
serviceConfig = {
ExecStart =
"${pkgs.backplane-dns-server}/bin/launch-backplane-dns.sh";
Restart = "on-failure";
PIDFile = "/run/backplane-dns.$USERNAME.pid";
User = cfg.user;
Group = cfg.group;
StandardOutput = "journal";
};
environment = {
# LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib";
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host;
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role;
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file;
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host;
FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database;
FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
cfg.backplane.database.username;
FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
cfg.backplane.database.password-file;
# CL_SOURCE_REGISTRY = "${pkgs.localLispPackages.backplane-dns}//";
CL_SOURCE_REGISTRY =
lib.concatStringsSep ":" (map (pkg: "${pkg}//") lisp-pkgs);
};
requires = cfg.required-services;
partOf = [ "backplane-dns.target" ];
wantedBy = [ "multi-user.target" ];
requries = cfg.required-services ++ [ "postgresql.service" ];
};
};
};
+1 -1
View File
@@ -108,7 +108,7 @@ in {
enable = true;
appName = cfg.site-name;
database = {
createDatabase = true;
createDatabase = false;
host = cfg.database.hostname;
name = cfg.database.name;
user = cfg.database.user;
+2 -1
View File
@@ -34,8 +34,9 @@ in {
systemd = {
# Ensure the mountpoints exist
tmpfiles.rules = let
mpPerms = mpOpts: if mpOpts.world-readable then "755" else "750";
mountpointToPath = mp: mpOpts:
"d '${mp}' 750 root ${optionalOrDefault mpOpts.group "-"} - -";
"d '${mp}' ${mpPerms mpOpts} root ${optionalOrDefault mpOpts.group "-"} - -";
filesystemsToMountpointLists = mapAttrsToList
(fs: fsOpts: fsOpts.mountpoints);
mountpointListsToPaths = concatMap
+11 -9
View File
@@ -6,6 +6,10 @@ let
host = import ../types/host.nix { inherit lib; };
hostname = config.instance.hostname;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
in {
options.fudo.hosts = with types;
mkOption {
@@ -73,6 +77,8 @@ in {
config.environment.systemPackages;
sorted-unique = sort lessThan (unique packages);
in concatStringsSep "\n" sorted-unique;
build-timestamp.text = toString config.instance.build-timestamp;
};
systemPackages = with pkgs;
@@ -103,10 +109,6 @@ in {
(keypair: keypair.private-key)
(try-attr hostname files.build-keypairs);
backplane-passwd-source = try-attr hostname files.backplane-passwords;
backplane-passwd-target = "/var/run/backplane/passwd";
in {
secrets.host-secrets.${hostname} = {
host-keytab = mkIf (keytab-file != null) {
@@ -121,15 +123,15 @@ in {
user = "root";
};
backplane-passwd = mkIf (backplane-passwd-source != null) {
source-file = backplane-passwd-source;
target-file = backplane-passwd-target;
backplane-passwd = {
source-file = host-cfg.backplane-password-file;
target-file = "/run/backplane/client/passwd";
user = config.fudo.client.dns.user;
};
};
client.dns.password-file = mkIf (backplane-passwd-source != null)
backplane-passwd-target;
client.dns.password-file =
host-secrets.backplane-passwd.target-file;
};
programs.adb.enable = host-cfg.android-dev;
+67 -23
View File
@@ -23,16 +23,16 @@ let
foldr (a: b: a // b) {} (mapAttrs f attrs);
concatMapAttrsToList = f: attr:
attrValues (concatMapAttrs f attr);
concatMap (i: i) (attrValues (mapAttrs f attr));
host-domains = config.fudo.acme.host-domains.${hostname};
siteCerts = site: let
certPath = host-domains.${site}.local-copies.ejabberd.path;
cert-copy = host-domains.${site}.local-copies.ejabberd;
in [
"${certPath}/fullchain.pem"
"${certPath}/privkey.pem"
"${certPath}/chain.pem"
cert-copy.certificate
cert-copy.private-key
cert-copy.chain
];
siteCertService = site:
@@ -60,13 +60,13 @@ let
hosts = attrNames cfg.sites;
listen = [{
listen = map (ip: {
port = cfg.port;
module = "ejabberd_c2s";
ip = cfg.listen-ip;
ip = ip;
starttls = true;
starttls_required = true;
}];
}) cfg.listen-ips;
certfiles = concatMapAttrsToList
(site: siteOpts:
@@ -94,7 +94,6 @@ let
swapper = concatStringsSep " | " secret-swappers;
in pkgs.writeShellScript "ejabberd-generate-config.sh" ''
cat ${template} | ${swapper} > ${target}
chown ${cfg.user}:${cfg.group} ${target}
'';
cfg = config.fudo.jabber;
@@ -102,6 +101,11 @@ let
in {
options.fudo.jabber = with types; {
enable = mkEnableOption "Enable ejabberd server.";
listen-ips = mkOption {
type = listOf str;
description = "IPs on which to listen for Jabber connections.";
};
port = mkOption {
type = port;
@@ -122,7 +126,7 @@ in {
};
admins = mkOption {
type = str;
type = listOf str;
description = "List of admin users for the server.";
default = [];
};
@@ -141,7 +145,23 @@ in {
config-file = mkOption {
type = str;
description = "Location at which to generate the configuration file.";
default = "/var/run/ejabberd/ejabberd.yaml";
default = "/run/ejabberd/ejabberd.yaml";
};
log-level = mkOption {
type = int;
description = ''
Log level at which to run the server.
See: https://docs.ejabberd.im/admin/guide/troubleshooting/
'';
default = 3;
};
environment = mkOption {
type = attrsOf str;
description = "Environment variables to set for the ejabberd daemon.";
default = {};
};
};
@@ -156,26 +176,50 @@ in {
};
};
fudo.acme.host-domains.${hostname} = mapAttrs (site: siteCfg:
mkIf siteCfg.enableACME {
local-copies.ejabberd = {
fudo = {
acme.host-domains.${hostname} = mapAttrs (site: siteCfg:
mkIf siteCfg.enableACME {
local-copies.ejabberd = {
user = cfg.user;
group = cfg.group;
};
}) cfg.sites;
system = let
config-dir = dirOf cfg.config-file;
in {
ensure-directories.${config-dir} = {
user = cfg.user;
group = cfg.group;
perms = "0700";
};
}) cfg.sites;
services.ejabberd-config-generator = let
config-generator =
enter-secrets config-file-template cfg.secret-files cfg.config-file;
in {
script = "${config-generator}";
readWritePaths = [ config-dir ];
workingDirectory = config-dir;
user = cfg.user;
description = "Generate ejabberd config file with necessary passwords.";
postStart = ''
chown ${cfg.user} ${cfg.config-file}
chmod 0400 ${cfg.config-file}
'';
};
};
};
systemd = {
tmpfiles.rules = [
"D '${dirOf cfg.config-file}' 0550 ${cfg.user} ${cfg.group} - -"
];
services.ejabberd = let
config-generator = enter-secrets config-file-template cfg.secret-files cfg.config-file;
in {
wants = map (site: siteCertService site) (attrNames cfg.sites);
environment = cfg.secret-files;
serviceConfig = {
ExecStartPre = mkAfter "${config-generator}";
services = {
ejabberd = {
wants = map (site: siteCertService site) (attrNames cfg.sites);
requires = [ "ejabberd-config-generator.service" ];
environment = cfg.environment;
};
};
};
+123 -71
View File
@@ -205,6 +205,7 @@ in {
ssl-ca-certificate = mkOption {
type = with types; nullOr str;
description = ''
The path to the SSL CA cert used to sign the certificate.
'';
@@ -220,9 +221,8 @@ in {
base = mkOption {
type = str;
description = ''
The base dn of the LDAP server (eg. "dc=fudo,dc=org").
'';
description = "The base dn of the LDAP server.";
example = "dc=fudo,dc=org";
};
rootpw-file = mkOption {
@@ -239,7 +239,6 @@ in {
A list of URIs on which the ldap server should listen.
'';
example = [ "ldap://auth.fudo.org" "ldaps://auth.fudo.org" ];
default = [ ];
};
users = mkOption {
@@ -295,12 +294,11 @@ in {
etc = {
"openldap/sasl2/slapd.conf" = {
mode = "0400";
user = "openldap";
group = "openldap";
# FIXME: take arguments!
user = config.services.openldap.user;
group = config.services.openldap.group;
text = ''
mech_list: gssapi external
keytab: /etc/ldap/ldap.keytab
keytab: ${cfg.kerberos-keytab}
'';
};
};
@@ -343,72 +341,126 @@ in {
rootdn = "cn=admin,${cfg.base}";
rootpwFile = "${cfg.rootpw-file}";
urlList = cfg.listen-uris;
database = "mdb";
extraConfig = ''
settings = let
makeAccessLine = i: attrs: perm-map: let
perm-strings = mapAttrs (dn: perm: "by ${dn} ${perm}") perm-map;
perm-string = concatStringsSep " " perm-strings;
in "${i}to ${attrs} ${perm-string}";
TLSCertificateFile ${cfg.ssl-certificate}
TLSCertificateKeyFile ${cfg.ssl-private-key}
${optionalString (cfg.ssl-ca-certificate != null)
"TLSCACertificateFile ${cfg.ssl-ca-certificate}"}
makeAccess = access-map: let
pairs = mapAttrsToList (target: perm-map: [target perm-map]) access-map;
in imap0 (i: pair: makeAccessLine i pair[0] pair[1]) pairs;
authz-regexp "^uid=auth/([^.]+)\.fudo\.org,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=$1,ou=hosts,dc=fudo,dc=org"
authz-regexp "^uid=[^,/]+/root,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=admin,dc=fudo,dc=org"
authz-regexp "^uid=([^,/]+),cn=fudo\.org,cn=gssapi,cn=auth$" "uid=$1,ou=members,dc=fudo,dc=org"
authz-regexp "^uid=host/([^,/]+),cn=fudo\.org,cn=gssapi,cn=auth$" "cn=$1,ou=hosts,dc=fudo,dc=org"
authz-regexp "^gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth$" "cn=admin,dc=fudo,dc=org"
'';
extraDatabaseConfig = ''
# access to dn=base=""
# by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
# by * read
access to attrs=userPassword,shadowLastChange
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by group.exact="cn=admin,ou=members,${cfg.base}" write
by dn.exact="cn=auth_reader,${cfg.base}" read
by dn.exact="cn=replicator,${cfg.base}" read
by self write
by * auth
access to dn.exact="cn=admin,ou=groups,${cfg.base}"
by dn.exact="cn=admin,${cfg.base}" write
by users read
by * none
access to dn.subtree="ou=groups,${cfg.base}" attrs=memberUid
by dn.regex="cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" write
by group.exact="cn=admin,ou=groups,${cfg.base}" write
by users read
by * none
access to dn.subtree="ou=members,${cfg.base}" attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber
by group.exact="cn=admin,ou=groups,${cfg.base}" write
by dn.exact="cn=user_db_reader,${cfg.base}" read
by users read
by * none
access to dn.exact="cn=admin,ou=groups,${cfg.base}"
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by users read
by * none
access to dn.subtree="ou=groups,${cfg.base}" attrs=memberUid
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by dn.regex="cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" write
by group.exact="cn=admin,ou=groups,${cfg.base}" write
by users read
by * none
access to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by users read
by * none
index objectClass,uid eq
'';
in {
attrs = {
cn = "config";
objectClass = "olcGlobal";
olcPidFile = "/run/slapd/slapd.pid";
olcTLSCertificateFile = cfg.ssl-certificate;
olcTLSCertificateKeyFile = cfg.ssl-private-key;
olcTLSCACertificateFile = cfg.ssl-ca-certificate;
olcSaslSecProps = "noplain,noanonymous";
olcAuthzRegexp = let
authz-regex-entry = i: { regex, target}:
"{${i}}\"${rx}\" \"${target}\"";
in imap0 authz-regex-entry [
{
regex = "^uid=auth/([^.]+).fudo.org,cn=fudo.org,cn=gssapi,cn=auth$";
target = "cn=$1,ou=hosts,dc=fudo,dc=org";
}
{
regex = "^uid=[^,/]+/root,cn=fudo.org,cn=gssapi,cn=auth$";
target = "cn=admin,dc=fudo,dc=org";
}
{
regex = "^uid=([^,/]+),cn=fudo.org,cn=gssapi,cn=auth$";
target = "uid=$1,ou=members,dc=fudo,dc=org";
}
{
regex = "^uid=host/([^,/]+),cn=fudo.org,cn=gssapi,cn=auth$";
target = "cn=$1,ou=hosts,dc=fudo,dc=org";
}
{
regex = "^gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth$";
target = "cn=admin,dc=fudo,dc=org";
}
];
};
children = {
"olcDatabase{-1}frontend" = {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcFrontendConfig" ];
olcDatabase = "{-1}frontend";
olcAccess = makeAccess {
"*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,dc=fudo,dc=org" = "manage";
"*" = "none";
};
};
};
};
"olcDatabase{0}config" = {
attrs = {
objectClass = [ "olcDatabaseConfig" ];
olcDatabase = "{0}config";
olcAccess = [ "by * none" ];
};
};
"olcDatabase{1}mdb" = {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
olcDatabase = "{1}mdb";
olcSuffix = cfg.base;
# olcRootDN = "cn=admin,${cfg.base}";
# olcRootPW = FIXME; # NOTE: this should be hashed...
olcDbDirectory = cfg.database-directory;
olcDbIndex = [ "objectClass eq" "uid eq" ];
olcAccess = makeAccess {
"attrs=userPassword,shadowLastChange" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"group/groupOfNames/member.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"dn.exact=cn=auth_reader,${cfg.base}" = "read";
"dn.exact=cn=replicator,${cfg.base}" = "read";
"self" = "write";
"*" = "auth";
};
"dn.base=cn=admin,ou=groups,${cfg.base}" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"users" = "read";
"*" = "none";
};
"dn.subtree=ou=groups,${cfg.base} attrs=memberUid" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"dn.regex=cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" = "write";
"group/groupOfNames/member.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"users" = "read";
"*" = "none";
};
"dn.subtree=ou=members,${cfg.base} attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"group/groupOfNames/member.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"dn.exact=cn=user_db_reader,${cfg.base}" = "read";
"users" = "read";
"*" = "none";
};
"*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,ou=groups,${cfg.base}" = "write";
"group/groupOfNames/member.exact=cn=admin,ou=groups,${cfg.base}" = "read";
"users" = "read";
"*" = "none";
};
};
};
};
};
};
declarativeContents = ''
dn: ${cfg.base}
+85 -72
View File
@@ -1,67 +1,32 @@
{ lib, config, ... }:
with lib;
let
hostname = config.instance.hostname;
cfg = config.fudo.mail-server;
container-maildir = "/var/lib/mail";
container-statedir = "/var/lib/mail-state";
container-shared = "container/mail-server";
container-postfix-cert = "${container-shared}/postfix/cert.pem";
container-postfix-key = "${container-shared}/postfix/key.pem";
container-dovecot-cert = "${container-shared}/dovecot/cert.pem";
container-dovecot-key = "${container-shared}/dovecot/key.pem";
container-fudo-ca-cert = "${container-shared}/fudo-ca.pem";
# Don't bother with group-id, nixos doesn't seem to use it anyway
container-mail-user = "mailer";
container-mail-user-id = 542;
container-mail-group = "mailer";
fudo-cfg = config.fudo.common;
in rec {
options.fudo.mail-server.container = {
ldap-url = mkOption {
type = types.str;
description = "URL of the LDAP server to use for authentication.";
example = "ldaps://auth.fudo.org/";
};
};
config = mkIf (cfg.enableContainer && !cfg.enable) {
# Disable postfix on thi host--it'll be run in the container instead
config = mkIf (cfg.enableContainer) {
# Disable postfix on this host--it'll be run in the container instead
services.postfix.enable = false;
# Copy data intended for the container to a path in /etc which can be
# bind-mounted.
environment.etc = {
"${container-postfix-cert}" = {
mode = "0444";
source = cfg.postfix.ssl-certificate;
};
"${container-postfix-key}" = {
mode = "0400";
source = cfg.postfix.ssl-private-key;
};
"${container-dovecot-cert}" = {
mode = "0444";
source = cfg.dovecot.ssl-certificate;
};
"${container-dovecot-key}" = {
mode = "0400";
source = cfg.dovecot.ssl-private-key;
};
"${container-fudo-ca-cert}" = {
mode = "0444";
source = "/etc/nixos/static/fudo_ca.pem";
fudo.acme.host-domains.${hostname}.${cfg.mail-hostname} = {
local-copies = {
postfix = {
user = "root";
};
dovecot-cert = {
user = "root";
};
};
};
security.acme.certs.${cfg.hostname}.email = fudo-cfg.admin-email;
services.nginx = mkIf cfg.monitoring {
enable = true;
@@ -71,14 +36,15 @@ in rec {
proxy_set_header Host $host;
'';
trusted-network-string =
optionalString ((length fudo-cfg.local-networks) > 0)
optionalString ((length config.instance.local-networks) > 0)
(concatStringsSep "\n"
(map (network: "allow ${network};") fudo-cfg.local-networks)) + ''
(map (network: "allow ${network};")
config.instance.local-networks)) + ''
deny all;'';
in {
"${cfg.hostname}" = {
"${cfg.mail-hostname}" = {
enableACME = true;
forceSSL = true;
@@ -119,7 +85,9 @@ in rec {
autoStart = true;
bindMounts = {
bindMounts = let
cert-copies = config.fudo.acme.host-domains.${hostname}.${cfg.mail-hostname}.local-copies;
in {
"${container-maildir}" = {
hostPath = cfg.mail-directory;
isReadOnly = false;
@@ -134,30 +102,74 @@ in rec {
hostPath = "/etc/${container-shared}";
isReadOnly = true;
};
"/run/mail/certs/postfix/cert.pem" = {
hostPath = cert-copies.postfix.certificate;
isReadOnly = true;
};
"/run/mail/certs/postfix/key.pem" = {
hostPath = cert-copies.postfix.private-key;
isReadOnly = true;
};
"/run/mail/certs/dovecot/cert.pem" = {
hostPath = cert-copies.dovecot.certificate;
isReadOnly = true;
};
"/run/mail/certs/dovecot/key.pem" = {
hostPath = cert-copies.dovecot.private-key;
isReadOnly = true;
};
};
imports = let
initialize-host = import ../../initialize-host.nix;
build-timestamp = config.instance.build-timestamp;
site = config.instance.site;
domain = config.instance.domain;
profile = "container";
in [
(initialize-host {
inherit
lib
pkgs
build-timestamp
site
domain
profile;
hostname = "mail-container";
})
];
config = { config, pkgs, ... }: {
environment.systemPackages = with pkgs; [ nmap ];
imports = [ ./mail.nix ];
environment = {
etc = {
"postfix-certs/key.pem" = {
source = "/etc/${container-postfix-key}";
user = config.services.postfix.user;
mode = "0400";
};
"dovecot-certs/key.pem" = {
source = "/etc/${container-dovecot-key}";
user = config.services.dovecot2.user;
mode = "0400";
};
environment.etc = {
"mail-server/postfix/cert.pem" = {
source = "/run/mail/certs/postfix/cert.pem";
user = config.services.postfix.user;
mode = "0444";
};
"mail-server/postfix/key.pem" = {
source = "/run/mail/certs/postfix/key.pem";
user = config.services.postfix.user;
mode = "0400";
};
"mail-server/dovecot/cert.pem" = {
source = "/run/mail/certs/dovecot/cert.pem";
user = config.services.dovecot.user;
mode = "0444";
};
"mail-server/dovecot/key.pem" = {
source = "/run/mail/certs/dovecot/key.pem";
user = config.services.dovecot.user;
mode = "0400";
};
};
imports = [ ./mail.nix ];
fudo.mail-server = {
enable = true;
hostname = cfg.hostname;
@@ -169,14 +181,15 @@ in rec {
state-directory = container-statedir;
mail-directory = container-maildir;
postfix.ssl-certificate = "/etc/${container-postfix-cert}";
postfix.ssl-private-key = "/etc/postfix-certs/key.pem";
postfix = {
ssl-certificate = "/etc/mail-server/postfix/cert.pem";
ssl-private-key = "/etc/mail-server/postfix/key.pem";
};
dovecot = {
ssl-certificate = "/etc/${container-dovecot-cert}";
ssl-private-key = "/etc/dovecot-certs/key.pem";
ssl-certificate = "/etc/mail-server/dovecot/cert.pem";
ssl-private-key = "/etc/mail-server/dovecot/key.pem";
ldap = {
# ca = "/etc/${container-fudo-ca-cert}";
server-urls = cfg.dovecot.ldap.server-urls;
reader-dn = cfg.dovecot.ldap.reader-dn;
reader-passwd = cfg.dovecot.ldap.reader-passwd;
+9 -2
View File
@@ -21,11 +21,18 @@ in {
description = "The main and default domain name for this email server.";
};
hostname = mkOption {
mail-hostname = mkOption {
type = types.str;
description = "The domain name to use for the mail server.";
};
ldap-url = mkOption {
type = types.str;
description = "URL of the LDAP server to use for authentication.";
example = "ldaps://auth.fudo.org/";
};
monitoring = mkEnableOption "Enable monitoring for the mail server.";
mail-user = mkOption {
@@ -176,7 +183,7 @@ in {
./mail/clamav.nix
];
config = mkIf cfg.enable {
config = mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [ 25 110 143 587 993 995 ];
};
+2 -5
View File
@@ -4,7 +4,6 @@ with lib;
let
inherit (lib.strings) concatStringsSep;
cfg = config.fudo.prometheus;
fudo-cfg = config.fudo.common;
in {
@@ -76,9 +75,6 @@ in {
};
config = mkIf cfg.enable {
security.acme.certs.${cfg.hostname}.email = fudo-cfg.admin-email;
services.nginx = {
enable = true;
@@ -111,7 +107,8 @@ in {
webExternalUrl = "https://${cfg.hostname}";
listenAddress = "127.0.0.1:9090";
listenAddress = "127.0.0.1";
port = 9090;
scrapeConfigs = [
{
+5 -2
View File
@@ -9,7 +9,10 @@ let
webmail-user = cfg.user;
webmail-group = cfg.group;
base-data-path = "/var/run/rainloop";
base-data-path = "/run/rainloop";
concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
fastcgi-conf = builtins.toFile "fastcgi.conf" ''
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
@@ -301,7 +304,7 @@ in {
site-config-file = builtins.toFile "${site}-rainloop.cfg"
(import ./include/rainloop.nix lib site site-cfg site-pkgs.${site}.version);
domain-cfg-file = builtins.toFile "${site}-domain.cfg" ''
domain-config-file = builtins.toFile "${site}-domain.cfg" ''
imap_host = "${site-cfg.mail-server}"
imap_port = 143
imap_secure = "TLS"
+18 -10
View File
@@ -1,16 +1,24 @@
{ lib, ... }:
with lib;
{
let
hostname-from-file = filename: builtins.replaceStrings [".nix"] [""] filename;
is-nix-file = filename: type: (builtins.match ".+\.nix$" filename) != null;
is-regular-file = filename: type: type == "regular" || type == "link";
host-files = host-path:
attrNames
(filterAttrs is-nix-file
(filterAttrs is-regular-file
(builtins.readDir host-path)));
hosts = host-path:
map hostname-from-file (host-files host-path);
in {
base-host-config = host-path: let
hostname-from-file = filename: builtins.replaceStrings [".nix"] [""] filename;
is-nix-file = filename: type: (builtins.match ".+\.nix$" filename) != null;
is-regular-file = filename: type: type == "regular" || type == "link";
host-files = attrNames (filterAttrs is-nix-file (filterAttrs is-regular-file (builtins.readDir host-path)));
hosts = map hostname-from-file host-files;
load-host-file = hostname: import (host-path + "/${hostname}.nix");
in genAttrs hosts (hostname: load-host-file hostname);
in genAttrs (hosts host-path) (hostname: load-host-file hostname);
host-list = host-path: hosts host-path;
}
+1 -3
View File
@@ -4,8 +4,6 @@ with lib;
let
cfg = config.informis.cl-gemini;
lisp-helper = import ../lisp.nix { inherit pkgs; };
feedOpts = { ... }: with types; {
options = {
url = mkOption {
@@ -163,7 +161,7 @@ in {
GEMINI_TEXTFILES_ROOT = cfg.textfiles-archive;
GEMINI_FEEDS = "${generate-feeds cfg.feeds}";
CL_SOURCE_REGISTRY = "${lisp-helper.lisp-source-registry pkgs.cl-gemini}";
CL_SOURCE_REGISTRY = "${pkgs.lib.fudo.lisp.lisp-source-registry pkgs.cl-gemini}";
};
path = with pkgs; [
+18 -7
View File
@@ -51,6 +51,11 @@ in {
type = attrsOf (submodule user.userOpts);
description = "List of users who should have access to the local host";
};
build-seed = mkOption {
type = str;
description = "Seed used to generate configuration.";
};
};
config = let
@@ -86,15 +91,21 @@ in {
config.fudo.domains.${local-domain}.local-networks //
config.fudo.sites.${local-site}.local-networks;
local-profile = host.profile;
build-seed = builtins.readFile config.fudo.secrets.files.build-seed;
in {
instance = {
local-domain = local-domain;
local-site = local-site;
local-users = local-users;
local-admins = local-admins;
local-groups = local-groups;
local-hosts = local-hosts;
local-profile = host.profile;
inherit
build-seed
local-domain
local-site
local-users
local-admins
local-groups
local-hosts
local-profile;
};
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
{ lib, ... }:
{ pkgs, ... }:
with lib;
with pkgs.lib;
let
pow = x: e: if (e == 0) then 1 else x * (pow x (e - 1));
+1 -2
View File
@@ -1,8 +1,7 @@
{ pkgs, ... }:
with pkgs.lib;
let
in rec {
rec {
gather-dependencies = pkg: unique (pkg.propagatedBuildInputs ++ (concatMap gather-dependencies pkg.propagatedBuildInputs));
lisp-source-registry = pkg: concatStringsSep ":" (map (p: "${p}//") (gather-dependencies pkg));
+8 -6
View File
@@ -1,10 +1,12 @@
(final: prev: let
ip = import ./ip.nix { lib = prev.lib; };
dns = import ./dns.nix { lib = prev.lib; };
in {
(final: prev: {
lib = prev.lib // {
fudo = {
inherit ip dns;
fudo = let
lib = prev.lib;
in {
ip = import ./ip.nix { pkgs = prev; };
dns = import ./dns.nix { pkgs = prev;};
passwd = import ./passwd.nix { pkgs = prev;};
lisp = import ./lisp.nix { pkgs = prev;};
};
};
})
+23 -11
View File
@@ -1,6 +1,6 @@
{ lib, ... }:
{ pkgs, ... }:
with lib;
with pkgs.lib;
let
hash-ldap-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation {
name = "${name}-ldap-passwd";
@@ -14,7 +14,7 @@ let
'';
installPhase = ''
mkdir $out
mkdir -p $out
mv ldap-passwd $out
'';
};
@@ -26,23 +26,35 @@ let
generate-random-passwd = name: length: pkgs.stdenv.mkDerivation {
name = "${name}-random-passwd";
phases = [ "buildPhase" "installPhase" ];
phases = [ "installPhase" ];
buildInputs = with pkgs; [ pwgen ];
buildPhase = ''
pwgen --symbols --num-passwords=1 ${length} > passwd
'';
installPhase = ''
mkdir $out
mv passwd $out
pwgen --secure --num-passwords=1 ${length} > $out
'';
};
generate-stablerandom-passwd = name: { seed, length ? 20, ... }:
pkgs.stdenv.mkDerivation {
name = "${name}-stablerandom-passwd";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ pwgen ];
installPhase = ''
echo "${name}-${seed}" > seedfile
pwgen --secure --num-passwords=1 -H seedfile ${toString length} > $out
'';
};
in {
hash-ldap-passwd = hash-ldap-passwd;
random-passwd-file = name: length:
toPath "${generate-random-passwd name length}/passwd";
builtins.toPath "${generate-random-passwd name length}";
stablerandom-passwd-file = name: seed:
builtins.toPath "${generate-stablerandom-passwd name { seed = seed; }}";
}
+18 -2
View File
@@ -1,7 +1,10 @@
{ lib, ... }:
with lib;
rec {
let
passwd = import ../passwd.nix { inherit lib; };
in rec {
encryptedFSOpts = { ... }: let
mountpoint = { mp, ... }: {
options = with types; {
@@ -31,6 +34,12 @@ rec {
'';
default = [ ];
};
world-readable = mkOption {
type = bool;
description = "Whether to leave the top level world-readable.";
default = true;
};
};
};
in {
@@ -81,7 +90,9 @@ rec {
};
};
hostOpts = { hostname, ... }: {
hostOpts = { name, ... }: let
hostname = name;
in {
options = with types; {
master-key = mkOption {
type = nullOr (submodule masterKeyOpts);
@@ -284,6 +295,11 @@ rec {
description = "Configuration parameters to set up initrd SSH network.";
default = null;
};
backplane-password-file = mkOption {
options = path;
description = "File containing the password used by this host to connect to the backplane.";
};
};
};
}
+5 -3
View File
@@ -2,12 +2,12 @@
with lib;
rec {
systemUserOpts = { username, ... }: {
systemUserOpts = { name, ... }: {
options = with lib.types; {
username = mkOption {
type = str;
description = "The system user's login name.";
default = username;
default = name;
};
description = mkOption {
@@ -23,7 +23,9 @@ rec {
};
};
userOpts = { username, ... }: {
userOpts = { name, ... }: let
username = name;
in {
options = with lib.types; {
username = mkOption {
type = str;