Tons of changes, I guess?

This commit is contained in:
niten 2021-11-05 07:06:08 -07:00
parent c31af09ede
commit 2dd5407129
62 changed files with 1789 additions and 958 deletions

29
config/aliases.nix Normal file
View File

@ -0,0 +1,29 @@
{ config, lib, pkgs, ... }:
let
hostname = config.instance.hostname;
admins = config.instance.local-admins;
domain = config.instance.local-domain;
gen-addrs = names: domain:
map (name: "${name}@${domain}") names;
admin-addrs = gen-addrs admins domain;
in {
config.fudo.mail-server.alias-users = {
root = admin-addrs;
postmaster = admin-addrs;
www-data = admin-addrs;
hostmaster = admin-addrs;
webmaster = admin-addrs;
ftp = admin-addrs;
irc = admin-addrs;
admin = admin-addrs;
system = admin-addrs;
asdf = [ "mswaffer@gmail.com" "bouncetest@fudo.org" ];
network-info = [ "niten@fudo.org" ];
};
}

View File

@ -1,16 +1,16 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ {
imports = [ imports = [
./bash.nix ./aliases.nix
./common.nix ./bash.nix
./domains.nix ./common.nix
./groups.nix ./domains.nix
./hosts.nix ./groups.nix
./networks.nix ./hosts.nix
./profiles.nix ./networks.nix
./sites.nix ./sites.nix
./users.nix ./users.nix
./wireless-networks.nix ./wireless-networks.nix
]; ];
} }

View File

@ -0,0 +1,5 @@
{ config, lib, pkgs, ... }:
{
}

View File

@ -3,7 +3,11 @@
{ {
config.fudo.domains = { config.fudo.domains = {
"fudo.org" = { "fudo.org" = {
local-networks = [ "208.81.1.128/28" "208.81.3.112/28" ]; local-networks = [
"208.81.1.128/28"
"208.81.3.112/28"
"91.229.23.204/31"
];
local-users = [ "niten" "reaper" ]; local-users = [ "niten" "reaper" ];
local-groups = [ "fudo" "selby" "admin" ]; local-groups = [ "fudo" "selby" "admin" ];
@ -13,7 +17,12 @@
}; };
"sea.fudo.org" = { "sea.fudo.org" = {
local-networks = [ "10.0.0.0/16" ]; local-networks = [
"10.0.0.0/16"
"208.81.1.128/28"
"208.81.3.112/28"
];
local-users = [ "niten" "reaper" "xiaoxuan" "ken" ]; local-users = [ "niten" "reaper" "xiaoxuan" "ken" ];
local-groups = [ "fudo" "selby" "admin" ]; local-groups = [ "fudo" "selby" "admin" ];
@ -44,7 +53,9 @@
}; };
"informis.land" = { "informis.land" = {
local-networks = [ ]; local-networks = [
"172.86.179.17/29"
];
local-users = [ "niten" "viator" ]; local-users = [ "niten" "viator" ];
local-groups = [ "admin" "informis" ]; local-groups = [ "admin" "informis" ];
@ -52,5 +63,19 @@
admin-email = "viator@informis.land"; admin-email = "viator@informis.land";
gssapi-realm = "INFORMIS.LAND"; gssapi-realm = "INFORMIS.LAND";
}; };
eur.fudo.org = {
local-networks = [
"208.81.1.128/28"
"208.81.3.112/28"
"91.229.23.204/31"
];
local-users = [ "niten"];
local-groups = [ "admin" ];
local-admins = [ "niten" ];
admin-email = "nitenn@fudo.org";
gssapi-realm = "FUDO.ORG";
};
}; };
} }

View File

@ -3,8 +3,15 @@
{ {
boot = { boot = {
initrd = { initrd = {
availableKernelModules = availableKernelModules = [
[ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "floppy" "sd_mod" "sr_mod" ]; "uhci_hcd"
"ehci_pci"
"ata_piix"
"ahci"
"floppy"
"sd_mod"
"sr_mod"
];
kernelModules = [ "dm-snapshot" ]; kernelModules = [ "dm-snapshot" ];
}; };
@ -23,23 +30,27 @@
"/boot" = { "/boot" = {
device = "/dev/disk/by-label/france-boot"; device = "/dev/disk/by-label/france-boot";
fsType = "ext4"; fsType = "ext4";
options = [ "noatime" "nodiratime" ];
}; };
"/" = { "/" = {
device = "/dev/disk/by-label/france-root"; device = "/dev/disk/by-label/france-root";
fsType = "ext4"; fsType = "ext4";
options = [ "noatime" "nodiratime" ];
}; };
"/var/lib/lxd/storage-pools/pool0" = { "/var/lib/lxd/storage-pools/pool0" = {
device = "/dev/disk/by-label/pool0"; device = "/dev/disk/by-label/pool0";
fsType = "btrfs"; fsType = "btrfs";
label = "pool0"; label = "pool0";
options = [ "noatime" "nodiratime" "noexec" ];
}; };
"/var/lib/lxd/storage-pools/pool1" = { "/var/lib/lxd/storage-pools/pool1" = {
device = "/dev/france-user/fudo-user"; device = "/dev/france-user/fudo-user";
fsType = "btrfs"; fsType = "btrfs";
label = "pool1"; label = "pool1";
options = [ "noatime" "nodiratime" "noexec" ];
}; };
}; };
@ -50,6 +61,8 @@
hardware.bluetooth.enable = false; hardware.bluetooth.enable = false;
networking = { networking = {
useDHCP = false;
macvlans = { macvlans = {
intif0 = { intif0 = {
interface = "enp4s0f1"; interface = "enp4s0f1";
@ -60,6 +73,11 @@
interface = "enp4s0f0"; interface = "enp4s0f0";
mode = "bridge"; mode = "bridge";
}; };
# extif1 = {
# interface = "enp4s0f0";
# mode = "bridge";
# };
}; };
interfaces = { interfaces = {
@ -72,6 +90,11 @@
# output of: echo france-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' # output of: echo france-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
macAddress = "02:5e:ff:e4:83:e4"; macAddress = "02:5e:ff:e4:83:e4";
}; };
# extif1 = {
# # output of: echo france-extif1|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
# macAddress = "02:30:91:97:40:1f";
# };
}; };
}; };
} }

View File

@ -0,0 +1,75 @@
{ config, lib, pkgs, ... }:
with lib; {
boot = {
initrd = {
availableKernelModules = [
"ahci"
"usbhid"
];
kernelModules = [ "dm-snapshot" ];
};
kernelModules = [ ];
extraModulePackages = [ ];
loader.grub = {
enable = true;
version = 2;
device = "/dev/sda";
};
supportedFilesystems = [ "btrfs" ];
};
fileSystems = {
"/" = {
device = "root-tmpfs";
fsType = "tmpfs";
options = [ "mode=755" "noexec" ];
};
"/boot" = {
device = "/dev/disk/by-label/boot";
fsType = "ext4";
options = [ "noexec" "noatime" "nodiratime" ];
};
"/nix" = {
device = "/dev/disk/by-label/system";
fsType = "btrfs";
options = [ "subvol=@nix" "compress=zstd" "noatime" "nodiratime" ];
};
"/var/log" = {
device = "/dev/disk/by-label/system";
fsType = "btrfs";
options = [ "subvol=@logs" "compress=zstd" "noatime" "nodiratime" "noexec" ];
neededForBoot = true;
};
"/state" = {
device = "/dev/disk/by-label/system";
fsType = "btrfs";
options = [ "subvol=@state" "compress=zstd" "noatime" "nodiratime" "noexec" ];
};
};
swapDevices = [{ device = "/dev/disk/by-label/swap"; }];
networking = {
macvlans = {
extif0 = {
interface = "eno2";
mode = "bridge";
};
};
useDHCP = false;
interfaces = {
extif0 = {
# output of: echo legatus-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'
macAddress = pkgs.lib.fudo.network.generate-mac-address "legatus" "extif0";
};
};
};
}

View File

@ -0,0 +1,5 @@
{ config, lib, pkgs, ... }:
{
}

View File

