Lots of changes.

This commit is contained in:
niten 2022-04-26 18:39:53 -07:00
parent c40aba6133
commit 9edae4a39c
6 changed files with 269 additions and 240 deletions

View File

@ -103,14 +103,13 @@ in {
networking.firewall.allowedTCPPorts =
mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ];
environment.systemPackages = with pkgs; let
gitea-admin = writeShellScriptBin "gitea-admin" ''
TMP=$(mktemp -d /tmp/gitea-XXXXXXXX)
${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@
'';
in [
gitea-admin
];
environment.systemPackages = with pkgs;
let
gitea-admin = writeShellScriptBin "gitea-admin" ''
TMP=$(mktemp -d /tmp/gitea-XXXXXXXX)
${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@
'';
in [ gitea-admin ];
services = {
gitea = {

View File

@ -11,8 +11,7 @@ let
join-lines = lib.concatStringsSep "\n";
strip-ext = filename:
head (builtins.match "^(.+)[.][^.]+$" filename);
strip-ext = filename: head (builtins.match "^(.+)[.][^.]+$" filename);
userDatabaseOpts = { database, ... }: {
options = {
@ -109,25 +108,25 @@ let
makeNetworksEntry = networks: join-lines (map makeEntry networks);
makeLocalUserPasswordEntries = users: networks: let
network-entries = user: db:
join-lines
(map (network: "hostssl ${db} ${user} ${network} md5")
networks);
in join-lines (mapAttrsToList (user: opts:
join-lines (map (db: ''
local ${db} ${user} md5
host ${db} ${user} 127.0.0.1/16 md5
host ${db} ${user} ::1/128 md5
${network-entries user db}
'') (attrNames opts.databases))) (filterPasswordedUsers users));
makeLocalUserPasswordEntries = users: networks:
let
network-entries = user: db:
join-lines
(map (network: "hostssl ${db} ${user} ${network} md5") networks);
in join-lines (mapAttrsToList (user: opts:
join-lines (map (db: ''
local ${db} ${user} md5
host ${db} ${user} 127.0.0.1/16 md5
host ${db} ${user} ::1/128 md5
${network-entries user db}
'') (attrNames opts.databases))) (filterPasswordedUsers users));
userTableAccessSql = user: entity: access:
"GRANT ${access} ON ${entity} TO ${user};";
userDatabaseAccessSql = user: database: dbOpts: ''
\c ${database}
${join-lines
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
'';
userAccessSql = user: userOpts:
join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases);
@ -220,7 +219,7 @@ in {
cleanup-tasks = mkOption {
type = listOf str;
description = "List of actions to take during shutdown of the service.";
default = [];
default = [ ];
};
systemd-target = mkOption {
@ -254,8 +253,7 @@ in {
ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; };
}) opts.users)) cfg.databases)));
settings = let
ssl-enabled = cfg.ssl-certificate != null;
settings = let ssl-enabled = cfg.ssl-certificate != null;
in {
krb_server_keyfile = mkIf (cfg.keytab != null) cfg.keytab;
@ -286,9 +284,9 @@ in {
systemd = {
tmpfiles.rules = optional (cfg.state-directory != null) (let
user = config.systemd.services.postgresql.serviceConfig.User;
in "d ${cfg.state-directory} 0700 ${user} - - -");
tmpfiles.rules = optional (cfg.state-directory != null)
(let user = config.systemd.services.postgresql.serviceConfig.User;
in "d ${cfg.state-directory} 0700 ${user} - - -");
targets.${strip-ext cfg.systemd-target} = {
description = "Postgresql and associated systemd services.";
@ -296,14 +294,12 @@ in {
};
paths = let
user-password-files = mapAttrsToList
(user: userOpts: userOpts.password-file)
cfg.users;
user-password-files =
mapAttrsToList (user: userOpts: userOpts.password-file) cfg.users;
in {
postgresql-password-watcher = mkIf (length user-password-files > 0) {
wantedBy = [ "default.target" ];
description =
"Reset all user passwords if any changes occur.";
description = "Reset all user passwords if any changes occur.";
pathConfig = {
PathChanged = user-password-files;
Unit = "postgresql-password-setter.service";
@ -368,6 +364,10 @@ in {
# ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL*
# '';
# Wait a bit before starting dependent services, to let postgres finish initializing
serviceConfig.ExecStartPost =
mkAfter [ "${pkgs.coreutils}/bin/sleep 10" ];
postStop = concatStringsSep "\n" cfg.cleanup-tasks;
};
@ -382,8 +382,8 @@ in {
allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
extra-settings-sql = pkgs.writeText "settings.sql" ''
${concatStringsSep "\n"
(map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
${concatStringsSep "\n" (map allow-user-login
(mapAttrsToList (key: val: key) cfg.users))}
${usersAccessSql cfg.users}
'';
in pkgs.writeShellScript "postgresql-finalizer.sh" ''

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... } @ toplevel:
{ config, lib, pkgs, ... }@toplevel:
# NOTE: To get DNS records:
# pdnsutil --config-dir=... show-zone <domain>
@ -38,61 +38,67 @@ let
include-dir=${runtime-dir}
'';
make-pgpass-file = user: target-file: let
db = cfg.database;
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
touch ${target-file}
chown ${user} ${target-file}
chmod 700 ${target-file}
PASSWORD=$(cat ${db.password-file})
echo "${db.host}:${toString db.port}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
'';
make-pgpass-file = user: target-file:
let db = cfg.database;
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
touch ${target-file}
chown ${user} ${target-file}
chmod 700 ${target-file}
PASSWORD=$(cat ${db.password-file})
echo "${db.host}:${
toString db.port
}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
'';
mkRecord = name: type: content: {
inherit name type content;
};
mkRecord = name: type: content: { inherit name type content; };
initialize-domain-sql = domain: let
domain-name = domain.domain;
host-ip = pkgs.lib.network.host-ipv4 config hostname;
ipv6-net = net: (builtins.match ":" net) != null;
ipv4-net = net: !(ipv6-net net);
domain-records = [
(mkRecord domain-name "SOA" "ns1.${domain-name} hostmaster.${domain-name} ${toString config.instance.build-timestamp} 10800 3600 1209600 3600")
(mkRecord "_dmark.${domain-name}" "TXT" ''"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
(mkRecord domain-name "NS" "ns1.${domain-name}")
(mkRecord domain-name "TXT" (let
networks = config.instance.local-networks;
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
in ''"v=spf1 mx ${networks-string} -all"''))
(mkRecord "ns1.${domain-name}" "A" host-ip)
(mkRecord domain-name "A" host-ip)
] ++
(optional (domain.gssapi-realm != null)
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
domain.aliases);
records-strings = map (record:
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
domain-records;
in pkgs.writeText "initialize-${domain-name}.sql" ''
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${toString config.instance.build-timestamp}');
${concatStringsSep "\n" records-strings}
'';
initialize-domain-sql = domain:
let
domain-name = domain.domain;
host-ip = pkgs.lib.network.host-ipv4 config hostname;
ipv6-net = net: (builtins.match ":" net) != null;
ipv4-net = net: !(ipv6-net net);
domain-records = [
(mkRecord domain-name "SOA"
"ns1.${domain-name} hostmaster.${domain-name} ${
toString config.instance.build-timestamp
} 10800 3600 1209600 3600")
(mkRecord "_dmark.${domain-name}" "TXT" ''
"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
(mkRecord domain-name "NS" "ns1.${domain-name}")
(mkRecord domain-name "TXT" (let
networks = config.instance.local-networks;
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
in ''"v=spf1 mx ${networks-string} -all"''))
(mkRecord "ns1.${domain-name}" "A" host-ip)
(mkRecord domain-name "A" host-ip)
] ++ (optional (domain.gssapi-realm != null)
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"''))
++ (mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
domain.aliases);
records-strings = map (record:
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
domain-records;
in pkgs.writeText "initialize-${domain-name}.sql" ''
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${
toString config.instance.build-timestamp
}');
${concatStringsSep "\n" records-strings}
'';
initialize-domain-script = domain: let
domain-name = domain.domain;
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
logger "${domain-name} already initialized, skipping"
exit 0
else
logger "initializing ${domain-name} in powerdns database"
psql -f ${initialize-domain-sql domain}
fi
'';
initialize-domain-script = domain:
let domain-name = domain.domain;
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
logger "${domain-name} already initialized, skipping"
exit 0
else
logger "initializing ${domain-name} in powerdns database"
psql -f ${initialize-domain-sql domain}
fi
'';
domainOpts = { name, ... }: {
options = with types; {
@ -111,7 +117,7 @@ let
aliases = mkOption {
type = attrsOf str;
description = "Map of alias to authoritative hostname for this domain.";
default = {};
default = { };
};
gssapi-realm = mkOption {
@ -152,7 +158,7 @@ in {
listen-v6-addresses = mkOption {
type = listOf str;
description = "List of IPv6 addresses on which to listen.";
default = [];
default = [ ];
};
domains = mkOption {
@ -238,32 +244,34 @@ in {
systemd = let
pgpass-file = "${runtime-dir}/pgpass";
initialize-jobs = mapAttrs' (_: domain: let
domain-name = domain.domain;
in nameValuePair "powerdns-initialize-${domain-name}" {
description = "Initialize the ${domain-name} domain";
requires = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
after = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
requiredBy = [ "powerdns.service" ];
wantedBy = [ "powerdns.service" ];
before = [ "powerdns.service" ];
environment = {
PGHOST = cfg.database.host;
PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port;
PGSSLMODE= "require";
PGPASSFILE = pgpass-file;
};
path = with pkgs; [ postgresql util-linux ];
serviceConfig = {
ExecStart = initialize-domain-script domain;
};
}) cfg.domains;
initialize-jobs = mapAttrs' (_: domain:
let domain-name = domain.domain;
in nameValuePair "powerdns-initialize-${domain-name}" {
description = "Initialize the ${domain-name} domain";
requires = [
"powerdns-initialize-db.service"
"powerdns-generate-pgpass.service"
];
after = [
"powerdns-initialize-db.service"
"powerdns-generate-pgpass.service"
];
requiredBy = [ "powerdns.service" ];
wantedBy = [ "powerdns.service" ];
before = [ "powerdns.service" ];
environment = {
PGHOST = cfg.database.host;
PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port;
PGSSLMODE = "require";
PGPASSFILE = pgpass-file;
};
path = with pkgs; [ postgresql util-linux ];
serviceConfig = { ExecStart = initialize-domain-script domain; };
}) cfg.domains;
in {
tmpfiles.rules = [
"d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -"
];
tmpfiles.rules = [ "d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -" ];
services = initialize-jobs // {
powerdns-generate-pgpass = {
@ -285,7 +293,7 @@ in {
PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port;
PGSSLMODE= "require";
PGSSLMODE = "require";
PGPASSFILE = pgpass-file;
};
serviceConfig = {
@ -297,16 +305,20 @@ in {
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
fi
'';
# Wait until posgresql is available before starting
ExecStartPre =
pkgs.writeShellScript "ensure-postgresql-running.sh" ''
while [ ! "$( psql -tAc "SELECT 1" )" ]; do
${pkgs.coreutils}/bin/sleep 3
done
'';
};
};
powerdns = {
description = "PowerDNS nameserver.";
requires = [ "powerdns-config-generator.service" ];
after = [
"network.target"
"powerdns-config-generator.service"
];
after = [ "network.target" "powerdns-config-generator.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ powerdns postgresql util-linux ];
serviceConfig = {
@ -318,35 +330,33 @@ in {
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET
'';
ExecStart = pkgs.writeShellScript "launch-powerdns.sh" (concatStringsSep " " [
"${pkgs.powerdns}/bin/pdns_server"
"--setuid=${cfg.user}"
"--setgid=${cfg.group}"
"--chroot=${runtime-dir}"
"--daemon=no"
"--guardian=no"
"--write-pid=no"
"--config-dir=${pdns-config-dir}"
]);
ExecStart = pkgs.writeShellScript "launch-powerdns.sh"
(concatStringsSep " " [
"${pkgs.powerdns}/bin/pdns_server"
"--setuid=${cfg.user}"
"--setgid=${cfg.group}"
"--chroot=${runtime-dir}"
"--daemon=no"
"--guardian=no"
"--write-pid=no"
"--config-dir=${pdns-config-dir}"
]);
ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh"
(concatStringsSep "\n"
(mapAttrsToList
(_: domain: ''
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
logger "zone ${domain.domain} does not exist in powerdns database"
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
else
logger "not securing zone ${domain.domain} in powerdns database"
fi
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
'')
cfg.domains));
(concatStringsSep "\n" (mapAttrsToList (_: domain: ''
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
logger "zone ${domain.domain} does not exist in powerdns database"
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
else
logger "not securing zone ${domain.domain} in powerdns database"
fi
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
'') cfg.domains));
};
};
};

View File

@ -1,8 +1,7 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fudo.metrics.prometheus;
let cfg = config.fudo.metrics.prometheus;
in {
@ -19,26 +18,24 @@ in {
postfix = [ "postfix._metrics._tcp.my-domain.com" ];
};
default = {
dovecot = [];
node = [];
postfix = [];
rspamd = [];
dovecot = [ ];
node = [ ];
postfix = [ ];
rspamd = [ ];
};
};
static-targets = mkOption {
type = attrsOf (listOf str);
description = ''
A map of exporter type to a list of host:ports from which to collect metrics.
'';
example = {
node = [ "my-host.my-domain:1111" ];
};
A map of exporter type to a list of host:ports from which to collect metrics.
'';
example = { node = [ "my-host.my-domain:1111" ]; };
default = {
dovecot = [];
node = [];
postfix = [];
rspamd = [];
dovecot = [ ];
node = [ ];
postfix = [ ];
rspamd = [ ];
};
};
@ -47,7 +44,7 @@ in {
description = ''
A list of explicit <host:port> docker targets from which to gather node data.
'';
default = [];
default = [ ];
};
push-url = mkOption {
@ -86,6 +83,15 @@ in {
"d ${cfg.state-directory} 0700 ${config.systemd.services.prometheus.serviceConfig.User} - - -"
];
fileSystems =
let state-dir = "/var/lib/${config.services.prometheus.stateDir}";
in mkIf (cfg.state-directory != state-dir) {
${state-dir} = {
device = cfg.state-directory;
options = [ "bind" ];
};
};
services.nginx = {
enable = true;
recommendedOptimisation = true;
@ -93,17 +99,18 @@ in {
virtualHosts = {
"${cfg.hostname}" = {
enableACME = ! cfg.private-network;
forceSSL = ! cfg.private-network;
enableACME = !cfg.private-network;
forceSSL = !cfg.private-network;
locations."/" = {
proxyPass = "http://127.0.0.1:9090";
extraConfig = let
local-networks = config.instance.local-networks;
extraConfig = let local-networks = config.instance.local-networks;
in "${optionalString ((length local-networks) > 0)
(concatStringsSep "\n"
(map (network: "allow ${network};") local-networks)) + "\ndeny all;"}";
(concatStringsSep "\n"
(map (network: "allow ${network};") local-networks)) + ''
deny all;''}";
};
};
};
@ -124,19 +131,22 @@ in {
honor_labels = false;
scheme = if cfg.private-network then "http" else "https";
metrics_path = "/metrics/${type}";
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then
[ { names = cfg.service-discovery-dns.${type}; } ] else [];
static_configs = if (hasAttr type cfg.static-targets) then
[ { targets = cfg.static-targets.${type}; } ] else [];
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then [{
names = cfg.service-discovery-dns.${type};
}] else
[ ];
static_configs = if (hasAttr type cfg.static-targets) then [{
targets = cfg.static-targets.${type};
}] else
[ ];
};
in map make-job ["docker" "node" "dovecot" "postfix" "rspamd"];
in map make-job [ "docker" "node" "dovecot" "postfix" "rspamd" ];
pushgateway = {
enable = if (cfg.push-url != null) then true else false;
web = {
external-url = if cfg.push-url == null then
cfg.push-address else
cfg.push-url;
external-url =
if cfg.push-url == null then cfg.push-address else cfg.push-url;
listen-address = cfg.push-address;
};
};

View File

@ -16,7 +16,8 @@ let
decrypt-script = { secret-name, source-file, target-host, target-file
, host-master-key, user, group, permissions }:
pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
pkgs.writeShellScript
"decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
rm -f ${target-file}
touch ${target-file}
chown ${user}:${group} ${target-file}
@ -24,27 +25,33 @@ let
# NOTE: silly hack because sometimes age leaves a blank line
# Only include lines with at least one non-space character
SRC=$(mktemp fudo-secret-${target-host}-${secret-name}.XXXXXXXX)
cat ${encrypt-on-disk {
cat ${
encrypt-on-disk {
inherit secret-name source-file target-host;
target-pubkey = host-master-key.public-key;
}} | grep "[^ ]" > $SRC
}
} | grep "[^ ]" > $SRC
age -d -i ${host-master-key.key-path} -o ${target-file} $SRC
rm -f $SRC
'';
secret-service = target-host: secret-name:
{ source-file, target-file, user, group, permissions, ... }: {
description = "decrypt secret ${secret-name} for ${target-host}.";
description =
"decrypt secret ${secret-name} at ${target-host}:${target-file}.";
wantedBy = [ cfg.secret-target ];
before = [ cfg.secret-target ];
serviceConfig = {
Type = "oneshot";
ExecStart = let
host-master-key = config.fudo.hosts.${target-host}.master-key;
in decrypt-script {
inherit secret-name source-file target-host target-file host-master-key
user group permissions;
};
Type = "simple";
RemainAfterExit = true;
ExecStart =
let host-master-key = config.fudo.hosts.${target-host}.master-key;
in decrypt-script {
inherit secret-name source-file target-host target-file
host-master-key user group permissions;
};
ExecStop = pkgs.writeShellScript "fudo-remove-${secret-name}-secret.sh"
"rm -f ${target-file}";
};
path = [ pkgs.age ];
};
@ -52,8 +59,10 @@ let
secretOpts = { name, ... }: {
options = with types; {
source-file = mkOption {
type = path; # CAREFUL: this will copy the file to nixstore...keep on deploy host
description = "File from which to load the secret. If unspecified, a random new password will be generated.";
type =
path; # CAREFUL: this will copy the file to nixstore...keep on deploy host
description =
"File from which to load the secret. If unspecified, a random new password will be generated.";
default = "${generate-secret name}/passwd";
};
@ -72,7 +81,7 @@ let
group = mkOption {
type = str;
description = "Group (on target host) to which the file will belong.";
default = "nogroup";
default = "root";
};
permissions = mkOption {
@ -84,7 +93,7 @@ let
metadata = mkOption {
type = attrsOf anything;
description = "Arbitrary metadata associated with this secret.";
default = {};
default = { };
};
service = mkOption {
@ -97,32 +106,34 @@ let
nix-build-users = let usernames = attrNames config.users.users;
in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null)
usernames;
usernames;
generate-secret = name: pkgs.stdenv.mkDerivation {
name = "${name}-generated-passwd";
generate-secret = name:
pkgs.stdenv.mkDerivation {
name = "${name}-generated-passwd";
phases = [ "installPhase" ];
phases = [ "installPhase" ];
buildInputs = with pkgs; [ pwgen ];
buildInputs = with pkgs; [ pwgen ];
buildPhase = ''
echo "${name}-${config.instance.build-timestamp}" >> file.txt
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
rm -f file.txt
'';
buildPhase = ''
echo "${name}-${config.instance.build-timestamp}" >> file.txt
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
rm -f file.txt
'';
installPhase = ''
mkdir $out
mv passwd $out/passwd
'';
};
installPhase = ''
mkdir $out
mv passwd $out/passwd
'';
};
in {
options.fudo.secrets = with types; {
enable = mkOption {
type = bool;
description = "Include secrets in the build (disable when secrets are unavailable)";
description =
"Include secrets in the build (disable when secrets are unavailable)";
default = true;
};
@ -175,9 +186,7 @@ in {
config = mkIf cfg.enable {
users.groups = {
${cfg.secret-group} = {
members = cfg.secret-users ++ nix-build-users;
};
${cfg.secret-group} = { members = cfg.secret-users ++ nix-build-users; };
};
systemd = let
@ -189,25 +198,24 @@ in {
{ };
host-secret-services = let
head-or-null = lst: if (lst == []) then null else head lst;
head-or-null = lst: if (lst == [ ]) then null else head lst;
strip-service = service-name:
head-or-null
(builtins.match "^(.+)[.]service$" service-name);
head-or-null (builtins.match "^(.+)[.]service$" service-name);
in mapAttrs' (secret: secretOpts:
(nameValuePair (strip-service secretOpts.service)
(secret-service hostname secret secretOpts))) host-secrets;
trace-all = obj: builtins.trace obj obj;
host-secret-paths = mapAttrsToList
(secret: secretOpts:
let perms = if secretOpts.group != "nobody" then "550" else "500";
in "d ${dirOf secretOpts.target-file} ${perms} ${secretOpts.user} ${secretOpts.group} - -")
host-secrets;
host-secret-paths = mapAttrsToList (secret: secretOpts:
let perms = if secretOpts.group != "nobody" then "550" else "500";
in "d ${
dirOf secretOpts.target-file
} ${perms} ${secretOpts.user} ${secretOpts.group} - -") host-secrets;
build-secret-paths =
map (path: "d '${path}' - root ${cfg.secret-group} - -")
cfg.secret-paths;
cfg.secret-paths;
in {
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
@ -231,7 +239,8 @@ in {
strip-ext = filename: head (builtins.match "^(.+)[.]target$" filename);
in {
${strip-ext cfg.secret-target} = {
description = "Target indicating that all Fudo secrets are available.";
description =
"Target indicating that all Fudo secrets are available.";
wantedBy = [ "default.target" ];
};
};

View File

@ -2,33 +2,34 @@
with pkgs.lib;
let
head-or-null = lst: if (lst == []) then null else head lst;
head-or-null = lst: if (lst == [ ]) then null else head lst;
is-regular-file = filename: type: type == "regular" || type == "link";
regular-files = path: filterAttrs is-regular-file (builtins.readDir path);
matches-ext = ext: filename: type: (builtins.match ".+[.]${ext}$" filename) != null;
matches-ext = ext: filename: type:
(builtins.match ".+[.]${ext}$" filename) != null;
is-nix-file = matches-ext "nix";
strip-ext = ext: filename: head-or-null (builtins.match "(.+)[.]${ext}$" filename);
strip-ext = ext: filename:
head-or-null (builtins.match "(.+)[.]${ext}$" filename);
get-ext = filename: head-or-null (builtins.match "^.+[.](.+)$" filename);
hostname-from-file = filename: strip-ext "nix";
nix-files = path:
attrNames
(filterAttrs is-nix-file
(filterAttrs is-regular-file
(builtins.readDir path)));
basename-to-file-map = path: let
files = nix-files path;
in listToAttrs
(map (file:
nameValuePair (strip-ext "nix" file)
(path + "/${file}"))
ext-files = ext: path:
attrNames (filterAttrs (matches-ext ext)
(filterAttrs is-regular-file (builtins.readDir path)));
nix-files = ext-files "nix";
basename-to-file-map = path:
let files = nix-files path;
in listToAttrs
(map (file: nameValuePair (strip-ext "nix" file) (path + "/${file}"))
files);
import-by-basename = path:
mapAttrs (attr: attr-file: import attr-file)
(basename-to-file-map path);
mapAttrs (attr: attr-file: import attr-file) (basename-to-file-map path);
list-nix-files = nix-files;
in {
inherit basename-to-file-map import-by-basename list-nix-files;
inherit strip-ext basename-to-file-map import-by-basename list-nix-files
ext-files;
}