@ -71,6 +71,7 @@
device = "/dev/disk/by-label/socrates-data"; device = "/dev/disk/by-label/socrates-data";
fsType = "btrfs"; fsType = "btrfs";
options = [ "subvol=@log" "compress=zstd" "noatime" "nodiratime" "noexec" ]; options = [ "subvol=@log" "compress=zstd" "noatime" "nodiratime" "noexec" ];
neededForBoot = true;
}; };
"/state" = { "/state" = {

View File

@ -9,6 +9,7 @@ let
domain = config.fudo.domains.${domain-name}; domain = config.fudo.domains.${domain-name};
host-fqdn = "${hostname}.${domain-name}"; host-fqdn = "${hostname}.${domain-name}";
mail-hostname = "mail.fudo.org"; mail-hostname = "mail.fudo.org";
mail-directory = "/srv/mail";
secrets = config.fudo.secrets.host-secrets.france; secrets = config.fudo.secrets.host-secrets.france;
secret-files = config.fudo.secrets.files; secret-files = config.fudo.secrets.files;
@ -37,14 +38,73 @@ in {
config = { config = {
security.acme.email = "admin@fudo.org"; security.acme.email = "admin@fudo.org";
fudo = { fileSystems = {
"/srv/archiva" = {
fsType = "btrfs";
label = "pool0";
options = [ "noatime" "nodiratime" "noexec" "subvol=archiva" ];
};
"/srv/grafana" = {
fsType = "btrfs";
label = "pool0";
options = [ "noatime" "nodiratime" "noexec" "subvol=grafana" ];
};
"/srv/gitlab" = {
fsType = "btrfs";
label = "pool0";
options = [ "noatime" "nodiratime" "noexec" "subvol=grafana" ];
};
${mail-directory} = {
fsType = "btrfs";
label = "pool0";
options = [ "noatime" "nodiratime" "noexec" "subvol=mail" ];
};
};
users.users.archiva = {
isSystemUser = true;
group = "nogroup";
};
virtualisation = {
lxd.enable = true;
oci-containers = {
backend = "docker";
containers = {
archiva = {
image = "xetusoss/archiva";
autoStart = true;
ports = [ "8001:8080/tcp" ];
# Ugly: name-to-uid lookup fails.
user = toString config.users.users.archiva.uid;
volumes = [ "/srv/archiva:/archiva-data" ];
environment = {
# Not directly connected to the world anyway
SSL_ENABLED = "false";
PROXY_BASE_URL = "https://archiva.fudo.org/";
};
};
};
};
};
fudo = let
backplane-dns-password-file = pkgs.lib.fudo.passwd.stablerandom-passwd-file
"dns-service-backplane-passwd"
"dns-service-backplane-passwd-${config.instance.build-seed}";
in {
hosts.france.external-interfaces = [ "extif0" ]; hosts.france.external-interfaces = [ "extif0" ];
acme.host-domains.france."france.fudo.org" = { acme.host-domains.france."france.fudo.org" = {
email = "admin@fudo.org"; email = "admin@fudo.org";
local-copies = { local-copies = {
postgres = { postgres = {
user = config.services.postgresql.user; user = "postgres";
}; };
openldap = { openldap = {
user = config.services.openldap.user; user = config.services.openldap.user;
@ -56,26 +116,6 @@ in {
ldap-user = config.services.openldap.user; ldap-user = config.services.openldap.user;
ldap-group = config.services.openldap.group; ldap-group = config.services.openldap.group;
in { in {
ldap-ssl-certificate = {
source-file = cfg.ssl-certificate;
target-file = "/run/openldap/ssl-certificate.pem";
user = ldap-user;
group = ldap-group;
permissions = "0444";
};
ldap-ssl-private-key = {
source-file = cfg.ssl-private-key;
target-file = "/run/openldap/ssl-private-key.pem";
user = ldap-user;
group = ldap-group;
};
ldap-ssl-ca-certificate = {
source-file = cfg.ssl-ca-certificate;
target-file = "/run/openldap/ssl-ca-certificate.pem";
user = ldap-user;
group = ldap-group;
permissions = "0444";
};
ldap-keytab = { ldap-keytab = {
source-file = secret-files.service-keytabs.france.ldap; source-file = secret-files.service-keytabs.france.ldap;
target-file = "/run/openldap/ldap.keytab"; target-file = "/run/openldap/ldap.keytab";
@ -83,7 +123,8 @@ in {
group = ldap-group; group = ldap-group;
}; };
ldap-root-passwd = { ldap-root-passwd = {
source-file = passwd.random-passwd-file; source-file =
pkgs.lib.fudo.passwd.random-passwd-file "ldap-root-passwd" 20;
target-file = "/run/openldap/root.passwd"; target-file = "/run/openldap/root.passwd";
user = ldap-user; user = ldap-user;
group = ldap-group; group = ldap-group;
@ -91,7 +132,12 @@ in {
postgres-keytab = { postgres-keytab = {
source-file = secret-files.service-keytabs.france.postgres; source-file = secret-files.service-keytabs.france.postgres;
target-file = "/run/postgres/postgres.keytab"; target-file = "/run/postgres/postgres.keytab";
user = config.services.postgresql.user; user = "postgres"; # This is just plain hard-coded...
};
backplane-dns-password = {
source-file = backplane-dns-password-file;
target-file = "/run/backplane/dns/xmpp.passwd";
user = config.fudo.backplane.dns.user;
}; };
}; };
@ -117,34 +163,34 @@ in {
kdc = { kdc = {
state-directory = "/state/kerberos"; state-directory = "/state/kerberos";
master-key-file = ""; master-key-file = secret-files.realm-master-keys."FUDO.ORG";
listen-ips = [ primary-ip "127.0.0.1" "127.0.1.1" "::1" ]; listen-ips = [ primary-ip "127.0.0.1" "127.0.1.1" "::1" ];
}; };
jabber = { jabber = {
ldap-servers = [ "france.fudo.org" ]; ldap-servers = [ "france.fudo.org" ];
listen-ips = [ primary-ip ]; listen-ips = [ primary-ip ];
};
backplane = { backplane = {
host-passwd-files = let host-passwd-files = let
hosts = attrNames config.fudo.hosts; hosts = attrNames config.fudo.hosts;
in mapAttrs (hostname: hostOpts: hostOpts.backplane-password-file) in mapAttrs (hostname: hostOpts: hostOpts.backplane-password-file)
config.fudo.hosts; config.fudo.hosts;
service-passwd-files = genAttrs [ "dns" ] service-passwd-files = {
(service-name: dns = backplane-dns-password-file;
lib.fudo.passwd.stablerandom-passwd-file };
"${service-name}-service-backplane-passwd" };
"${service-name}-service-backplane-passwd-${config.instance.build-seed}");
}; };
backplane-server = { backplane-server = {
listen-ips = [ primary-ip ]; listen-ips = [ primary-ip ];
backplane-dns-password-file =
secrets.backplane-dns-password.target-file;
}; };
mail = { mail = {
mail-directory = "/srv/mail/mailboxes"; mail-directory = "${mail-directory}/mailboxes";
state-directory = "/srv/mail/var"; state-directory = "${mail-directory}/var";
ldap-server-urls = [ ldap-server-urls = [
"ldap://france.fudo.org" "ldap://france.fudo.org"
]; ];
@ -169,6 +215,18 @@ in {
ssl-certificate = cert-copy.certificate; ssl-certificate = cert-copy.certificate;
ssl-private-key = cert-copy.private-key; ssl-private-key = cert-copy.private-key;
}; };
dns = {
default-host = primary-ip;
listen-ip = primary-ip;
mail-hosts = [ "mail.fudo.org" ];
};
chat = {
chat-hostname = "chat.fudo.org";
mail-server = "mail.fudo.org";
database-host = "localhost";
};
}; };
minecraft-server = { minecraft-server = {
@ -181,8 +239,6 @@ in {
}; };
networking = { networking = {
useDHCP = false;
interfaces = { interfaces = {
intif0 = { intif0 = {
ipv4.addresses = [{ ipv4.addresses = [{
@ -196,10 +252,6 @@ in {
address = primary-ip; address = primary-ip;
prefixLength = 28; prefixLength = 28;
} }
{
address = git-server-ip;
prefixLength = 32;
}
]; ];
}; };
}; };
@ -218,6 +270,22 @@ in {
enableACME = true; enableACME = true;
locations."/".return = "301 https://webmail.fudo.org$request_uri"; locations."/".return = "301 https://webmail.fudo.org$request_uri";
}; };
"archiva.fudo.org" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8001";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
}; };
}; };
}; };

View File

@ -11,8 +11,6 @@ let
concatGenAttrs = lst: f: concatGenAttrs = lst: f:
foldr (a0: a1: a0 // a1) {} (map f lst); foldr (a0: a1: a0 // a1) {} (map f lst);
passwd = import ../../../lib/passwd.nix { inherit lib; };
secrets = config.fudo.secrets.host-secrets.${hostname}; secrets = config.fudo.secrets.host-secrets.${hostname};
cfg = config.fudo.france; cfg = config.fudo.france;
@ -69,45 +67,47 @@ in {
user = config.fudo.auth.kdc.user; user = config.fudo.auth.kdc.user;
}; };
auth = { # auth = {
ldap-server = { # ldap-server = {
enable = true; # enable = true;
base = "dc=fudo,dc=org"; # base = "dc=fudo,dc=org";
organization = "Fudo"; # organization = "Fudo";
rootpw-file = cfg.ldap.root-password-file; # rootpw-file = cfg.ldap.root-password-file;
kerberos-host = fqdn; # kerberos-host = fqdn;
kerberos-keytab = cfg.ldap.keytab; # kerberos-keytab = cfg.ldap.keytab;
ssl-certificate = cfg.ldap.ssl-certificate; # ssl-certificate = cfg.ldap.ssl-certificate;
ssl-private-key = cfg.ldap.ssl-private-key; # ssl-private-key = cfg.ldap.ssl-private-key;
ssl-ca-certificate = cfg.ldap.ssl-ca-certificate; # ssl-ca-certificate = cfg.ldap.ssl-ca-certificate;
listen-uris = [ "ldap:///" "ldaps:///" "ldapi:///" ]; # listen-uris = [ "ldap:///" "ldaps:///" "ldapi:///" ];
users = config.fudo.users; # users = config.fudo.users;
groups = config.fudo.groups; # groups = config.fudo.groups;
system-users = config.fudo.system-users; # system-users = config.fudo.system-users;
};
# TODO: let build hosts create keys? # database-directory = "/state/openldap";
kdc = { # };
enable = true;
realm = config.fudo.domains.${domain-name}.gssapi-realm; # # TODO: let build hosts create keys?
state-directory = cfg.kdc.state-directory; # kdc = {
master-key-file = secrets.kdc-master-key.target-file; # enable = true;
acl = let # realm = config.fudo.domains.${domain-name}.gssapi-realm;
admin-entries = concatGenAttrs # state-directory = cfg.kdc.state-directory;
config.instance.local-admins # master-key-file = secrets.kdc-master-key.target-file;
(admin: { # acl = let
"${admin}" = { perms = [ "add" "list" "change-password" ]; }; # admin-entries = concatGenAttrs
"${admin}/root" = { perms = [ "all" ]; }; # config.instance.local-admins
}); # (admin: {
in { # "${admin}" = { perms = [ "add" "list" "change-password" ]; };
"host/*.fudo.org" = { perms = [ "add" ]; }; # "${admin}/root" = { perms = [ "all" ]; };
"pam_migrate/*.fudo.org" = { perms = [ "add" "change-password" ]; }; # });
} // admin-entries; # in {
bind-addresses = cfg.kdc.listen-ips; # "host/*.fudo.org" = { perms = [ "add" ]; };
}; # "pam_migrate/*.fudo.org" = { perms = [ "add" "change-password" ]; };
}; # } // admin-entries;
# bind-addresses = cfg.kdc.listen-ips;
# };
# };o
}; };
}; };
} }

View File

@ -10,14 +10,12 @@ let
backplane-dns-user = "backplane-dns"; backplane-dns-user = "backplane-dns";
generate-role-passwd = role: generate-role-passwd = role:
lib.fudo.passwd.stablerandom-password-file pkgs.lib.fudo.passwd.stablerandom-passwd-file
"backplane-${role}-password" "backplane-${role}-password"
"${hostname}-${domain}-${role}-password-${config.instance.build-timestamp}"; "${hostname}-${domain}-${role}-password-${config.instance.build-seed}";
powerdns-password = generate-role-passwd "powerdns-db"; powerdns-password = generate-role-passwd "powerdns-db";
backplane-dns-xmpp-password = generate-role-passwd "backplane-dns-xmpp";
backplane-dns-db-password = generate-role-passwd "backplane-dns-db"; backplane-dns-db-password = generate-role-passwd "backplane-dns-db";
secrets = config.fudo.secrets.host-secrets.france; secrets = config.fudo.secrets.host-secrets.france;
@ -36,6 +34,11 @@ in {
description = "List of IPv6s on which to listen for incoming backplane connections."; description = "List of IPv6s on which to listen for incoming backplane connections.";
default = []; default = [];
}; };
backplane-dns-password-file = mkOption {
type = str;
description = "Path to file containing the password for connecting to the XMPP backplane.";
};
}; };
config = { config = {
@ -64,19 +67,13 @@ in {
powerdns-password = { powerdns-password = {
source-file = powerdns-password; source-file = powerdns-password;
target-file = "/run/backplane/dns/powerdns/db.passwd"; target-file = "/run/backplane/dns/powerdns/db.passwd";
user = config.fudo.backplane.dns.database.user; user = config.fudo.backplane.dns.powerdns.user;
}; };
backplane-dns-db-password = { backplane-dns-db-password = {
source-file = backplane-dns-db-password; source-file = backplane-dns-db-password;
target-file = "/run/backplane/dns/db.passwd"; target-file = "/run/backplane/dns/db.passwd";
user = config.fudo.backplane.dns.backplane.user; user = config.fudo.backplane.dns.user;
};
backplane-dns-xmpp-password = {
source-file = backplane-dns-db-password;
target-file = "/run/backplane/dns/xmpp.passwd";
user = config.fudo.backplane.dns.backplane.user;
}; };
}; };
@ -98,7 +95,7 @@ in {
}; };
}; };
${backplane-dns-user} = { ${backplane-dns-user} = {
password-file = secrets.backplane-dns-db-password; password-file = secrets.backplane-dns-db-password.target-file;
databases = { databases = {
backplane_dns = { backplane_dns = {
access = "CONNECT"; access = "CONNECT";
@ -134,7 +131,7 @@ in {
backplane = { backplane = {
host = "backplane.fudo.org"; host = "backplane.fudo.org";
role = "service-dns"; role = "service-dns";
password-file = secrets.backplane-dns-xmpp-password.target-file; password-file = cfg.backplane-dns-password-file;
database = { database = {
username = backplane-dns-user; username = backplane-dns-user;
database = backplane-dns-user; database = backplane-dns-user;

View File

@ -0,0 +1,98 @@
{ config, lib, pkgs, ... }:
with lib;
{
options.fudo.france.chat = with types; {
chat-hostname = mkOption {
type = str;
description = "Hostname of the chat server.";
};
mail-server = mkOption {
type = str;
description = "Email server to use for communication.";
};
database-host = mkOption {
type = str;
description = "Hostname of the database server.";
};
};
config = let
hostname = config.instance.hostname;
cfg = config.fudo.france.chat;
secrets = config.fudo.secrets.host-secrets.${hostname};
in {
fudo = {
secrets.host-secrets.${hostname} = {
mattermost-mail-password = {
source-file = pkgs.lib.fudo.passwd.stablerandom-passwd-file
"mattermost-mail-password"
"${hostname}-mattermost-mail-password-${config.instance.build-seed}";
target-file = "/run/chat/mattermost/mail.passwd";
user = config.services.mattermost.user;
};
mattermost-db-password = {
source-file = pkgs.lib.fudo.passwd.stablerandom-passwd-file
"mattermost-db-password"
"${hostname}-mattermost-db-password-${config.instance.build-seed}";
target-file = "/run/chat/mattermost/database.passwd";
user = config.services.mattermost.user;
};
};
users.fudo-chat = {
uid = 20001;
primary-group = "fudo";
common-name = "Fudo Chat";
ldap-hashed-passwd =
pkgs.lib.fudo.passwd.hash-ldap-passwd "mattermost-chat"
secrets.mattermost-mail-password.source-file;
};
postgresql = {
databases.mattermost.users =
config.instance.local-admins;
users.mattermost = {
password-file =
secrets.mattermost-db-password.target-file;
databases = {
mattermost = {
access = "CONNECT";
entity-access = {
"ALL TABLES IN SCHEMA public" =
"SELECT,INSERT,UPDATE,DELETE";
"ALL SEQUENCES IN SCHEMA public" =
"SELECT,UPDATE";
};
};
};
};
};
chat = {
enable = true;
hostname = cfg.chat-hostname;
site-name = "Fudo Chat";
smtp = {
server = cfg.mail-server;
user = "fudo-chat";
password-file = secrets.mattermost-mail-password.target-file;
};
database = {
name = "mattermost";
hostname = cfg.database-host;
user = "mattermost";
password-file = secrets.mattermost-db-password.target-file;
};
};
};
};
}

View File

@ -0,0 +1,88 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
cfg = config.fudo.france.dns;
in {
options.fudo.france.dns = with types; {
default-host = mkOption {
type = str;
description = "IP address to which the domain will map.";
};
listen-ip = mkOption {
type = str;
description = "IP addresses on which to listen";
};
listen-ipv6 = mkOption {
type = nullOr str;
description = "IPv6 addresses on which to listen";
default = null;
};
mail-hosts = mkOption {
type = listOf str;
description = "List of mail hosts for the MX records.";
};
};
config = let
dom = config.instance.local-domain;
dom-cfg = config.fudo.domains.${dom};
in {
fudo = {
mail-server.alias-users.dmarc-report =
map (admin: "${admin}@${dom}") dom-cfg.local-admins;
dns = {
enable = true;
identity = "${hostname}.fudo.org";
listen-ips =
[ cfg.listen-ip ] ++
(optional (cfg.listen-ipv6 != null) cfg.listen-ipv6);
nameservers = {
ns1 = {
ipv4-address = cfg.listen-ip;
ipv6-address = mkIf (cfg.listen-ipv6 != null) cfg.listen-ipv6;
description = "Nameserver 1, france, in Winnipeg, MB, CA";
};
ns2 = {
ipv4-address = "209.117.102.102";
ipv6-address = "2001:470:1f16:40::2";
description = "Nameserver 2, musashi, in Winnipeg, MB, CA";
};
ns3 = {
ipv4-address = "104.131.53.95";
ipv6-address = "2604:a880:800:10::8:7001";
description =
"Nameserver 3, ns2.henchmman21.net, in New York City, NY, US";
};
ns4 = {
ipv4-address = "204.42.254.5";
ipv6-address = "2001:418:3f4::5";
description = "Nameserver 4, puck.nether.net, in Chicago, IL, US";
};
};
domains = let
in {
${dom} = {
dnssec = true;
default-host = cfg.default-host;
gssapi-realm = dom-cfg.gssapi-realm;
mx = cfg.mail-hosts;
dmarc-report-address = "dmarc-report@${dom}";
network-definition = import ../../networks/fudo.org.nix;
};
};
};
};
};
}

View File

@ -46,7 +46,7 @@ in {
config.fudo = { config.fudo = {
secrets.host-secrets.${hostname}.git-database-password = { secrets.host-secrets.${hostname}.git-database-password = {
source-file = lib.fudo.passwd.stablerandom-passwd-file source-file = pkgs.lib.fudo.passwd.stablerandom-passwd-file
"gitea-database-passwd" "gitea-database-passwd"
"${hostname}-gitea-database-passwd-${config.instance.build-seed}"; "${hostname}-gitea-database-passwd-${config.instance.build-seed}";
target-file = "/var/gitea/database.passwd"; target-file = "/var/gitea/database.passwd";
@ -55,7 +55,7 @@ in {
postgresql = { postgresql = {
databases.fudo_git.users = databases.fudo_git.users =
config.instance.local_admins; config.instance.local-admins;
users.fudo_git = { users.fudo_git = {
password-file = password-file =

View File

@ -5,23 +5,23 @@ let
hostname = config.instance.hostname; hostname = config.instance.hostname;
secrets = config.fudo.secrets.host-secrets.${hostname}; secrets = config.fudo.secrets.host-secrets.${hostname};
cfg = config.fudo.france; cfg = config.fudo.france.jabber;
generate-auth-file = name: files: let generate-auth-file = name: files: let
make-entry = name: passwd-file: make-entry = name: passwd-file:
''("${name}" . "${readFile passwd-file}")''; ''("${name}" . "${readFile passwd-file}")'';
entries = mapAttrsToList make-entry files; entries = mapAttrsToList make-entry files;
content = concatStringsSep "\n" entries; content = concatStringsSep "\n" entries;
in writeText "${name}-backplane-auth.scm" "'(${content})'"; in pkgs.writeText "${name}-backplane-auth.scm" "'(${content})";
host-auth-file = generate-auth-file "host" cfg.host-passwd-files; host-auth-file = generate-auth-file "host" cfg.backplane.host-passwd-files;
service-auth-file = generate-auth-filre "service" cfg.service-passwd-files; service-auth-file = generate-auth-file "service" cfg.backplane.service-passwd-files;
ldap-password-file = ldap-password-file =
lib.fudo.passwd.random-passwd-file "ejabberd-ldap-auth-user"; pkgs.lib.fudo.passwd.random-passwd-file "ejabberd-ldap-auth-user" 30;
ldap-hashed-password = ldap-hashed-password =
hash-ldap-passwd "ejabberd-ldap-hashed-passwd" ldap-password-file; pkgs.lib.fudo.passwd.hash-ldap-passwd "ejabberd-ldap-hashed-passwd" ldap-password-file;
in { in {
options.fudo.france = with types; { options.fudo.france = with types; {
@ -41,28 +41,28 @@ in {
type = listOf str; type = listOf str;
description = "IPs on which to listen for incoming connections."; description = "IPs on which to listen for incoming connections.";
}; };
};
backplane = { backplane = {
host-passwd-files = mkOption { host-passwd-files = mkOption {
type = attrsOf str; type = attrsOf str;
description = "Map of hostname to password file, for backplane host authentication."; description = "Map of hostname to password file, for backplane host authentication.";
default = {}; default = {};
}; };
service-passwd-files = mkOption { service-passwd-files = mkOption {
type = attrsOf str; type = attrsOf str;
description = "Map of service to password file, for backplane service authentication."; description = "Map of service to password file, for backplane service authentication.";
default = {}; default = {};
};
}; };
}; };
}; };
config = { config = {
fudo = { fudo = {
system-users.${cfg.jabber.ldap-user} = { system-users.${cfg.ldap-user} = {
description = "ejabberd authentication user."; description = "ejabberd authentication user.";
hashed-password = ldap-hashed-password; ldap-hashed-password = ldap-hashed-password;
}; };
secrets.host-secrets.${hostname} = let secrets.host-secrets.${hostname} = let
@ -88,7 +88,7 @@ in {
jabber = { jabber = {
enable = true; enable = true;
listen-ips = cfg.jabber.listen-ips; listen-ips = cfg.listen-ips;
environment = { environment = {
FUDO_HOST_PASSWD_FILE = secrets.host-auth.target-file; FUDO_HOST_PASSWD_FILE = secrets.host-auth.target-file;
@ -103,9 +103,9 @@ in {
"fudo.im" = { "fudo.im" = {
site-config = { site-config = {
auth_method = "ldap"; auth_method = "ldap";
ldap_servers = cfg.jabber.ldap-servers; ldap_servers = cfg.ldap-servers;
ldap_port = 389; ldap_port = 389;
ldap_rootdn = "cn=${cfg.jabber.ldap-user},dc=fudo,dc=org"; ldap_rootdn = "cn=${cfg.ldap-user},dc=fudo,dc=org";
ldap_password = ''"LDAP_PASSWD"''; ldap_password = ''"LDAP_PASSWD"'';
ldap_base = "ou=members,dc=fudo,dc=org"; ldap_base = "ou=members,dc=fudo,dc=org";
ldap_filter = "(objectClass=posixAccount)"; ldap_filter = "(objectClass=posixAccount)";

View File

@ -5,6 +5,8 @@ let
hostname = config.instance.hostname; hostname = config.instance.hostname;
domain-name = config.instance.local-domain; domain-name = config.instance.local-domain;
cfg = config.fudo.france.mail;
secrets = config.fudo.secrets.host-secrets.${hostname}; secrets = config.fudo.secrets.host-secrets.${hostname};
mail-reader-dn = "mail-auth-reader"; mail-reader-dn = "mail-auth-reader";
@ -26,35 +28,63 @@ in {
}; };
}; };
config.fudo = { config.fudo = let
system-users = { mail-reader-password =
username = mail-reader-dn; pkgs.lib.fudo.passwd.random-passwd-file "${mail-reader-dn}-ldap-password" 30;
in {
# This is used at build time...
# secrets.host-secrets.${hostname}.mail-reader-passwd = {
# source-file = ldap-password;
# target-file = "/run/mail/${mail-reader-dn}-ldap.passwd";
# user = config.services.dovecot2.user;
# };
system-users.${mail-reader-dn} = {
description = "Used by the mail server to connect to LDAP for auth."; description = "Used by the mail server to connect to LDAP for auth.";
ldap-hashed-password = ldap-hashed-password =
pkgs.lib.fudo.passwd.hash-ldap-passwd pkgs.lib.fudo.passwd.hash-ldap-passwd
secrets.mail-reader-passwd.target-file; "${mail-reader-dn}-hashed"
mail-reader-password;
}; };
mail-server = { mail-server = let
mail-hostname = "mail.${domain-name}";
mail-ssl-dir = config.security.acme.certs.${mail-hostname}.directory;
ssl-certificate = "${mail-ssl-dir}/cert.pem";
ssl-private-key = "${mail-ssl-dir}/key.pem";
in {
enableContainer = true; enableContainer = true;
monitoring = true; monitoring = true;
domain = domain-name; domain = domain-name;
mail-hostname = "mail.${domain-name}"; mail-hostname = "mail.${domain-name}";
trusted-networks = config.instance.local-networks;
dovecot = { dovecot = {
ldap = { ldap = {
reader-dn = "cn=${mail-reader-dn},${config.fudo.auth.ldap.base}"; reader-dn = "cn=${mail-reader-dn},${config.fudo.authentication.base}";
reader-password-file = secrets.mail-reader-passwd.target-file; reader-password-file = mail-reader-password;
server-urls = cfg.ldap-server-urls; server-urls = cfg.ldap-server-urls;
}; };
}; };
user-aliases = let
aliased-users = filterAttrs
(username: userOpts: length userOpts.email-aliases > 0)
config.fudo.users;
in mapAttrs (username: userOpts: userOpts.email-aliases) aliased-users;
state-directory = cfg.state-directory; state-directory = cfg.state-directory;
mail-directory = cfg.mail-directory; mail-directory = cfg.mail-directory;
clamav.enable = true; clamav.enable = true;
dkim.signing = true; dkim.signing = true;
ssl = {
certificate = ssl-certificate;
private-key = ssl-private-key;
};
}; };
}; };
} }

View File

@ -4,6 +4,7 @@ with lib;
let let
hostname = config.instance.hostname; hostname = config.instance.hostname;
secrets = config.fudo.secrets.host-secrets.${hostname}; secrets = config.fudo.secrets.host-secrets.${hostname};
cfg = config.fudo.france.postgresql;
in { in {
options.fudo.france.postgresql = with types; { options.fudo.france.postgresql = with types; {
ssl-certificate = mkOption { ssl-certificate = mkOption {

View File

@ -8,11 +8,10 @@ let
secrets = config.fudo.secrets.host-secrets.${hostname}; secrets = config.fudo.secrets.host-secrets.${hostname};
static = config.fudo.static; # TODO: what should go here?
static = ../../../static;
mail-hostname = config.france.webmail.mail-server; cfg = config.fudo.france.webmail;
db-host = config.france.webmail.database.hostname;
db-passwd = pkgs.lib.fudo.passwd.random-passwd-file "webmail" 40; db-passwd = pkgs.lib.fudo.passwd.random-passwd-file "webmail" 40;
@ -39,12 +38,12 @@ in {
"webmail.fudo.link" = { "webmail.fudo.link" = {
title = "Fudo Link Webmail"; title = "Fudo Link Webmail";
favicon = "${static}/fudo.link/favicon.ico"; favicon = "${static}/fudo.link/favicon.ico";
mail-server = mail-hostname; mail-server = cfg.mail-server;
domain = "fudo.link"; domain = "fudo.link";
edit-mode = "Plain"; edit-mode = "Plain";
layout-mode = "bottom"; layout-mode = "bottom";
database = { database = {
hostname = db-host; hostname = cfg.database.hostname;
password-file = db-passwd; password-file = db-passwd;
}; };
}; };
@ -52,11 +51,11 @@ in {
"webmail.test.fudo.org" = { "webmail.test.fudo.org" = {
title = "Fudo Webmail"; title = "Fudo Webmail";
favicon = "${static}/fudo.org/favicon.ico"; favicon = "${static}/fudo.org/favicon.ico";
mail-server = mail-hostname; mail-server = cfg.mail-server;
domain = "fudo.org"; domain = "fudo.org";
edit-mode = "Plain"; edit-mode = "Plain";
database = { database = {
hostname = db-host; hostname = cfg.database.hostname;
password-file = db-passwd; password-file = db-passwd;
}; };
}; };
@ -64,11 +63,11 @@ in {
"webmail.fudo.org" = { "webmail.fudo.org" = {
title = "Fudo Webmail"; title = "Fudo Webmail";
favicon = "${static}/fudo.org/favicon.ico"; favicon = "${static}/fudo.org/favicon.ico";
mail-server = mail-hostname; mail-server = cfg.mail-server;
domain = "fudo.org"; domain = "fudo.org";
edit-mode = "Plain"; edit-mode = "Plain";
database = { database = {
hostname = db-host; hostname = cfg.database.hostname;
password-file = db-passwd; password-file = db-passwd;
}; };
}; };
@ -76,10 +75,10 @@ in {
"webmail.test.selby.ca" = { "webmail.test.selby.ca" = {
title = "Selby Webmail"; title = "Selby Webmail";
favicon = "${static}/selby.ca/favicon.ico"; favicon = "${static}/selby.ca/favicon.ico";
mail-server = mail-hostname; mail-server = cfg.mail-server;
domain = "selby.ca"; domain = "selby.ca";
database = { database = {
hostname = db-host; hostname = cfg.database.hostname;
password-file = db-passwd; password-file = db-passwd;
}; };
}; };
@ -87,10 +86,10 @@ in {
"webmail.selby.ca" = { "webmail.selby.ca" = {
title = "Selby Webmail"; title = "Selby Webmail";
favicon = "${static}/selby.ca/favicon.ico"; favicon = "${static}/selby.ca/favicon.ico";
mail-server = mail-hostname; mail-server = cfg.mail-server;
domain = "selby.ca"; domain = "selby.ca";
database = { database = {
hostname = db-host; hostname = cfg.database.hostname;
password-file = db-passwd; password-file = db-passwd;
}; };
}; };

View File

@ -0,0 +1,233 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = "legatus";
host-ipv4 = "91.229.23.204";
domain-name = config.fudo.hosts.${hostname}.domain;
domain = config.fudo.domains.${domain-name};
site-name = config.fudo.hosts.${hostname}.site;
site = config.fudo.sites.${site-name};
host-fqdn = "${hostname}.${domain-name}";
local-packages = with pkgs; [ ldns.examples ];
secrets = config.fudo.secrets.host-secrets.${hostname};
in {
networking = {
enableIPv6 = true;
nameservers = [ "1.1.1.1" ];
defaultGateway = {
address = site.gateway-v4;
interface = "extif0";
};
interfaces.extif0.ipv4.addresses = [{
address = host-ipv4;
prefixLength = 24;
}];
};
systemd.tmpfiles.rules = [
"L /etc/adjtime - - - - /state/etc/adjtime"
];
environment.systemPackages = local-packages;
# networking.firewall.allowedTCPPorts = [ 80 443 ];
# informis.cl-gemini = {
# enable = true;
# hostname = "gemini.informis.land";
# server-ip = host-ipv4;
# document-root = "/srv/gemini/root";
# textfiles-archive = "${pkgs.textfiles}";
# slynk-port = 4005;
# feeds = {
# viator = {
# title = "viator's phlog";
# path = "/home/viator/gemini-public/feed/";
# url = "gemini://informis.land/user/viator/feed/";
# };
# };
# };
fudo = {
hosts.legatus.external-interfaces = [ "extif0" ];
# secrets.host-secrets.procul = let
# files = config.fudo.secrets.files;
# in {
# postgres-keytab = {
# source-file = files.service-keytabs.procul.postgres;
# target-file = "/srv/postgres/secure/postgres.keytab";
# user = "root";
# };
# gitea-database-password = {
# source-file = files.service-passwords.procul.gitea-database;
# target-file = "/srv/gitea/secure/database.passwd";
# user = config.fudo.git.user;
# };
# };
# client.dns = {
# enable = true;
# ipv4 = true;
# ipv6 = true;
# user = "fudo-client";
# external-interface = "extif0";
# };
# auth.kdc = {
# enable = true;
# realm = "INFORMIS.LAND";
# bind-addresses = [ host-ipv4 "127.0.0.1" ];
# acl = {
# "niten" = { perms = [ "add" "change-password" "list" ]; };
# "*/root" = { perms = [ "all" ]; };
# };
# };
# secure-dns-proxy = {
# enable = true;
# upstream-dns =
# [ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" ];
# bootstrap-dns = "1.1.1.1";
# listen-ips = [ "127.0.0.1" ];
# listen-port = 53;
# allowed-networks = [ "1.1.1.1/32" "1.0.0.1/32" "localhost" "link-local" ];
# };
# dns = {
# enable = true;
# identity = "procul.informis.land";
# nameservers = {
# ns1 = {
# ipv4-address = host-ipv4;
# description = "Primary Informis Nameserver";
# };
# ns2 = {
# ipv4-address = host-ipv4;
# description = "Secondary Informis Nameserver";
# };
# };
# listen-ips = [ host-ipv4 ];
# domains = {
# "informis.land" = {
# dnssec = true;
# default-host = host-ipv4;
# gssapi-realm = "INFORMIS.LAND";
# mx = [ "smtp.informis.land" ];
# network-definition = config.fudo.networks."informis.land";
# dmarc-report-address = "dmarc-report@informis.land";
# };
# };
# };
# mail-server = {
# enable = true;
# debug = true;
# domain = domain-name;
# mail-hostname = "${host-fqdn}";
# monitoring = false;
# mail-user = "mailuser";
# mail-user-id = 525;
# mail-group = "mailgroup";
# clamav.enable = true;
# dkim.signing = true;
# dovecot = {
# ssl-certificate = acme-certificate "imap.${domain-name}";
# ssl-private-key = acme-private-key "imap.${domain-name}";
# };
# postfix = {
# ssl-certificate = acme-certificate "smtp.${domain-name}";
# ssl-private-key = acme-private-key "smtp.${domain-name}";
# };
# # This should NOT include the primary domain
# local-domains = [ host-fqdn "smtp.${domain-name}" ];
# mail-directory = "/srv/mailserver/mail";
# state-directory = "/srv/mailserver/state";
# trusted-networks = [ "172.86.179.16/29" "127.0.0.0/16" ];
# alias-users = {
# root = [ "niten" ];
# postmaster = [ "niten" ];
# hostmaster = [ "niten" ];
# webmaster = [ "niten" ];
# system = [ "niten" ];
# admin = [ "niten" ];
# dmarc-report = [ "niten" ];
# };
# };
# postgresql = {
# enable = true;
# ssl-certificate = (acme-certificate host-fqdn);
# ssl-private-key = (acme-private-key host-fqdn);
# keytab = secrets.postgres-keytab.target-file;
# local-networks = local-networks;
# users = {
# gituser = {
# password-file =
# secrets.gitea-database-password.target-file;
# databases = {
# git = {
# access = "CONNECT";
# entity-access = {
# "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
# "ALL SEQUENCES IN SCHEMA public" = "SELECT, UPDATE";
# };
# };
# };
# };
# };
# databases = { git = { users = [ "niten" ]; }; };
# };
# git = {
# enable = true;
# hostname = "git.informis.land";
# site-name = "informis git";
# user = "gituser";
# repository-dir = /srv/git/repo;
# state-dir = /srv/git/state;
# database = {
# user = "gituser";
# password-file =
# secrets.gitea-database-password.target-file;
# hostname = "127.0.0.1";
# name = "git";
# };
# ssh = {
# listen-ip = host-ipv4;
# listen-port = 2222;
# };
# };
# acme = {
# enable = true;
# admin-address = "admin@${domain-name}";
# hostnames = [
# "informis.land"
# "imap.informis.land"
# "smtp.informis.land"
# "gemini.informis.land"
# ];
# };
};
}

View File

@ -0,0 +1,5 @@
{ config, lib, pkgs, ... }:
{
}

View File

@ -50,11 +50,12 @@ in {
]; ];
}; };
fudo.ipfs = { ## Until I can figure out how to use one common host API, forget this
enable = true; # fudo.ipfs = {
users = [ "niten" ]; # enable = true;
api-address = "/ip4/0.0.0.0/tcp/5001"; # users = [ "niten" ];
}; # api-address = "/ip4/0.0.0.0/tcp/5001";
# };
virtualisation = { virtualisation = {
libvirtd = { libvirtd = {

View File

@ -164,7 +164,7 @@ in {
debug = true; debug = true;
domain = domain-name; domain = domain-name;
hostname = "${host-fqdn}"; mail-hostname = "${host-fqdn}";
monitoring = false; monitoring = false;
mail-user = "mailuser"; mail-user = "mailuser";
mail-user-id = 525; mail-user-id = 525;

25
config/hosts/legatus.nix Normal file
View File

@ -0,0 +1,25 @@
{
description = "informis.land server.";
rp = "niten";
admin-email = "niten@fudo.org";
domain = "eur.fudo.org";
site = "worldstream";
profile = "server";
tmp-on-tmpfs = false;
enable-gui = false;
arch = "x86_64-linux";
nixos-system = true;
machine-id = "749bbf411088411b8784b76bb44bd617";
master-key = {
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqUnzf8bfPyoJX6XjFqD6v5MZQnV8STP0152VS3uwM7";
key-path = "/state/master-key/ed25519_key";
};
# initrd-network = {
# ip = "172.86.179.18";
# interface = "enp0s25";
# keypair = {
# public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIgvl/pxPGN5XuUFsEywHV/PJMI+wPHA6NKTtE8SZC04";
# private-key-file = "/state/ssh/initrd/ssh_ed25519_key";
# };
# };
}

View File

@ -0,0 +1,11 @@
{
description = "Fudo mailserver container";
rp = "admin";
admin-email = "admin@fudo.org";
domain = "fudo.org";
site = "portage";
profile = "container";
arch = "x86_64-linux";
machine-id = "5a907f8cf2644b0ba5a786fd45b758b3";
nixos-system = false;
}

View File

@ -1,8 +1,8 @@
{ {
description = "informis.land server."; description = "informis.land server.";
docker-server = true; docker-server = true;
rp = "niten"; rp = "viator";
admin-email = "niten@fudo.org"; admin-email = "viator@fudo.org";
domain = "informis.land"; domain = "informis.land";
site = "joes-datacenter-0"; site = "joes-datacenter-0";
profile = "server"; profile = "server";

View File

@ -2,8 +2,10 @@
{ {
config.fudo.networks = { config.fudo.networks = {
"fudo.org" = import ./networks/fudo.org.nix;
"rus.selby.ca" = import ./networks/rus.selby.ca.nix; "rus.selby.ca" = import ./networks/rus.selby.ca.nix;
"sea.fudo.org" = import ./networks/sea.fudo.org.nix; "sea.fudo.org" = import ./networks/sea.fudo.org.nix;
"informis.land" = import ./networks/informis.land.nix; "informis.land" = import ./networks/informis.land.nix;
"eur.fudo.org" = import ./networks/eur.fudo.org.nix;
}; };
} }

View File

@ -0,0 +1,7 @@
{
mx = [ "mail.fudo.org" ];
hosts = {
legatus.ipv4-address = "91.229.23.204";
};
}

View File

@ -1,8 +1,4 @@
{ {
mx = [ "mail.fudo.org" ];
default-host = "208.81.3.117";
aliases = { aliases = {
pop = "mail.fudo.org."; pop = "mail.fudo.org.";
smtp = "mail.fudo.org."; smtp = "mail.fudo.org.";
@ -27,13 +23,11 @@
wiki = "hanover.fudo.org."; wiki = "hanover.fudo.org.";
}; };
extra-dns-records = [ verbatim-dns-records = [
''@ IN TXT "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"'' ''@ IN TXT "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"''
''@ IN SPF "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"'' ''@ IN SPF "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"''
]; ];
dmarc-report-address = "dmarc-report@fudo.org";
srv-records = { srv-records = {
tcp = { tcp = {
domain = [ domain = [

View File

@ -0,0 +1,10 @@
{ config, lib, pkgs, ... }:
with lib;
let
has-secret-files = hasAttr "files" config.fudo.secrets;
in {
config.instance = mkIf has-secret-files {
build-seed = builtins.readFile config.fudo.secrets.files.build-seed;
};
}

View File

@ -14,131 +14,154 @@ let
wget wget
]; ];
import-paths = [
./build
./host
./user
];
in { in {
environment = {
etc.nixos-live.source = ../../.;
systemPackages = global-packages; imports = let
is-regular-file = filename: type: type == "regular" || type == "link";
regular-files = path:
attrNames (filterAttrs is-regular-file (builtins.readDir path));
is-nix-file = filename: (builtins.match "^(.+)\.nix$" filename) != null;
nix-files = path:
map
(file: path + "/${file}")
(filter is-nix-file (regular-files path));
in concatMap nix-files import-paths;
# shellInit = '' config = {
# ${pkgs.gnupg}/bin/gpg-connect-agent /bye environment = {
# export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket) etc.nixos-live.source = ../../.;
# '';
};
system.autoUpgrade.enable = false; systemPackages = global-packages;
nix = { # shellInit = ''
package = pkgs.nixFlakes; # ${pkgs.gnupg}/bin/gpg-connect-agent /bye
extraOptions = '' # export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)
# '';
};
system.autoUpgrade.enable = false;
nix = {
package = pkgs.nixFlakes;
extraOptions = ''
experimental-features = nix-command flakes experimental-features = nix-command flakes
''; '';
};
nixpkgs.config.allowUnfree = true;
security.acme.acceptTerms = true;
hardware.enableRedistributableFirmware = true;
krb5 = {
enable = true;
appdefaults = {
forwardable = true;
proxiable = true;
encrypt = true;
forward = true;
}; };
libdefaults = { nixpkgs.config.allowUnfree = true;
allow_weak_crypto = true; security.acme.acceptTerms = true;
dns_lookup_kdc = true; hardware.enableRedistributableFirmware = true;
dns_lookup_realm = true;
forwardable = true;
proxiable = true;
};
kerberos = pkgs.heimdalFull; krb5 = {
};
services = {
openssh = {
enable = true; enable = true;
startWhenNeeded = true;
useDns = true; appdefaults = {
permitRootLogin = "prohibit-password"; forwardable = true;
extraConfig = '' proxiable = true;
encrypt = true;
forward = true;
};
libdefaults = {
allow_weak_crypto = true;
dns_lookup_kdc = true;
dns_lookup_realm = true;
forwardable = true;
proxiable = true;
};
kerberos = pkgs.heimdalFull;
};
services = {
openssh = {
enable = true;
startWhenNeeded = true;
useDns = true;
permitRootLogin = "prohibit-password";
extraConfig = ''
GSSAPIAuthentication yes GSSAPIAuthentication yes
GSSAPICleanupCredentials yes GSSAPICleanupCredentials yes
GSSAPIKeyExchange yes GSSAPIKeyExchange yes
GSSAPIStoreCredentialsOnRekey yes GSSAPIStoreCredentialsOnRekey yes
''; '';
};
fail2ban = let
domain-name = config.fudo.hosts.${config.instance.hostname}.domain;
in {
enable = config.networking.firewall.enable;
bantime-increment.enable = true;
};
xserver = {
layout = "us";
xkbVariant = "dvp";
xkbOptions = "ctrl:nocaps";
};
# pcscd.enable = true;
# udev.packages = with pkgs; [ yubikey-personalization ];
}; };
fail2ban = let networking.firewall = {
domain-name = config.fudo.hosts.${config.instance.hostname}.domain; # Allow mosh connections if the firewall is enabled
in { allowedUDPPortRanges = [{
enable = config.networking.firewall.enable; from = 60000;
bantime-increment.enable = true; to = 60100;
ignoreIP = config.instance.local-networks; }];
}; };
xserver = { console.useXkbConfig = true;
layout = "us";
xkbVariant = "dvp";
xkbOptions = "ctrl:nocaps";
};
# pcscd.enable = true; i18n.defaultLocale = "en_US.UTF-8";
# udev.packages = with pkgs; [ yubikey-personalization ];
};
networking.firewall = { programs = {
# Allow mosh connections if the firewall is enabled mosh.enable = true;
allowedUDPPortRanges = [{
from = 60000;
to = 60100;
}];
};
console.useXkbConfig = true; bash.enableCompletion = true;
i18n.defaultLocale = "en_US.UTF-8"; fish.enable = true;
programs = { gnupg.agent = {
mosh.enable = true; enable = true;
# enableSSHSupport = true;
# pinentryFlavor = if cfg.enable-gui then "gnome3" else "curses";
};
bash.enableCompletion = true; ssh = {
startAgent = true;
fish.enable = true; package = pkgs.openssh_gssapi;
gnupg.agent = { extraConfig = ''
enable = true;
# enableSSHSupport = true;
# pinentryFlavor = if cfg.enable-gui then "gnome3" else "curses";
};
ssh = {
startAgent = true;
package = pkgs.openssh_gssapi;
extraConfig = ''
GSSAPIAuthentication yes GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes GSSAPIDelegateCredentials yes
''; '';
};
};
security.pam = {
enableSSHAgentAuth = true;
services = {
sshd = {
makeHomeDir = true;
sshAgentAuth = true;
# This isn't supposed to ask for a code unless ~/.google_authenticator exists...but it does
# googleAuthenticator.enable = true;
}; };
}; };
security.pam = {
enableSSHAgentAuth = true;
services = {
sshd = {
makeHomeDir = true;
sshAgentAuth = true;
# This isn't supposed to ask for a code unless ~/.google_authenticator exists...but it does
# googleAuthenticator.enable = true;
};
};
};
home-manager = {
useGlobalPkgs = true;
};
}; };
} }

View File

@ -0,0 +1,7 @@
{ config, lib, pkgs, ... }:
{
config = {
};
}

View File

@ -0,0 +1,21 @@
{ config, lib, pkgs, ... }:
let
hostname = config.instance.hostname;
host-cfg = config.fudo.hosts.${hostname};
secrets = config.fudo.secrets.host-secrets.${hostname};
in {
config.fudo = {
secrets.host-secrets.${hostname} = {
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 =
secrets.backplane-passwd.target-file;
};
}

View File

@ -0,0 +1,21 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
has-secret-files = hasAttr "files" config.fudo.secrets;
try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;
in {
config = mkIf has-secret-files {
fudo.secrets.host-secrets.${hostname} = let
keytab-file = try-attr hostname config.fudo.secrets.files.host-keytabs;
in mkIf (keytab-file != null) {
host-keytab = {
source-file = keytab-file;
target-file = "/etc/krb5.keytab";
user = "root";
};
};
};
}

View File

@ -0,0 +1,60 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
has-attrs = set: length (attrNames set) > 0;
read-lines = filename: splitString "\n" (fileContents filename);
has-secret-files = hasAttr "files" config.fudo.secrets;
in {
config = mkIf has-secret-files
(let
host-keypairs =
if (hasAttr hostname config.fudo.secrets.files.host-ssh-keypairs) then
config.fudo.secrets.files.host-ssh-keypairs.${hostname}
else [];
in {
fudo = let
sshfp-filename = host: keypair: "ssh-${host}-${keypair.key-type}.sshfp-record";
dns-sshfp-records = host: keypair:
pkgs.stdenv.mkDerivation {
name = "${host}-sshfp-records";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ openssh ];
installPhase =
"ssh-keygen -r REMOVEME -f \"${keypair.public-key}\" | sed 's/^REMOVEME IN SSHFP //' > $out";
};
host-cfg = config.fudo.hosts.${hostname};
in {
secrets.host-secrets.${hostname} = listToAttrs
(map
(keypair: nameValuePair "host-${keypair.key-type}-private-key" {
source-file = keypair.private-key;
target-file = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
user = "root";
})
host-keypairs);
hosts = mkIf (hasAttr "files" config.fudo.secrets)
(mapAttrs (hostname: keypairs: {
ssh-pubkeys = map (keypair: keypair.public-key) keypairs;
ssh-fingerprints = concatMap (keypair:
let
fingerprint-derivation = dns-sshfp-records hostname keypair;
in read-lines "${fingerprint-derivation}") keypairs;
}) config.fudo.secrets.files.host-ssh-keypairs);
};
services.openssh.hostKeys = map (keypair: {
path = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
type = keypair.key-type;
}) host-keypairs;
});
}

View File

@ -45,7 +45,7 @@ in {
config = { config = {
environment = { environment = {
serverPackages = with pkgs; systemPackages = with pkgs;
[ emacs-nox reboot-if-necessary test-config ]; [ emacs-nox reboot-if-necessary test-config ];
}; };

View File

@ -0,0 +1,16 @@
{ config, lib, pkgs, ... }:
with lib;
let
ipfs-cfg = config.fudo.ipfs;
site-name = config.instance.site;
site = config.site.${site-name};
in {
config = {
home-manager.users = mapAttrs
(user: userOpts: {
home.sessionVariables.IPFS_PATH = ipfs-cfg.data-dir;
}) config.instance.local-users;
};
}

View File

@ -0,0 +1,52 @@
{ config, lib, pkgs, ... }:
with lib;
let
list-contains = lst: item: any (i: i == item) lst;
domain-realm = domain: domainOpts: domainOpts.gssapi-realm;
user-realms = username:
mapAttrsToList domain-realm
(filterAttrs
(domain: domainOpts:
list-contains domainOpts.local-users username)
config.fudo.domains);
admin-realms = username:
mapAttrsToList domain-realm
(filterAttrs
(domain: domainOpts:
list-contains domainOpts.local-admins username)
config.fudo.domains);
user-principals = username: let
user-princs = map (realm: "${username}@${realm}") (user-realms username);
admin-princs = map (realm: "${username}/root@${realm}") (admin-realms username);
in user-princs ++ admin-princs;
user-k5login = principals: concatStringsSep "\n" principals;
user-config = username: userOpts: {
home.file.".k5login".text =
user-k5login (user-principals username);
};
in {
config = {
home-manager.users = let
user-configs =
mapAttrs user-config config.instance.local-users;
root-config = {
root = let
domain-name = config.instance.local-domain;
realm = config.fudo.domains.${domain-name}.gssapi-realm;
principals = map (admin: "${admin}/root@${realm}")
config.instance.local-admins;
in {
home.file.".k5login".text =
user-k5login principals;
};
};
in user-configs // root-config;
};
}

View File

@ -3,16 +3,16 @@
let local-domain = "sea.fudo.org"; let local-domain = "sea.fudo.org";
in { in {
fileSystems = { fileSystems = {
"/mnt/documents" = { # "/mnt/documents" = {
device = "whitedwarf.${local-domain}:/volume1/Documents"; # device = "whitedwarf.${local-domain}:/volume1/Documents";
fsType = "nfs4"; # fsType = "nfs4";
options = [ "comment=systemd.automount" ]; # options = [ "comment=systemd.automount" ];
}; # };
"/mnt/downloads" = { # "/mnt/downloads" = {
device = "whitedwarf.${local-domain}:/volume1/Downloads"; # device = "whitedwarf.${local-domain}:/volume1/Downloads";
fsType = "nfs4"; # fsType = "nfs4";
options = [ "comment=systemd.automount" ]; # options = [ "comment=systemd.automount" ];
}; # };
"/mnt/music" = { "/mnt/music" = {
device = "doraemon.${local-domain}:/volume1/Music"; device = "doraemon.${local-domain}:/volume1/Music";
fsType = "nfs4"; fsType = "nfs4";

View File

@ -59,5 +59,17 @@
]; ];
mail-server = "mail.informis.land"; mail-server = "mail.informis.land";
}; };
worldstream = {
gateway-v4 = "91.229.23.204";
network = "91.229.23.0/24";
nameservers = [ "1.1.1.1" "2606:4700:4700::1111" ];
timezone = "Europe/Amsterdam";
deploy-pubkeys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDPwh522lvafTJYA0X2uFdP7Ws+Um1f8gZsARK1Y5nMzf6ZcWBF1jplTOKUVSOl4isMWni0Tu0TnX4zqCcgocWUVbwIwXSIRYqdiCPvVOH+/Ibc97n1/dYxk5JPMtbrsEw6/gWZxVg0qwe0J3dQWldEMiDY7iWhlrmIr7YL+Y3PUd7DOwp3PbfWfNyzTfE1kXcz5YvTeN+txFhbbXT0oS2R2wtc1vYXFZ/KbNstjqd+i8jszAq3ZkbbwL3aNR0RO4n8+GoIILGw8Ya4eP7D6+mYk608IhAoxpGyMrUch2TC2uvOK3rd/rw1hsTxf4AKjAZbrfd/FJaYru9ZeoLjD4bRGMdVp56F1m7pLvRiWRK62pV2Q/fjx+4KjHUrgyPd601eUIP0ayS/Rfuq8ijLpBJgO5/Y/6mFus/kjZIfRR9dXfLM67IMpyEzEITYrc/R2sedWf+YHxSh6eguAZ/kLzioar1nHLR7Wzgeu0tgWkD78WQGjpXGoefAz3xHeBg3Et0="
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGVez4of30f+j0cWKj5kYCKeFjyNsYvG9UbOMxF5hImD2lP5MSbFBv31gFgHjx3yCG4zQRZlpuyU5uWo0qIwe9N84/LcZcB9WrWKZXDmuof7zPFy0J+Hj+LVLDQI/mVXHNwkMhBMHpPrdwA05EYDAYCYklWT4cSByu10pHtST+olF8i+A+UQgUzgNZzdJVeiYZv6MBDTYsJWptGeDUkl2B0Es3gtbGYcCCfnyS3RC7DIXlDo3NBbAr7WaHY2MBbT+R/+jicn9E3IY3NCM5jENxqmvHy9MDsxEEYgFNm7IDwq4V1VRUWy277YsvRbmEaHb+osOA5u1VNN4z3UftOZcSZgR5C/vR71cENXoPt1YQpCzu7i38ojtvL+tDVEKT7sIovrQw8q1sszNlW2nXh8RSPiIq5TMnrV73MP0egKcr9n3tfxwi1BIkLjvfom/02BkTK9R9v+VMNhYU1YwROhORCiMIgoxUGiUvtH8u38JGr7E0hhMoAjCE5k80WPUivl0="
];
mail-server = "mail.fudo.org";
};
}; };
} }

View File

@ -27,6 +27,20 @@ in {
"niten/root@RUS.SELBY.CA" "niten/root@RUS.SELBY.CA"
]; ];
email = "niten@fudo.org"; email = "niten@fudo.org";
email-aliases = [
"ertian@fudo.org"
"peter@fudo.org"
"peter@fudo.link"
"pselby@fudo.org"
"yiliu@fudo.org"
"forum@selby.ca"
"peter@selby.ca"
# Used to create spotify accounts for Google Home & Tesla
"tesla@fudo.org"
"seattle-home@fudo.org"
];
}; };
andrew = { andrew = {
@ -99,6 +113,7 @@ in {
ldap-hashed-passwd = "{SSHA}YvtkEpqsReXcMdrzlui/ZmhIUKN42YO1"; ldap-hashed-passwd = "{SSHA}YvtkEpqsReXcMdrzlui/ZmhIUKN42YO1";
login-hashed-passwd = login-hashed-passwd =
"$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf."; "$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf.";
email-aliases = [ "kselby@selby.ca" ];
}; };
reaper = { reaper = {
@ -112,6 +127,12 @@ in {
k5login = k5login =
[ "reaper@FUDO.ORG" "reaper/root@FUDO.ORG" "reaper/admin@FUDO.ORG" ]; [ "reaper@FUDO.ORG" "reaper/root@FUDO.ORG" "reaper/admin@FUDO.ORG" ];
email = "reaper@fudo.org"; email = "reaper@fudo.org";
email-aliases = [
"cricket@fudo.org"
"jstewart@fudo.org"
"jonathan@fudo.org"
"reaper@fudo.link"
];
}; };
slickoil = { slickoil = {
@ -133,6 +154,7 @@ in {
primary-group = "fudo"; primary-group = "fudo";
common-name = "Mark Swaffer"; common-name = "Mark Swaffer";
ldap-hashed-passwd = "{MD5}C5gIsLsaKSvIPydu4uzhNg=="; ldap-hashed-passwd = "{MD5}C5gIsLsaKSvIPydu4uzhNg==";
email-aliases = [ "mark@fudo.org" ];
}; };
brian = { brian = {
@ -192,6 +214,13 @@ in {
login-hashed-passwd = login-hashed-passwd =
"$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0";
email = "xiaoxuan@fudo.org"; email = "xiaoxuan@fudo.org";
email-aliases = [
"xixi@fudo.org"
"claire@fudo.org"
"xixi@selby.ca"
"claire@selby.ca"
];
}; };
thibor = { thibor = {
@ -311,6 +340,9 @@ in {
primary-group = "selby"; primary-group = "selby";
common-name = "Vee Selby"; common-name = "Vee Selby";
ldap-hashed-passwd = "snoinuer"; ldap-hashed-passwd = "snoinuer";
email-aliases = [
"virginia@selby.ca"
];
}; };
dabar = { dabar = {

View File

@ -2,34 +2,6 @@
with lib; with lib;
let let
# localCopyOpts = { copy, ... }: let
# in {
# options = with types; {
# user = mkOption {
# type = str;
# description = "User to which this copy belongs.";
# };
# group = mkOption {
# type = nullOr str;
# description = "Group to which this copy belongs.";
# default = null;
# };
# path = mkOption {
# type = str;
# description = "Path at which to store the local copy.";
# #default = "/var/run/${toplevel.config.domain}/${copy}";
# };
# service = mkOption {
# type = str;
# description = "systemd job to copy certs.";
# default = "fudo-${toplevel.config.domain}-${copy}-certs.service";
# };
# };
# };
domainOpts = { name, ... }: let domainOpts = { name, ... }: let
domain = name; domain = name;
in { in {
@ -140,14 +112,14 @@ in {
perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500"; perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500";
copy-paths = mapAttrsToList (copy: copyOpts: copy-paths = mapAttrsToList (copy: copyOpts:
let let
dir-entry = copyOpts: file: "D '${dirOf file}' ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -"; dir-entry = copyOpts: file: "D \"${dirOf file}\" ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -";
in map (dir-entry copyOpts) [ in map (dir-entry copyOpts) [
copyOpts.certificate copyOpts.certificate
copyOpts.full-certificate copyOpts.full-certificate
copyOpts.chain copyOpts.chain
copyOpts.private-key copyOpts.private-key
]) copies; ]) copies;
in unique copy-paths; in unique (concatMap (i: unique i) copy-paths);
services = concatMapAttrs (domain: domainOpts: services = concatMapAttrs (domain: domainOpts:
mapAttrs' (copy: copyOpts: let mapAttrs' (copy: copyOpts: let
@ -169,7 +141,7 @@ in {
''; '';
remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" '' remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" ''
rm -f ${copyOpts.private-key} rm -f ${copyOpts.private-key}
rm -f ${copyOpts.chainy} rm -f ${copyOpts.chain}
rm -f ${copyOpts.full-certificate} rm -f ${copyOpts.full-certificate}
rm -f ${copyOpts.certificate} rm -f ${copyOpts.certificate}
''; '';

View File

@ -4,7 +4,7 @@ with lib;
let let
cfg = config.fudo.backplane.dns; cfg = config.fudo.backplane.dns;
powerdns-conf-dir = "${cfg.powerdns-home}/conf.d"; powerdns-conf-dir = "${cfg.powerdns.home}/conf.d";
backplaneOpts = { ... }: { backplaneOpts = { ... }: {
options = { options = {
@ -90,6 +90,7 @@ in {
type = listOf str; type = listOf str;
description = description =
"A list of services required before the DNS server can start."; "A list of services required before the DNS server can start.";
default = [ ];
}; };
user = mkOption { user = mkOption {
@ -109,16 +110,24 @@ in {
description = "Database settings for the DNS server."; 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 { backplane = mkOption {
type = submodule backplaneOpts; type = submodule backplaneOpts;
description = "Backplane Jabber settings for the DNS server."; description = "Backplane Jabber 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";
};
user = mkOption {
type = str;
description = "Username as which to run PowerDNS.";
default = "backplane-powerdns";
};
};
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -130,16 +139,16 @@ in {
createHome = true; createHome = true;
home = "/var/home/${cfg.user}"; home = "/var/home/${cfg.user}";
}; };
backplane-powerdns = { ${cfg.powerdns.user} = {
isSystemUser = true; isSystemUser = true;
home = cfg.powerdns-home; home = cfg.powerdns.home;
createHome = true; createHome = true;
}; };
}; };
groups = { groups = {
"${cfg.group}" = { members = [ cfg.user ]; }; "${cfg.group}" = { members = [ cfg.user ]; };
backplane-powerdns = { members = [ "backplane-powerdns" ]; }; ${cfg.powerdns.user} = { members = [ cfg.powerdns.user ]; };
}; };
}; };
@ -156,7 +165,7 @@ in {
preStart = '' preStart = ''
mkdir -p ${powerdns-conf-dir} mkdir -p ${powerdns-conf-dir}
chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir} chown ${cfg.powerdns.user}:${cfg.powerdns.user} ${powerdns-conf-dir}
''; '';
# This builds the config in a bash script, to avoid storing the password # This builds the config in a bash script, to avoid storing the password
@ -175,7 +184,7 @@ in {
fi fi
touch $TMPCONF touch $TMPCONF
chown backplane-powerdns:backplane-powerdns $TMPCONF chown ${cfg.powerdns.user}:${cfg.powerdns.user} $TMPCONF
chmod go-rwx $TMPCONF chmod go-rwx $TMPCONF
PASSWORD=$(cat ${cfg.database.password-file}) PASSWORD=$(cat ${cfg.database.password-file})
echo "launch+=gpgsql" >> $TMPCONF echo "launch+=gpgsql" >> $TMPCONF
@ -192,25 +201,6 @@ in {
''; '';
}; };
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 = { backplane-dns = {
description = "Fudo DNS Backplane Server"; description = "Fudo DNS Backplane Server";
restartIfChanged = true; restartIfChanged = true;
@ -220,7 +210,7 @@ in {
user = cfg.user; user = cfg.user;
group = cfg.group; group = cfg.group;
partOf = [ "backplane-dns.target" ]; partOf = [ "backplane-dns.target" ];
requires = [ "postgresql.service" ]; requires = cfg.required-services ++ [ "postgresql.service" ];
environment = { environment = {
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host;
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role;
@ -243,7 +233,30 @@ in {
backplane-dns = { backplane-dns = {
description = "Fudo DNS backplane services."; description = "Fudo DNS backplane services.";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
requries = cfg.required-services ++ [ "postgresql.service" ]; after = cfg.required-services ++ [ "postgresql.service" ];
};
};
services = {
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 ];
serviceConfig = {
ExecStart = "pdns_server --setuid=${cfg.powerdns.user} --setgid=${cfg.powerdns.user} --chroot=${cfg.powerdns.home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${pdns-config-dir}";
};
}; };
}; };
}; };

View File

@ -1,67 +1,71 @@
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
with lib; with lib;
let cfg = config.fudo.chat; let
cfg = config.fudo.chat;
mattermost-config-target = "/run/chat/mattermost/mattermost-config.json";
in { in {
options.fudo.chat = { options.fudo.chat = with types; {
enable = mkEnableOption "Enable chat server"; enable = mkEnableOption "Enable chat server";
hostname = mkOption { hostname = mkOption {
type = types.str; type = str;
description = "Hostname at which this chat server is accessible."; description = "Hostname at which this chat server is accessible.";
example = "chat.mydomain.com"; example = "chat.mydomain.com";
}; };
site-name = mkOption { site-name = mkOption {
type = types.str; type = str;
description = "The name of this chat server."; description = "The name of this chat server.";
example = "My Fancy Chat Site"; example = "My Fancy Chat Site";
}; };
smtp-server = mkOption { smtp = {
type = types.str; server = mkOption {
description = "SMTP server to use for sending notification emails."; type = str;
example = "mail.my-site.com"; description = "SMTP server to use for sending notification emails.";
}; example = "mail.my-site.com";
};
smtp-user = mkOption { user = mkOption {
type = types.str; type = str;
description = "Username with which to connect to the SMTP server."; description = "Username with which to connect to the SMTP server.";
}; };
smtp-password-file = mkOption { password-file = mkOption {
type = types.str; type = str;
description = description =
"Path to a file containing the password to use while connecting to the SMTP server."; "Path to a file containing the password to use while connecting to the SMTP server.";
};
}; };
state-directory = mkOption { state-directory = mkOption {
type = types.str; type = str;
description = "Path at which to store server state data."; description = "Path at which to store server state data.";
default = "/var/lib/mattermost"; default = "/var/lib/mattermost";
}; };
database = mkOption { database = mkOption {
type = (types.submodule { type = (submodule {
options = { options = {
name = mkOption { name = mkOption {
type = types.str; type = str;
description = "Database name."; description = "Database name.";
}; };
hostname = mkOption { hostname = mkOption {
type = types.str; type = str;
description = "Database host."; description = "Database host.";
}; };
user = mkOption { user = mkOption {
type = types.str; type = str;
description = "Database user."; description = "Database user.";
}; };
password-file = mkOption { password-file = mkOption {
type = types.str; type = str;
description = "Path to file containing database password."; description = "Path to file containing database password.";
}; };
}; };
@ -85,11 +89,11 @@ in {
TeamSettings.SiteName = cfg.site-name; TeamSettings.SiteName = cfg.site-name;
EmailSettings = { EmailSettings = {
RequireEmailVerification = true; RequireEmailVerification = true;
SMTPServer = cfg.smtp-server; SMTPServer = cfg.smtp.server;
SMTPPort = 587; SMTPPort = 587;
EnableSMTPAuth = true; EnableSMTPAuth = true;
SMTPUsername = cfg.smtp-user; SMTPUsername = cfg.smtp.user;
SMTPPassword = (fileContents cfg.smtp-password-file); SMTPPassword = "__SMTP_PASSWD__";
SendEmailNotifications = true; SendEmailNotifications = true;
ConnectionSecurity = "STARTTLS"; ConnectionSecurity = "STARTTLS";
FeedbackEmail = "chat@fudo.org"; FeedbackEmail = "chat@fudo.org";
@ -97,15 +101,26 @@ in {
}; };
EnableEmailInvitations = true; EnableEmailInvitations = true;
SqlSettings.DriverName = "postgres"; SqlSettings.DriverName = "postgres";
SqlSettings.DataSource = "postgres://${cfg.database.user}:${ SqlSettings.DataSource = "postgres://${
fileContents cfg.database.password-file cfg.database.user
}@${cfg.database.hostname}:5432/${cfg.database.name}"; }:__DATABASE_PASSWORD__@${
cfg.database.hostname
}:5432/${
cfg.database.name
}";
}; };
mattermost-config-file = mattermost-config-file-template =
pkgs.writeText "mattermost-config.json" (builtins.toJSON modified-config); pkgs.writeText "mattermost-config.json.template" (builtins.toJSON modified-config);
mattermost-user = "mattermost"; mattermost-user = "mattermost";
mattermost-group = "mattermost"; mattermost-group = "mattermost";
generate-mattermost-config = target: template: smtp-passwd-file: db-passwd-file:
pkgs.writeScript "mattermost-config-generator.sh" ''
SMTP_PASSWD=$( cat ${smtp-passwd-file} )
DATABASE_PASSWORD=$( cat ${db-passwd-file} )
sed -e 's/__SMTP_PASSWD__/"$SMTP_PASSWD"/' -e 's/__DATABASE_PASSWORD__/"$DATABASE_PASSWORD"/' ${template} > ${target}
'';
in { in {
users = { users = {
users = { users = {
@ -118,48 +133,75 @@ in {
groups = { ${mattermost-group} = { members = [ mattermost-user ]; }; }; groups = { ${mattermost-group} = { members = [ mattermost-user ]; }; };
}; };
system.activationScripts.mattermost = '' fudo.system.services.mattermost = {
mkdir -p ${cfg.state-directory}
'';
systemd.services.mattermost = {
description = "Mattermost Chat Server"; description = "Mattermost Chat Server";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
preStart = '' preStart = ''
mkdir -p ${cfg.state-directory}/config ${generate-mattermost-config
cp ${mattermost-config-file} ${cfg.state-directory}/config/config.json mattermost-config-target
ln -sf ${pkg}/bin ${cfg.state-directory} mattermost-config-file-template
ln -sf ${pkg}/fonts ${cfg.state-directory} cfg.smtp.password-file
ln -sf ${pkg}/i18n ${cfg.state-directory} cfg.database.password-file}
ln -sf ${pkg}/templates ${cfg.state-directory} cp ${cfg.smtp.password-file} ${cfg.state-directory}/config/config.json
cp -uRL ${pkg}/client ${cfg.state-directory} cp -uRL ${pkg}/client ${cfg.state-directory}
chown -R ${mattermost-user}:${mattermost-group} ${cfg.state-directory} chown ${mattermost-user}:${mattermost-group} ${cfg.state-directory}/client
chmod u+w -R ${cfg.state-directory}/client chmod 0750 ${cfg.state-directory}/client
chmod o-rwx -R ${cfg.state-directory}
''; '';
execStart = "${pkg}/bin/mattermost";
serviceConfig = { workingDirectory = cfg.state-directory;
PermissionsStartOnly = true; user = mattermost-user;
ExecStart = "${pkg}/bin/mattermost"; group = mattermost-group;
WorkingDirectory = cfg.state-directory;
Restart = "always";
RestartSec = "10";
LimitNOFILE = "49152";
User = mattermost-user;
Group = mattermost-group;
};
}; };
security.acme.certs.${cfg.hostname}.email = config.fudo.common.admin-email; systemd = {
tmpfiles.rules = [
"d ${cfg.state-directory} 0750 ${mattermost-user} ${mattermost-group} - -"
"d ${cfg.state-directory}/config 0750 ${mattermost-user} ${mattermost-group} - -"
"L ${cfg.state-directory}/bin - - - - ${pkg}/bin"
"L ${cfg.state-directory}/fonts - - - - ${pkg}/fonts"
"L ${cfg.state-directory}/i18n - - - - ${pkg}/i18n"
"L ${cfg.state-directory}/templates - - - - ${pkg}/templates"
];
# services.mattermost = {
# description = "Mattermost Chat Server";
# wantedBy = [ "multi-user.target" ];
# after = [ "network.target" ];
# preStart = ''
# ${generate-mattermost-config
# mattermost-config-target
# mattermost-config-file-template
# cfg.smtp.password-file
# cfg.database.password-file}
# cp ${cfg.smtp.password-file} ${cfg.state-directory}/config/config.json
# cp -uRL ${pkg}/client ${cfg.state-directory}
# chown ${mattermost-user}:${mattermost-group} ${cfg.state-directory}/client
# chmod 0750 ${cfg.state-directory}/client
# '';
# serviceConfig = {
# PermissionsStartOnly = true;
# ExecStart = "${pkg}/bin/mattermost";
# WorkingDirectory = cfg.state-directory;
# Restart = "always";
# RestartSec = "10";
# LimitNOFILE = "49152";
# User = mattermost-user;
# Group = mattermost-group;
# };
# };
};
services.nginx = { services.nginx = {
enable = true; enable = true;
appendHttpConfig = '' appendHttpConfig = ''
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off;
''; '';
virtualHosts = { virtualHosts = {
"${cfg.hostname}" = { "${cfg.hostname}" = {
@ -170,48 +212,48 @@ in {
proxyPass = "http://127.0.0.1:8065"; proxyPass = "http://127.0.0.1:8065";
extraConfig = '' extraConfig = ''
client_max_body_size 50M; client_max_body_size 50M;
proxy_set_header Connection ""; proxy_set_header Connection "";
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port; proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN; proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k; proxy_buffers 256 16k;
proxy_buffer_size 16k; proxy_buffer_size 16k;
proxy_read_timeout 600s; proxy_read_timeout 600s;
proxy_cache mattermost_cache; proxy_cache mattermost_cache;
proxy_cache_revalidate on; proxy_cache_revalidate on;
proxy_cache_min_uses 2; proxy_cache_min_uses 2;
proxy_cache_use_stale timeout; proxy_cache_use_stale timeout;
proxy_cache_lock on; proxy_cache_lock on;
proxy_http_version 1.1; proxy_http_version 1.1;
''; '';
}; };
locations."~ /api/v[0-9]+/(users/)?websocket$" = { locations."~ /api/v[0-9]+/(users/)?websocket$" = {
proxyPass = "http://127.0.0.1:8065"; proxyPass = "http://127.0.0.1:8065";
extraConfig = '' extraConfig = ''
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
client_max_body_size 50M; client_max_body_size 50M;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port; proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN; proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k; proxy_buffers 256 16k;
proxy_buffer_size 16k; proxy_buffer_size 16k;
client_body_timeout 60; client_body_timeout 60;
send_timeout 300; send_timeout 300;
lingering_timeout 5; lingering_timeout 5;
proxy_connect_timeout 90; proxy_connect_timeout 90;
proxy_send_timeout 300; proxy_send_timeout 300;
proxy_read_timeout 90s; proxy_read_timeout 90s;
''; '';
}; };
}; };
}; };

View File

@ -8,7 +8,14 @@ let
hostname = config.instance.hostname; hostname = config.instance.hostname;
host-secrets = config.fudo.secrets.host-secrets.${hostname}; generate-string-hash = name: str: let
string-hash-pkg = pkgs.stdenv.mkDerivation {
name = "${name}-string-hash";
phases = "installPhase";
buildInputs = [ pkgs.openssl ];
installPhase = "openssl passwd -6 ${str} > $out";
};
in string-hash-pkg;
in { in {
options.fudo.hosts = with types; options.fudo.hosts = with types;
@ -37,9 +44,12 @@ in {
#defaultGateway = site.gateway-v4; #defaultGateway = site.gateway-v4;
#defaultGateway6 = site.gateway-v6; #defaultGateway6 = site.gateway-v6;
firewall = { firewall = mkIf ((length host-cfg.external-interfaces) > 0) {
enable = (length host-cfg.external-interfaces) > 0; enable = true;
allowedTCPPorts = [ 22 ]; allowedTCPPorts = [ 22 2112 ]; # Make sure _at least_ SSH is allowed
trustedInterfaces = let
all-interfaces = attrNames config.networking.interfaces;
in subtractLists host-cfg.external-interfaces all-interfaces;
}; };
hostId = mkIf (host-cfg.machine-id != null) hostId = mkIf (host-cfg.machine-id != null)
@ -79,6 +89,8 @@ in {
in concatStringsSep "\n" sorted-unique; in concatStringsSep "\n" sorted-unique;
build-timestamp.text = toString config.instance.build-timestamp; build-timestamp.text = toString config.instance.build-timestamp;
build-seed-hash.source =
generate-string-hash "build-seed" config.instance.build-seed;
}; };
systemPackages = with pkgs; systemPackages = with pkgs;
@ -89,7 +101,10 @@ in {
krb5.libdefaults.default_realm = domain.gssapi-realm; krb5.libdefaults.default_realm = domain.gssapi-realm;
services.cron.mailto = domain.admin-email; services = {
cron.mailto = domain.admin-email;
fail2ban.ignoreIP = config.instance.local-networks;
};
virtualisation.docker = mkIf (host-cfg.docker-server) { virtualisation.docker = mkIf (host-cfg.docker-server) {
enable = true; enable = true;
@ -97,56 +112,11 @@ in {
autoPrune.enable = true; autoPrune.enable = true;
}; };
fudo = let
try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;
files = config.fudo.secrets.files;
keytab-file = try-attr hostname files.host-keytabs;
build-private-key-file =
mapOptional
(keypair: keypair.private-key)
(try-attr hostname files.build-keypairs);
in {
secrets.host-secrets.${hostname} = {
host-keytab = mkIf (keytab-file != null) {
source-file = keytab-file;
target-file = "/etc/krb5.keytab";
user = "root";
};
build-private-key = mkIf (build-private-key-file != null) {
source-file = build-private-key-file;
target-file = "/var/run/nix-build/host.key";
user = "root";
};
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 =
host-secrets.backplane-passwd.target-file;
};
programs.adb.enable = host-cfg.android-dev; programs.adb.enable = host-cfg.android-dev;
users.groups.adbusers = mkIf host-cfg.android-dev { users.groups.adbusers = mkIf host-cfg.android-dev {
members = config.instance.local-admins; members = config.instance.local-admins;
}; };
boot.tmpOnTmpfs = host-cfg.tmp-on-tmpfs; boot.tmpOnTmpfs = host-cfg.tmp-on-tmpfs;
home-manager.users.root.home.file = {
".k5login".text = let
realm = domain.gssapi-realm;
entries =
map (admin: "${admin}/root@${realm}") config.instance.local-admins;
in concatStringsSep "\n" entries;
};
}; };
} }

View File

@ -7,9 +7,6 @@ let
user-group-entry = group: user: user-group-entry = group: user:
nameValuePair user { extraGroups = [ group ]; }; nameValuePair user { extraGroups = [ group ]; };
user-home-entry = ipfs-path: user:
nameValuePair user { home.sessionVariables = { IPFS_PATH = ipfs-path; }; };
in { in {
options.fudo.ipfs = with types; { options.fudo.ipfs = with types; {
enable = mkEnableOption "Fudo IPFS"; enable = mkEnableOption "Fudo IPFS";
@ -53,7 +50,8 @@ in {
config = mkIf cfg.enable { config = mkIf cfg.enable {
users.users = listToAttrs (map (user-group-entry cfg.group) cfg.users); users.users =
mapAttrs user-group-entry config.instance.local-users;
services.ipfs = { services.ipfs = {
enable = true; enable = true;
@ -64,8 +62,5 @@ in {
group = cfg.group; group = cfg.group;
dataDir = cfg.data-dir; dataDir = cfg.data-dir;
}; };
home-manager.users =
listToAttrs (map (user-home-entry cfg.data-dir) cfg.users);
}; };
} }

View File

@ -5,100 +5,7 @@ let
cfg = config.fudo.auth.ldap-server; cfg = config.fudo.auth.ldap-server;
ldapSystemUserOpts = { name, ... }: { user-type = import ../types/user.nix { inherit lib; };
options = {
description = mkOption {
type = types.str;
description = ''
The description of this system user.
'';
};
hashed-password = mkOption {
type = types.str;
description = ''
The password for this user, hashed with ldappasswd.
'';
default = "";
};
};
};
ldapGroupOpts = { name, ... }: {
options = {
gid = mkOption {
type = types.int;
description = ''
The GID number of this group.
'';
};
description = mkOption {
type = types.str;
description = ''
The description of this group.
'';
};
members = mkOption {
type = with types; listOf str;
default = [ ];
description = ''
A list of usernames representing the members of this group.
'';
};
};
};
ldapUserOpts = { name, ... }: {
options = {
uid = mkOption {
type = types.int;
description = ''
The UID number of this user.
'';
};
common-name = mkOption {
type = types.str;
description = ''
The given name of this user.
'';
};
group = mkOption {
type = types.str;
description = ''
The name of the user's primary group.
'';
};
login-shell = mkOption {
type = types.str;
default = "/bin/bash";
description = ''
The user's preferred shell. Default is /bin/bash.
'';
};
description = mkOption {
type = types.str;
default = "Fudo Member";
description = ''
The description of this user.
'';
};
hashed-password = mkOption {
type = types.str;
description = ''
The password for this user, hashed with ldappasswd.
'';
default = "";
};
};
};
stringJoin = joiner: attrList: stringJoin = joiner: attrList:
if (length attrList) == 0 then if (length attrList) == 0 then
@ -107,15 +14,15 @@ let
foldr (lAttr: rAttr: "${lAttr}${joiner}${rAttr}") (last attrList) foldr (lAttr: rAttr: "${lAttr}${joiner}${rAttr}") (last attrList)
(init attrList); (init attrList);
getUserGidNumber = user: group-map: group-map.${user.group}.gid; getUserGidNumber = user: group-map: group-map.${user.primary-group}.gid;
attrOr = attrs: attr: value: if attrs ? ${attr} then attrs.${attr} else value; attrOr = attrs: attr: value: if attrs ? ${attr} then attrs.${attr} else value;
mkHomeDir = username: user-opts: mkHomeDir = username: user-opts:
if (user-opts.group == "admin") then if (user-opts.primary-group == "admin") then
"/home/${username}" "/home/${username}"
else else
"/home/${user-opts.group}/${username}"; "/home/${user-opts.primary-group}/${username}";
userLdif = base: name: group-map: opts: '' userLdif = base: name: group-map: opts: ''
dn: uid=${name},ou=members,${base} dn: uid=${name},ou=members,${base}
@ -131,7 +38,7 @@ let
shadowLastChange: 12230 shadowLastChange: 12230
shadowMax: 99999 shadowMax: 99999
shadowWarning: 7 shadowWarning: 7
userPassword: ${opts.hashed-password} userPassword: ${opts.ldap-hashed-passwd}
''; '';
systemUserLdif = base: name: opts: '' systemUserLdif = base: name: opts: ''
@ -140,7 +47,7 @@ let
objectClass: simpleSecurityObject objectClass: simpleSecurityObject
cn: ${name} cn: ${name}
description: ${opts.description} description: ${opts.description}
userPassword: ${opts.hashed-password} userPassword: ${opts.ldap-hashed-password}
''; '';
toMemberList = userList: toMemberList = userList:
@ -242,7 +149,7 @@ in {
}; };
users = mkOption { users = mkOption {
type = attrsOf (submodule ldapUserOpts); type = attrsOf (submodule user-type.userOpts);
example = { example = {
tester = { tester = {
uid = 10099; uid = 10099;
@ -258,7 +165,7 @@ in {
groups = mkOption { groups = mkOption {
default = { }; default = { };
type = attrsOf (submodule ldapGroupOpts); type = attrsOf (submodule user-type.groupOpts);
example = { example = {
admin = { admin = {
gid = 1099; gid = 1099;
@ -272,16 +179,19 @@ in {
system-users = mkOption { system-users = mkOption {
default = { }; default = { };
type = attrsOf (submodule ldapSystemUserOpts); type = attrsOf (submodule user-type.systemUserOpts);
example = { example = {
replicator = { replicator = {
description = "System user for database sync"; description = "System user for database sync";
hashed-password = "<insert password hash>"; ldap-hashed-password = "<insert password hash>";
}; };
}; };
description = '' description = "System users to be added to the Fudo LDAP database.";
System users to be added to the Fudo LDAP database. };
'';
database-directory = mkOption {
type = str;
description = "Path at which to store the database.";
}; };
}; };
}; };
@ -337,21 +247,19 @@ in {
services.openldap = { services.openldap = {
enable = true; enable = true;
suffix = cfg.base;
rootdn = "cn=admin,${cfg.base}";
rootpwFile = "${cfg.rootpw-file}";
urlList = cfg.listen-uris; urlList = cfg.listen-uris;
database = "mdb";
settings = let settings = let
makeAccessLine = i: attrs: perm-map: let makePermEntry = dn: perm: "by ${dn} ${perm}";
perm-strings = mapAttrs (dn: perm: "by ${dn} ${perm}") perm-map;
perm-string = concatStringsSep " " perm-strings; makeAccessLine = target: perm-map: let
in "${i}to ${attrs} ${perm-string}"; perm-entries = mapAttrsToList makePermEntry perm-map;
in "to ${target} ${concatStringsSep " " perm-entries} done";
makeAccess = access-map: let makeAccess = access-map: let
pairs = mapAttrsToList (target: perm-map: [target perm-map]) access-map; access-lines = mapAttrsToList makeAccessLine;
in imap0 (i: pair: makeAccessLine i pair[0] pair[1]) pairs; numbered-access-lines = imap0 (i: line: "{${toString i}}${line}");
in numbered-access-lines (access-lines access-map);
in { in {
attrs = { attrs = {
@ -363,8 +271,8 @@ in {
olcTLSCACertificateFile = cfg.ssl-ca-certificate; olcTLSCACertificateFile = cfg.ssl-ca-certificate;
olcSaslSecProps = "noplain,noanonymous"; olcSaslSecProps = "noplain,noanonymous";
olcAuthzRegexp = let olcAuthzRegexp = let
authz-regex-entry = i: { regex, target}: authz-regex-entry = i: { regex, target }:
"{${i}}\"${rx}\" \"${target}\""; "{${toString i}}\"${regex}\" \"${target}\"";
in imap0 authz-regex-entry [ in imap0 authz-regex-entry [
{ {
regex = "^uid=auth/([^.]+).fudo.org,cn=fudo.org,cn=gssapi,cn=auth$"; regex = "^uid=auth/([^.]+).fudo.org,cn=fudo.org,cn=gssapi,cn=auth$";
@ -396,7 +304,7 @@ in {
olcAccess = makeAccess { olcAccess = makeAccess {
"*" = { "*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=admin,dc=fudo,dc=org" = "manage"; "dn.exact=cn=admin,${cfg.base}" = "manage";
"*" = "none"; "*" = "none";
}; };
}; };
@ -462,30 +370,32 @@ in {
}; };
}; };
declarativeContents = '' declarativeContents = {
dn: ${cfg.base} "dc=fudo,dc=org" = ''
objectClass: top dn: ${cfg.base}
objectClass: dcObject objectClass: top
objectClass: organization objectClass: dcObject
o: ${cfg.organization} objectClass: organization
o: ${cfg.organization}
dn: ou=groups,${cfg.base} dn: ou=groups,${cfg.base}
objectClass: organizationalUnit objectClass: organizationalUnit
description: ${cfg.organization} groups description: ${cfg.organization} groups
dn: ou=members,${cfg.base} dn: ou=members,${cfg.base}
objectClass: organizationalUnit objectClass: organizationalUnit
description: ${cfg.organization} members description: ${cfg.organization} members
dn: cn=admin,${cfg.base} dn: cn=admin,${cfg.base}
objectClass: organizationalRole objectClass: organizationalRole
cn: admin cn: admin
description: "Admin User" description: "Admin User"
${systemUsersLdif cfg.base cfg.system-users} ${systemUsersLdif cfg.base cfg.system-users}
${groupsLdif cfg.base cfg.groups} ${groupsLdif cfg.base cfg.groups}
${usersLdif cfg.base cfg.groups cfg.users} ${usersLdif cfg.base cfg.groups cfg.users}
''; '';
};
}; };
}; };
} }

View File

@ -1,4 +1,4 @@
{ lib, config, ... }: { pkgs, lib, config, ... }:
with lib; with lib;
let let
hostname = config.instance.hostname; hostname = config.instance.hostname;
@ -11,22 +11,18 @@ let
container-mail-user-id = 542; container-mail-user-id = 542;
container-mail-group = "mailer"; container-mail-group = "mailer";
build-timestamp = config.instance.build-timestamp;
build-seed = config.instance.build-seed;
site = config.instance.local-site;
domain = cfg.domain;
local-networks = config.instance.local-networks;
in rec { in rec {
config = mkIf (cfg.enableContainer) { config = mkIf (cfg.enableContainer) {
# Disable postfix on this host--it'll be run in the container instead # Disable postfix on this host--it'll be run in the container instead
services.postfix.enable = false; services.postfix.enable = false;
fudo.acme.host-domains.${hostname}.${cfg.mail-hostname} = {
local-copies = {
postfix = {
user = "root";
};
dovecot-cert = {
user = "root";
};
};
};
services.nginx = mkIf cfg.monitoring { services.nginx = mkIf cfg.monitoring {
enable = true; enable = true;
@ -36,10 +32,10 @@ in rec {
proxy_set_header Host $host; proxy_set_header Host $host;
''; '';
trusted-network-string = trusted-network-string =
optionalString ((length config.instance.local-networks) > 0) optionalString ((length local-networks) > 0)
(concatStringsSep "\n" (concatStringsSep "\n"
(map (network: "allow ${network};") (map (network: "allow ${network};")
config.instance.local-networks)) + '' local-networks)) + ''
deny all;''; deny all;'';
@ -85,9 +81,7 @@ in rec {
autoStart = true; autoStart = true;
bindMounts = let bindMounts = {
cert-copies = config.fudo.acme.host-domains.${hostname}.${cfg.mail-hostname}.local-copies;
in {
"${container-maildir}" = { "${container-maildir}" = {
hostPath = cfg.mail-directory; hostPath = cfg.mail-directory;
isReadOnly = false; isReadOnly = false;
@ -98,53 +92,54 @@ in rec {
isReadOnly = false; isReadOnly = false;
}; };
"/etc/${container-shared}" = {
hostPath = "/etc/${container-shared}";
isReadOnly = true;
};
"/run/mail/certs/postfix/cert.pem" = { "/run/mail/certs/postfix/cert.pem" = {
hostPath = cert-copies.postfix.certificate; hostPath = cfg.ssl.certificate;
isReadOnly = true; isReadOnly = true;
}; };
"/run/mail/certs/postfix/key.pem" = { "/run/mail/certs/postfix/key.pem" = {
hostPath = cert-copies.postfix.private-key; hostPath = cfg.ssl.private-key;
isReadOnly = true; isReadOnly = true;
}; };
"/run/mail/certs/dovecot/cert.pem" = { "/run/mail/certs/dovecot/cert.pem" = {
hostPath = cert-copies.dovecot.certificate; hostPath = cfg.ssl.certificate;
isReadOnly = true; isReadOnly = true;
}; };
"/run/mail/certs/dovecot/key.pem" = { "/run/mail/certs/dovecot/key.pem" = {
hostPath = cert-copies.dovecot.private-key; hostPath = cfg.ssl.private-key;
isReadOnly = true;
};
"/run/mail/passwords/dovecot/ldap-reader.passwd" = {
hostPath = cfg.dovecot.ldap.reader-password-file;
isReadOnly = true; 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, ... }: { config = { config, pkgs, ... }: {
imports = let
initialize-host = import ../../initialize.nix;
profile = "container";
in [
./mail.nix
(initialize-host {
inherit
lib
pkgs
build-timestamp
site
domain
profile;
hostname = "mail-container";
})
];
instance.build-seed = build-seed;
environment.etc = { environment.etc = {
"mail-server/postfix/cert.pem" = { "mail-server/postfix/cert.pem" = {
source = "/run/mail/certs/postfix/cert.pem"; source = "/run/mail/certs/postfix/cert.pem";
@ -158,59 +153,67 @@ in rec {
}; };
"mail-server/dovecot/cert.pem" = { "mail-server/dovecot/cert.pem" = {
source = "/run/mail/certs/dovecot/cert.pem"; source = "/run/mail/certs/dovecot/cert.pem";
user = config.services.dovecot.user; user = config.services.dovecot2.user;
mode = "0444"; mode = "0444";
}; };
"mail-server/dovecot/key.pem" = { "mail-server/dovecot/key.pem" = {
source = "/run/mail/certs/dovecot/key.pem"; source = "/run/mail/certs/dovecot/key.pem";
user = config.services.dovecot.user; user = config.services.dovecot2.user;
mode = "0400"; mode = "0400";
}; };
## The pre-script runs as root anyway...
# "mail-server/dovecot/ldap-reader.passwd" = {
# source = "/run/mail/passwords/dovecot/ldap-reader.passwd";
# user = config.services.dovecot2.user;
# mode = "0400";
# };
}; };
imports = [ ./mail.nix ]; fudo = {
fudo.mail-server = { mail-server = {
enable = true; enable = true;
hostname = cfg.hostname; mail-hostname = cfg.mail-hostname;
domain = cfg.domain; domain = cfg.domain;
debug = cfg.debug; debug = cfg.debug;
monitoring = cfg.monitoring; monitoring = cfg.monitoring;
state-directory = container-statedir; state-directory = container-statedir;
mail-directory = container-maildir; mail-directory = container-maildir;
postfix = { postfix = {
ssl-certificate = "/etc/mail-server/postfix/cert.pem"; ssl-certificate = "/etc/mail-server/postfix/cert.pem";
ssl-private-key = "/etc/mail-server/postfix/key.pem"; ssl-private-key = "/etc/mail-server/postfix/key.pem";
};
dovecot = {
ssl-certificate = "/etc/mail-server/dovecot/cert.pem";
ssl-private-key = "/etc/mail-server/dovecot/key.pem";
ldap = {
server-urls = cfg.dovecot.ldap.server-urls;
reader-dn = cfg.dovecot.ldap.reader-dn;
reader-passwd = cfg.dovecot.ldap.reader-passwd;
}; };
dovecot = {
ssl-certificate = "/etc/mail-server/dovecot/cert.pem";
ssl-private-key = "/etc/mail-server/dovecot/key.pem";
ldap = {
server-urls = cfg.dovecot.ldap.server-urls;
reader-dn = cfg.dovecot.ldap.reader-dn;
reader-password-file = "/run/mail/passwords/dovecot/ldap-reader.passwd";
};
};
local-domains = cfg.local-domains;
alias-users = cfg.alias-users;
user-aliases = cfg.user-aliases;
sender-blacklist = cfg.sender-blacklist;
recipient-blacklist = cfg.recipient-blacklist;
trusted-networks = cfg.trusted-networks;
mail-user = container-mail-user;
mail-user-id = container-mail-user-id;
mail-group = container-mail-group;
clamav.enable = cfg.clamav.enable;
dkim.signing = cfg.dkim.signing;
}; };
local-domains = cfg.local-domains;
alias-users = cfg.alias-users;
user-aliases = cfg.user-aliases;
sender-blacklist = cfg.sender-blacklist;
recipient-blacklist = cfg.recipient-blacklist;
trusted-networks = cfg.trusted-networks;
mail-user = container-mail-user;
mail-user-id = container-mail-user-id;
mail-group = container-mail-group;
clamav.enable = cfg.clamav.enable;
dkim.signing = cfg.dkim.signing;
}; };
}; };
}; };

View File

@ -7,7 +7,7 @@ let
in { in {
options.fudo.mail-server = { options.fudo.mail-server = with types; {
enable = mkEnableOption "Fudo Email Server"; enable = mkEnableOption "Fudo Email Server";
enableContainer = mkEnableOption '' enableContainer = mkEnableOption ''
@ -17,18 +17,17 @@ in {
''; '';
domain = mkOption { domain = mkOption {
type = types.str; type = str;
description = "The main and default domain name for this email server."; description = "The main and default domain name for this email server.";
}; };
mail-hostname = mkOption { mail-hostname = mkOption {
type = types.str; type = str;
description = "The domain name to use for the mail server."; description = "The domain name to use for the mail server.";
}; };
ldap-url = mkOption { ldap-url = mkOption {
type = types.str; type = str;
description = "URL of the LDAP server to use for authentication."; description = "URL of the LDAP server to use for authentication.";
example = "ldaps://auth.fudo.org/"; example = "ldaps://auth.fudo.org/";
}; };
@ -36,23 +35,25 @@ in {
monitoring = mkEnableOption "Enable monitoring for the mail server."; monitoring = mkEnableOption "Enable monitoring for the mail server.";
mail-user = mkOption { mail-user = mkOption {
type = types.str; type = str;
description = "User to use for mail delivery."; description = "User to use for mail delivery.";
default = "mailuser";
}; };
# No group id, because NixOS doesn't seem to use it # No group id, because NixOS doesn't seem to use it
mail-group = mkOption { mail-group = mkOption {
type = types.str; type = str;
description = "Group to use for mail delivery."; description = "Group to use for mail delivery.";
default = "mailgroup";
}; };
mail-user-id = mkOption { mail-user-id = mkOption {
type = types.int; type = int;
description = "UID of mail-user."; description = "UID of mail-user.";
}; };
local-domains = mkOption { local-domains = mkOption {
type = with types; listOf str; type = listOf str;
description = "A list of domains for which we accept mail."; description = "A list of domains for which we accept mail.";
default = ["localhost" "localhost.localdomain"]; default = ["localhost" "localhost.localdomain"];
example = [ example = [
@ -64,17 +65,17 @@ in {
}; };
mail-directory = mkOption { mail-directory = mkOption {
type = types.str; type = str;
description = "Path to use for mail storage."; description = "Path to use for mail storage.";
}; };
state-directory = mkOption { state-directory = mkOption {
type = types.str; type = str;
description = "Path to use for state data."; description = "Path to use for state data.";
}; };
trusted-networks = mkOption { trusted-networks = mkOption {
type = with types; listOf str; type = listOf str;
description = "A list of trusted networks, for which we will happily relay without auth."; description = "A list of trusted networks, for which we will happily relay without auth.";
example = [ example = [
"10.0.0.0/16" "10.0.0.0/16"
@ -83,7 +84,7 @@ in {
}; };
sender-blacklist = mkOption { sender-blacklist = mkOption {
type = with types; listOf str; type = listOf str;
description = "A list of email addresses for whom we will not send email."; description = "A list of email addresses for whom we will not send email.";
default = []; default = [];
example = [ example = [
@ -93,7 +94,7 @@ in {
}; };
recipient-blacklist = mkOption { recipient-blacklist = mkOption {
type = with types; listOf str; type = listOf str;
description = "A list of email addresses for whom we will not accept email."; description = "A list of email addresses for whom we will not accept email.";
default = []; default = [];
example = [ example = [
@ -103,14 +104,14 @@ in {
}; };
message-size-limit = mkOption { message-size-limit = mkOption {
type = types.int; type = int;
description = "Size of max email in megabytes."; description = "Size of max email in megabytes.";
default = 30; default = 30;
}; };
user-aliases = mkOption { user-aliases = mkOption {
type = with types; attrsOf(listOf str); type = attrsOf (listOf str);
description = "A map of real user to list of aliases."; description = "A map of real user to list of alias emails.";
default = {}; default = {};
example = { example = {
someuser = ["alias0" "alias1"]; someuser = ["alias0" "alias1"];
@ -118,7 +119,7 @@ in {
}; };
alias-users = mkOption { alias-users = mkOption {
type = with types; attrsOf(listOf str); type = attrsOf (listOf str);
description = "A map of email alias to a list of users."; description = "A map of email alias to a list of users.";
example = { example = {
alias = ["realuser0" "realuser1"]; alias = ["realuser0" "realuser1"];
@ -164,15 +165,27 @@ in {
debug = mkOption { debug = mkOption {
description = "Enable debugging on mailservers."; description = "Enable debugging on mailservers.";
type = types.bool; type = bool;
default = false; default = false;
}; };
max-user-connections = mkOption { max-user-connections = mkOption {
description = "Max simultaneous connections per user."; description = "Max simultaneous connections per user.";
type = types.int; type = int;
default = 20; default = 20;
}; };
ssl = {
certificate = mkOption {
type = str;
description = "Path to the ssl certificate for the mail server to use.";
};
private-key = mkOption {
type = str;
description = "Path to the ssl private key for the mail server to use.";
};
};
}; };
imports = [ imports = [
@ -184,22 +197,26 @@ in {
]; ];
config = mkIf cfg.enable { config = mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d ${cfg.mail-directory} 770 ${cfg.mail-user} ${cfg.mail-group} - -"
];
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ 25 110 143 587 993 995 ]; allowedTCPPorts = [ 25 110 143 587 993 995 ];
}; };
users = { users = {
users = { users = {
mailuser = { ${cfg.mail-user} = {
isSystemUser = true; isSystemUser = true;
uid = cfg.mail-user-id; uid = cfg.mail-user-id;
group = "mailgroup"; group = cfg.mail-group;
}; };
}; };
groups = { groups = {
mailgroup = { ${cfg.mail-group} = {
members = ["mailuser"]; members = [ cfg.mail-user ];
}; };
}; };
}; };

View File

@ -4,7 +4,7 @@ with lib;
let let
cfg = config.fudo.mail-server; cfg = config.fudo.mail-server;
state-directory = "${cfg.state-directory}/dovecot"; sieve-path = "${cfg.state-directory}/dovecot/imap_sieve";
pipe-bin = pkgs.stdenv.mkDerivation { pipe-bin = pkgs.stdenv.mkDerivation {
name = "pipe_bin"; name = "pipe_bin";
@ -23,35 +23,47 @@ let
''; '';
}; };
ldap-conf = filename: config: ldap-conf-template = ldap-cfg:
let let
ssl-config = if config.ca == null then '' ssl-config = if (ldap-cfg.ca == null) then ''
tls = no tls = no
tls_require_cert = try tls_require_cert = try
'' else '' '' else ''
tls_ca_cert_file = ${config.ca} tls_ca_cert_file = ${ldap-cfg.ca}
tls = yes tls = yes
tls_require_cert = try tls_require_cert = try
''; '';
in in
pkgs.writeText filename '' pkgs.writeText "dovecot2-ldap-config.conf.template" ''
uris = ${concatStringsSep " " config.server-urls} uris = ${concatStringsSep " " ldap-cfg.server-urls}
ldap_version = 3 ldap_version = 3
dn = ${config.reader-dn} dn = ${ldap-cfg.reader-dn}
dnpass = ${config.reader-passwd} dnpass = __LDAP_READER_PASSWORD__
auth_bind = yes auth_bind = yes
auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
base = dc=fudo,dc=org base = dc=fudo,dc=org
${ssl-config} ${ssl-config}
''; '';
ldap-conf-generator = ldap-cfg: let
template = ldap-conf-template ldap-cfg;
target-dir = dirOf ldap-cfg.generated-ldap-config;
target = ldap-cfg.generated-ldap-config;
in pkgs.writeScript "dovecot2-ldap-password-swapper.sh" ''
mkdir -p ${target-dir}
touch ${target}
chmod 600 ${target}
chown ${config.services.dovecot2.user} ${target}
LDAP_READER_PASSWORD=$( cat "${ldap-cfg.reader-password-file}" )
sed 's/__LDAP_READER_PASSWORD__/$LDAP_READER_PASSWORD/' '${template}' > ${target}
'';
ldap-passwd-entry = ldap-config: '' ldap-passwd-entry = ldap-config: ''
passdb { passdb {
driver = ldap driver = ldap
args = ${ldap-conf "ldap-passdb.conf" ldap-config} args = ${ldap-conf "ldap-passdb.conf" ldap-config}
} }
''; '';
ldapOpts = { ldapOpts = {
options = with types; { options = with types; {
@ -61,6 +73,12 @@ let
default = null; default = null;
}; };
base = mkOption {
type = str;
description = "Base of the LDAP server database.";
example = "dc=fudo,dc=org";
};
server-urls = mkOption { server-urls = mkOption {
type = listOf str; type = listOf str;
description = "A list of LDAP server URLs used for authentication."; description = "A list of LDAP server URLs used for authentication.";
@ -69,16 +87,20 @@ let
reader-dn = mkOption { reader-dn = mkOption {
type = str; type = str;
description = '' description = ''
DN to use for reading user information. Needs access to homeDirectory, DN to use for reading user information. Needs access to homeDirectory,
uidNumber, gidNumber, and uid, but not password attributes. uidNumber, gidNumber, and uid, but not password attributes.
''; '';
}; };
reader-passwd = mkOption { reader-password-file = mkOption {
type = str; type = str;
description = '' description = "Password for the user specified in ldap-reader-dn.";
Password for the user specified in ldap-reader-dn. };
'';
generated-ldap-config = mkOption {
type = str;
description = "Path at which to store the generated LDAP config file, including password.";
default = "/run/dovecot2/config/ldap.conf";
}; };
}; };
}; };
@ -206,8 +228,12 @@ in {
auth_mechanisms = login plain auth_mechanisms = login plain
${optionalString (cfg.dovecot.ldap != null) ${optionalString (cfg.dovecot.ldap != null) ''
(ldap-passwd-entry cfg.dovecot.ldap)} passdb {
driver = ldap
args = ${cfg.dovecot.ldap.generated-ldap-config}
}
''}
userdb { userdb {
driver = static driver = static
args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u
@ -249,12 +275,12 @@ in {
# From elsewhere to Spam folder # From elsewhere to Spam folder
imapsieve_mailbox1_name = Junk imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:${state-directory}/imap_sieve/report-spam.sieve imapsieve_mailbox1_before = file:${sieve-path}/report-spam.sieve
# From Spam folder to elsewhere # From Spam folder to elsewhere
imapsieve_mailbox2_name = * imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:${state-directory}/imap_sieve/report-ham.sieve imapsieve_mailbox2_before = file:${sieve-path}/report-ham.sieve
sieve_pipe_bin_dir = ${pipe-bin}/pipe/bin sieve_pipe_bin_dir = ${pipe-bin}/pipe/bin
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
} }
@ -268,19 +294,21 @@ in {
''; '';
}; };
systemd.services.dovecot2.preStart = '' systemd = {
mkdir -p '${state-directory}' tmpfiles.rules = [
chown ${dovecot-user}:${cfg.mail-group} '${state-directory}' "d ${sieve-path} 750 ${dovecot-user} ${cfg.mail-group} - -"
rm -rf '${state-directory}/imap_sieve' ];
mkdir '${state-directory}/imap_sieve'
cp -p "${./dovecot/imap_sieve}"/*.sieve '${state-directory}/imap_sieve/'
for k in "${state-directory}/imap_sieve"/*.sieve ; do
${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
done
chown -R '${dovecot-user}:${cfg.mail-group}' '${state-directory}/imap_sieve'
chown '${cfg.mail-user}:${cfg.mail-group}' ${cfg.mail-directory} services.dovecot2.preStart = ''
chmod g+w ${cfg.mail-directory} rm -f ${sieve-path}/*
''; cp -p ${./dovecot/imap_sieve}/*.sieve ${sieve-path}
for k in ${sieve-path}/*.sieve ; do
${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
done
${optionalString (cfg.dovecot.ldap != null)
(ldap-conf-generator cfg.dovecot.ldap)}
'';
};
}; };
} }

View File

@ -109,7 +109,7 @@ in {
enable = true; enable = true;
domain = cfg.domain; domain = cfg.domain;
origin = cfg.domain; origin = cfg.domain;
hostname = cfg.hostname; hostname = cfg.mail-hostname;
destination = ["localhost" "localhost.localdomain"]; destination = ["localhost" "localhost.localdomain"];
# destination = ["localhost" "localhost.localdomain" cfg.hostname] ++ # destination = ["localhost" "localhost.localdomain" cfg.hostname] ++
# cfg.local-domains;; # cfg.local-domains;;
@ -150,7 +150,7 @@ in {
# mail_spool_directory = "${cfg.mail-directory}/"; # mail_spool_directory = "${cfg.mail-directory}/";
message_size_limit = toString(cfg.message-size-limit * 1024 * 1024); message_size_limit = toString(cfg.message-size-limit * 1024 * 1024);
smtpd_banner = "${cfg.hostname} ESMTP NO UCE"; smtpd_banner = "${cfg.mail-hostname} ESMTP NO UCE";
tls_eecdh_strong_curve = "prime256v1"; tls_eecdh_strong_curve = "prime256v1";
tls_eecdh_ultra_curve = "secp384r1"; tls_eecdh_ultra_curve = "secp384r1";

View File

@ -4,6 +4,11 @@ with lib;
let let
cfg = config.fudo.postgresql; cfg = config.fudo.postgresql;
hostname = config.instance.hostname;
domain-name = config.instance.local-domain;
gssapi-realm = config.fudo.domains.${domain-name}.gssapi-realm;
join-lines = lib.concatStringsSep "\n"; join-lines = lib.concatStringsSep "\n";
userDatabaseOpts = { database, ... }: { userDatabaseOpts = { database, ... }: {
@ -28,15 +33,15 @@ let
}; };
userOpts = { username, ... }: { userOpts = { username, ... }: {
options = { options = with types; {
password-file = mkOption { password-file = mkOption {
type = with types; nullOr str; type = nullOr str;
description = "A file containing the user's (plaintext) password."; description = "A file containing the user's (plaintext) password.";
default = null; default = null;
}; };
databases = mkOption { databases = mkOption {
type = with types; attrsOf (submodule userDatabaseOpts); type = attrsOf (submodule userDatabaseOpts);
description = "Map of databases to required database/table perms."; description = "Map of databases to required database/table perms.";
default = { }; default = { };
example = { example = {
@ -50,9 +55,9 @@ let
}; };
databaseOpts = { dbname, ... }: { databaseOpts = { dbname, ... }: {
options = { options = with types; {
users = mkOption { users = mkOption {
type = with types; listOf str; type = listOf str;
description = description =
"A list of users who should have full access to this database."; "A list of users who should have full access to this database.";
default = [ ]; default = [ ];
@ -74,9 +79,7 @@ let
''; '';
passwords-setter-script = users: passwords-setter-script = users:
pkgs.writeScriptBin "postgres-set-passwords.sh" '' pkgs.writeScript "postgres-set-passwords.sh" ''
#!${pkgs.bash}/bin/bash
if [ $# -ne 1 ]; then if [ $# -ne 1 ]; then
echo "usage: $0 output-file.sql" echo "usage: $0 output-file.sql"
exit 1 exit 1
@ -99,7 +102,7 @@ let
nameValuePair "DATABASE ${database}" databaseOpts.access) databases; nameValuePair "DATABASE ${database}" databaseOpts.access) databases;
makeEntry = nw: makeEntry = nw:
"host all all ${nw} gss include_realm=0 krb_realm=FUDO.ORG"; "host all all ${nw} gss include_realm=0 krb_realm=${gssapi-realm}";
makeNetworksEntry = networks: join-lines (map makeEntry networks); makeNetworksEntry = networks: join-lines (map makeEntry networks);
@ -263,8 +266,8 @@ in {
local all all ident local all all ident
# host-local # host-local
host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG host all all 127.0.0.1/32 gss include_realm=0 krb_realm=${gssapi-realm}
host all all ::1/128 gss include_realm=0 krb_realm=FUDO.ORG host all all ::1/128 gss include_realm=0 krb_realm=${gssapi-realm}
# local networks # local networks
${makeNetworksEntry cfg.local-networks} ${makeNetworksEntry cfg.local-networks}
@ -278,8 +281,7 @@ in {
postgresql-password-setter = let postgresql-password-setter = let
passwords-script = passwords-setter-script cfg.users; passwords-script = passwords-setter-script cfg.users;
password-wrapper-script = password-wrapper-script =
pkgs.writeScriptBin "password-script-wrapper.sh" '' pkgs.writeScript "password-script-wrapper.sh" ''
#!${pkgs.bash}/bin/bash
TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t postgres-XXXXXXXXXX) TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t postgres-XXXXXXXXXX)
echo "using temp dir $TMPDIR" echo "using temp dir $TMPDIR"
PASSWORD_SQL_FILE=$TMPDIR/user-passwords.sql PASSWORD_SQL_FILE=$TMPDIR/user-passwords.sql
@ -287,7 +289,7 @@ in {
touch $PASSWORD_SQL_FILE touch $PASSWORD_SQL_FILE
chown ${config.services.postgresql.superUser} $PASSWORD_SQL_FILE chown ${config.services.postgresql.superUser} $PASSWORD_SQL_FILE
chmod go-rwx $PASSWORD_SQL_FILE chmod go-rwx $PASSWORD_SQL_FILE
${passwords-script}/bin/postgres-set-passwords.sh $PASSWORD_SQL_FILE ${passwords-script} $PASSWORD_SQL_FILE
echo "executing $PASSWORD_SQL_FILE" echo "executing $PASSWORD_SQL_FILE"
${pkgs.postgresql}/bin/psql --port ${ ${pkgs.postgresql}/bin/psql --port ${
toString config.services.postgresql.port toString config.services.postgresql.port
@ -306,7 +308,7 @@ in {
Type = "oneshot"; Type = "oneshot";
User = config.services.postgresql.superUser; User = config.services.postgresql.superUser;
}; };
script = "${password-wrapper-script}/bin/password-script-wrapper.sh"; script = "${password-wrapper-script}";
}; };
postgresql.postStart = let postgresql.postStart = let

View File

@ -86,15 +86,17 @@ in {
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:9090"; proxyPass = "http://127.0.0.1:9090";
extraConfig = '' extraConfig = let
local-networks = config.instance.local-networks;
in ''
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port; proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
${optionalString ((length fudo-cfg.local-networks) > 0) ${optionalString ((length local-networks) > 0)
(concatStringsSep "\n" (map (network: "allow ${network};") fudo-cfg.local-networks)) + "\ndeny all;"} (concatStringsSep "\n" (map (network: "allow ${network};") local-networks)) + "\ndeny all;"}
''; '';
}; };
}; };

View File

@ -185,18 +185,6 @@ in {
}; };
config = { config = {
# users.users = {
# ${site-cfg.build-user} = mkIf
# (any (build-host: build-host == config.instance.hostname)
# (attrNames site-cfg.build-servers)) {
# isSystemUser = true;
# openssh.authorizedKeys.keys =
# concatMap (hostOpts: hostOpts.build-pubkeys)
# (attrValues site-hosts);
# shell = pkgs.bash;
# };
# };
networking.firewall.allowedTCPPorts = networking.firewall.allowedTCPPorts =
mkIf site-cfg.enable-ssh-backdoor [ site-cfg.dropbear-ssh-port ]; mkIf site-cfg.enable-ssh-backdoor [ site-cfg.dropbear-ssh-port ];

View File

@ -1,65 +1,9 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
let {
hostname = config.instance.hostname;
has-attrs = set: length (attrNames set) > 0;
host-keypairs =
if (hasAttr hostname config.fudo.secrets.files.host-ssh-keypairs) then
config.fudo.secrets.files.host-ssh-keypairs.${hostname}
else [];
sshfp-filename = host: keypair: "ssh-${host}-${keypair.key-type}.sshfp-record";
dns-sshfp-records = host: keypair: let
filename = sshfp-filename host keypair;
in pkgs.stdenv.mkDerivation {
name = "${host}-sshfp-record";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ openssh ];
installPhase = ''
mkdir $out
ssh-keygen -r REMOVEME -f "${keypair.public-key}" | sed 's/^REMOVEME IN SSHFP //' > $out/${filename}
'';
};
read-lines = filename: splitString "\n" (fileContents filename);
host-cfg = config.fudo.hosts.${hostname};
in {
config = { config = {
fudo = {
secrets.host-secrets.${hostname} = listToAttrs
(map
(keypair: nameValuePair "host-${keypair.key-type}-private-key" {
source-file = keypair.private-key;
target-file = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
user = "root";
})
host-keypairs);
hosts = mapAttrs (hostname: keypairs: {
ssh-pubkeys = map (keypair: keypair.public-key) keypairs;
ssh-fingerprints = concatMap (keypair:
let
fingerprint-derivation = dns-sshfp-records hostname keypair;
filename = sshfp-filename hostname keypair;
in read-lines "${fingerprint-derivation}/${filename}") keypairs;
}) config.fudo.secrets.files.host-ssh-keypairs;
};
services.openssh.hostKeys = map (keypair: {
path = "/var/run/ssh/private/host-${keypair.key-type}-private-key";
type = keypair.key-type;
}) host-keypairs;
programs.ssh.knownHosts = let programs.ssh.knownHosts = let
keyed-hosts = keyed-hosts =
filterAttrs (h: o: o.ssh-pubkeys != []) filterAttrs (h: o: o.ssh-pubkeys != [])
config.fudo.hosts; config.fudo.hosts;
@ -75,7 +19,7 @@ in {
in mapAttrs (hostname: hostOpts: { in mapAttrs (hostname: hostOpts: {
publicKeyFile = builtins.head hostOpts.ssh-pubkeys; publicKeyFile = builtins.head hostOpts.ssh-pubkeys;
hostNames = all-hostnames hostname host-cfg; hostNames = all-hostnames hostname hostOpts;
}) keyed-hosts; }) keyed-hosts;
}; };
} }

View File

@ -1,32 +0,0 @@
# Common home-manager config
{ config, lib, pkgs, ... }:
with lib;
let
list-contains = lst: item: any (i: i == item) lst;
domain-realm = domain: domainOpts: domainOpts.gssapi-realm;
user-realms = username:
mapAttrsToList domain-realm
(filterAttrs (domain: domainOpts: list-contains domainOpts.local-users username)
config.fudo.domains);
user-principals = username:
map (realm: "${username}@${realm}") (user-realms username);
user-k5login = username: userOpts: let
principals = userOpts.k5login ++ (user-principals username);
in ''
${concatStringsSep "\n" principals}
'';
user-config = username: userOpts: {
home.file.".k5login" = {
source = pkgs.writeText "${username}-k5login" (user-k5login username userOpts);
};
};
in {
config.home-manager.users = mapAttrs user-config config.instance.local-users;
}

View File

@ -65,34 +65,17 @@ in {
}; };
}; };
imports = [ ./users-common.nix ];
config = let config = let
sys = config.instance; sys = config.instance;
in { in {
fudo.auth.ldap-server = let fudo.auth.ldap-server = {
ldapUsers = (filterAttrs users = filterAttrs
(username: userOpts: userOpts.ldap-hashed-password != null)) (username: userOpts: userOpts.ldap-hashed-passwd != null)
config.fudo.users; config.fudo.users;
in { groups = config.fudo.groups;
users = mapAttrs (username: userOpts: {
uid = userOpts.uid;
group = userOpts.primary-group;
common-name = userOpts.common-name;
hashed-password = userOpts.ldap-hashed-password;
}) ldapUsers;
groups = mapAttrs (groupname: groupOpts: { system-users = config.fudo.system-users;
gid = groupOpts.gid-number;
description = groupOpts.description;
members = filterExistingUsers ldapUsers groupOpts.members;
}) config.fudo.groups;
system-users = mapAttrs (username: userOpts: {
description = userOpts.description;
hashed-password = userOpts.ldap-hashed-passwd;
}) config.fudo.system-users;
}; };
programs.ssh.extraConfig = mkAfter '' programs.ssh.extraConfig = mkAfter ''
@ -161,9 +144,6 @@ in {
in admin-entries // user-entries; in admin-entries // user-entries;
}; };
# TODO: This is NOT where this should be...
home-manager.useGlobalPkgs = true;
# Group home directories have to exist, otherwise users can't log in # Group home directories have to exist, otherwise users can't log in
systemd.services = let systemd.services = let
ensure-group-directories = group: ensure-group-directories = group:

View File

@ -302,7 +302,7 @@ in {
(site: site-cfg: let (site: site-cfg: let
site-config-file = builtins.toFile "${site}-rainloop.cfg" site-config-file = builtins.toFile "${site}-rainloop.cfg"
(import ./include/rainloop.nix lib site site-cfg site-pkgs.${site}.version); (import ./include/rainloop.nix lib site site-cfg site-packages.${site}.version);
domain-config-file = builtins.toFile "${site}-domain.cfg" '' domain-config-file = builtins.toFile "${site}-domain.cfg" ''
imap_host = "${site-cfg.mail-server}" imap_host = "${site-cfg.mail-server}"
@ -342,7 +342,7 @@ in {
link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg: link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg:
let let
cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-site-config".target-file; cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-site-config".target-file;
domain-cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-doomain-config".target-file; domain-cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-domain-config".target-file;
in '' in ''
${pkgs.coreutils}/bin/mkdir -p ${base-data-path}/${site}/_data_/_default_/configs ${pkgs.coreutils}/bin/mkdir -p ${base-data-path}/${site}/_data_/_default_/configs
${pkgs.coreutils}/bin/cp ${cfg-file} ${base-data-path}/${site}/_data_/_default_/configs/application.ini ${pkgs.coreutils}/bin/cp ${cfg-file} ${base-data-path}/${site}/_data_/_default_/configs/application.ini

View File

@ -52,6 +52,11 @@ in {
description = "List of users who should have access to the local host"; description = "List of users who should have access to the local host";
}; };
local-networks = mkOption {
type = listOf str;
description = "Networks which are considered local to this host, site, or domain.";
};
build-seed = mkOption { build-seed = mkOption {
type = str; type = str;
description = "Seed used to generate configuration."; description = "Seed used to generate configuration.";
@ -87,25 +92,23 @@ in {
filterAttrs (host: hostOpts: hostOpts.site == local-site) config.fudo.hosts; filterAttrs (host: hostOpts: hostOpts.site == local-site) config.fudo.hosts;
local-networks = local-networks =
host.local-networks // host.local-networks ++
config.fudo.domains.${local-domain}.local-networks // config.fudo.domains.${local-domain}.local-networks ++
config.fudo.sites.${local-site}.local-networks; config.fudo.sites.${local-site}.local-networks;
local-profile = host.profile; local-profile = host.profile;
build-seed = builtins.readFile config.fudo.secrets.files.build-seed;
in { in {
instance = { instance = {
inherit inherit
build-seed
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; local-profile
local-networks;
}; };
}; };
} }

17
lib/network.nix Normal file
View File

@ -0,0 +1,17 @@
{ pkgs, ... }:
with pkgs.lib;
let
generate-mac-address = hostname: interface: pkgs.stdenv.mkDerivation {
name = "mk-mac-${hostname}-${interface}";
phases = [ "installPhase" ];
installPhase = ''
echo ${hostname}-${interface} | sha1sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' > $out
'';
};
in {
generate-mac-address = hostname: interface: let
pkg = generate-mac-address hostname interface;
in builtins.readFile "${pkg}";
}

View File

@ -4,9 +4,10 @@
lib = prev.lib; lib = prev.lib;
in { in {
ip = import ./ip.nix { pkgs = prev; }; ip = import ./ip.nix { pkgs = prev; };
dns = import ./dns.nix { pkgs = prev;}; dns = import ./dns.nix { pkgs = prev; };
passwd = import ./passwd.nix { pkgs = prev;}; passwd = import ./passwd.nix { pkgs = prev; };
lisp = import ./lisp.nix { pkgs = prev;}; lisp = import ./lisp.nix { pkgs = prev; };
network = import ./network.nix { pkgs = prev; };
}; };
}; };
}) })

View File

@ -5,23 +5,17 @@ let
hash-ldap-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation { hash-ldap-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation {
name = "${name}-ldap-passwd"; name = "${name}-ldap-passwd";
phases = [ "buildPhase" "installPhase" ]; phases = [ "installPhase" ];
buildInputs = with pkgs; [ openldap ]; buildInputs = with pkgs; [ openldap ];
buildPhase = ''
slappasswd -T ${passwd-file} > ldap-passwd
'';
installPhase = '' installPhase = ''
mkdir -p $out slappasswd -T ${passwd-file} > $out
mv ldap-passwd $out
''; '';
}; };
hash-ldap-passwd = name: passwd-file: let hash-ldap-passwd = name: passwd-file:
passwd-pkgs = hash-ldap-passwd-pkg name passwd-file; builtins.readFile "${hash-ldap-passwd-pkg name passwd-file}";
in builtins.readFile "${passwd-pkgs}/ldap-passwd";
generate-random-passwd = name: length: pkgs.stdenv.mkDerivation { generate-random-passwd = name: length: pkgs.stdenv.mkDerivation {
name = "${name}-random-passwd"; name = "${name}-random-passwd";
@ -31,7 +25,7 @@ let
buildInputs = with pkgs; [ pwgen ]; buildInputs = with pkgs; [ pwgen ];
installPhase = '' installPhase = ''
pwgen --secure --num-passwords=1 ${length} > $out pwgen --secure --num-passwords=1 ${toString length} > $out
''; '';
}; };

View File

@ -108,15 +108,21 @@ rec {
description = "User's primary email address."; description = "User's primary email address.";
default = null; default = null;
}; };
email-aliases = mkOption {
type = listOf str;
description = "Email aliases that should map to this user.";
default = [];
};
}; };
}; };
groupOpts = { group-name, ... }: { groupOpts = { name, ... }: {
options = with lib.types; { options = with lib.types; {
# group-name = mkOption { group-name = mkOption {
# description = "Group name."; description = "Group name.";
# default = group-name; default = name;
# }; };
description = mkOption { description = mkOption {
type = str; type = str;