diff --git a/config/aliases.nix b/config/aliases.nix new file mode 100644 index 0000000..023f1ce --- /dev/null +++ b/config/aliases.nix @@ -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" ]; + }; +} diff --git a/config/default.nix b/config/default.nix index 396fefa..633eab5 100644 --- a/config/default.nix +++ b/config/default.nix @@ -1,16 +1,16 @@ { config, lib, pkgs, ... }: { - imports = [ - ./bash.nix - ./common.nix - ./domains.nix - ./groups.nix - ./hosts.nix - ./networks.nix - ./profiles.nix - ./sites.nix - ./users.nix - ./wireless-networks.nix - ]; + imports = [ + ./aliases.nix + ./bash.nix + ./common.nix + ./domains.nix + ./groups.nix + ./hosts.nix + ./networks.nix + ./sites.nix + ./users.nix + ./wireless-networks.nix + ]; } diff --git a/config/domain-config/eur.fudo.org.nix b/config/domain-config/eur.fudo.org.nix new file mode 100644 index 0000000..865d469 --- /dev/null +++ b/config/domain-config/eur.fudo.org.nix @@ -0,0 +1,5 @@ +{ config, lib, pkgs, ... }: + +{ + +} diff --git a/config/domains.nix b/config/domains.nix index ed37a4c..d46a497 100644 --- a/config/domains.nix +++ b/config/domains.nix @@ -3,7 +3,11 @@ { config.fudo.domains = { "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-groups = [ "fudo" "selby" "admin" ]; @@ -13,7 +17,12 @@ }; "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-groups = [ "fudo" "selby" "admin" ]; @@ -44,7 +53,9 @@ }; "informis.land" = { - local-networks = [ ]; + local-networks = [ + "172.86.179.17/29" + ]; local-users = [ "niten" "viator" ]; local-groups = [ "admin" "informis" ]; @@ -52,5 +63,19 @@ admin-email = "viator@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"; + }; }; } diff --git a/config/hardware/france.nix b/config/hardware/france.nix index 320db4d..ef7ffe3 100644 --- a/config/hardware/france.nix +++ b/config/hardware/france.nix @@ -3,8 +3,15 @@ { boot = { initrd = { - availableKernelModules = - [ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "floppy" "sd_mod" "sr_mod" ]; + availableKernelModules = [ + "uhci_hcd" + "ehci_pci" + "ata_piix" + "ahci" + "floppy" + "sd_mod" + "sr_mod" + ]; kernelModules = [ "dm-snapshot" ]; }; @@ -23,23 +30,27 @@ "/boot" = { device = "/dev/disk/by-label/france-boot"; fsType = "ext4"; + options = [ "noatime" "nodiratime" ]; }; "/" = { device = "/dev/disk/by-label/france-root"; fsType = "ext4"; + options = [ "noatime" "nodiratime" ]; }; "/var/lib/lxd/storage-pools/pool0" = { device = "/dev/disk/by-label/pool0"; fsType = "btrfs"; label = "pool0"; + options = [ "noatime" "nodiratime" "noexec" ]; }; "/var/lib/lxd/storage-pools/pool1" = { device = "/dev/france-user/fudo-user"; fsType = "btrfs"; label = "pool1"; + options = [ "noatime" "nodiratime" "noexec" ]; }; }; @@ -50,6 +61,8 @@ hardware.bluetooth.enable = false; networking = { + useDHCP = false; + macvlans = { intif0 = { interface = "enp4s0f1"; @@ -60,6 +73,11 @@ interface = "enp4s0f0"; mode = "bridge"; }; + + # extif1 = { + # interface = "enp4s0f0"; + # mode = "bridge"; + # }; }; interfaces = { @@ -72,6 +90,11 @@ # output of: echo france-extif0|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' 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"; + # }; }; }; } diff --git a/config/hardware/legatus.nix b/config/hardware/legatus.nix new file mode 100644 index 0000000..05d393a --- /dev/null +++ b/config/hardware/legatus.nix @@ -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"; + }; + }; + }; +} diff --git a/config/hardware/mail-container.nix b/config/hardware/mail-container.nix new file mode 100644 index 0000000..865d469 --- /dev/null +++ b/config/hardware/mail-container.nix @@ -0,0 +1,5 @@ +{ config, lib, pkgs, ... }: + +{ + +} diff --git a/config/hardware/socrates.nix b/config/hardware/socrates.nix index 1542c73..debd007 100644 --- a/config/hardware/socrates.nix +++ b/config/hardware/socrates.nix @@ -71,6 +71,7 @@ device = "/dev/disk/by-label/socrates-data"; fsType = "btrfs"; options = [ "subvol=@log" "compress=zstd" "noatime" "nodiratime" "noexec" ]; + neededForBoot = true; }; "/state" = { diff --git a/config/host-config/france.nix b/config/host-config/france.nix index 1054e35..ea23c12 100644 --- a/config/host-config/france.nix +++ b/config/host-config/france.nix @@ -9,6 +9,7 @@ let domain = config.fudo.domains.${domain-name}; host-fqdn = "${hostname}.${domain-name}"; mail-hostname = "mail.fudo.org"; + mail-directory = "/srv/mail"; secrets = config.fudo.secrets.host-secrets.france; secret-files = config.fudo.secrets.files; @@ -37,14 +38,73 @@ in { config = { 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" ]; acme.host-domains.france."france.fudo.org" = { email = "admin@fudo.org"; local-copies = { postgres = { - user = config.services.postgresql.user; + user = "postgres"; }; openldap = { user = config.services.openldap.user; @@ -56,26 +116,6 @@ in { ldap-user = config.services.openldap.user; ldap-group = config.services.openldap.group; 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 = { source-file = secret-files.service-keytabs.france.ldap; target-file = "/run/openldap/ldap.keytab"; @@ -83,7 +123,8 @@ in { group = ldap-group; }; 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"; user = ldap-user; group = ldap-group; @@ -91,7 +132,12 @@ in { postgres-keytab = { source-file = secret-files.service-keytabs.france.postgres; 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 = { 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" ]; }; jabber = { ldap-servers = [ "france.fudo.org" ]; listen-ips = [ primary-ip ]; - }; - backplane = { - host-passwd-files = let - hosts = attrNames config.fudo.hosts; - in mapAttrs (hostname: hostOpts: hostOpts.backplane-password-file) - config.fudo.hosts; - service-passwd-files = genAttrs [ "dns" ] - (service-name: - lib.fudo.passwd.stablerandom-passwd-file - "${service-name}-service-backplane-passwd" - "${service-name}-service-backplane-passwd-${config.instance.build-seed}"); + backplane = { + host-passwd-files = let + hosts = attrNames config.fudo.hosts; + in mapAttrs (hostname: hostOpts: hostOpts.backplane-password-file) + config.fudo.hosts; + service-passwd-files = { + dns = backplane-dns-password-file; + }; + }; }; backplane-server = { listen-ips = [ primary-ip ]; + backplane-dns-password-file = + secrets.backplane-dns-password.target-file; }; mail = { - mail-directory = "/srv/mail/mailboxes"; - state-directory = "/srv/mail/var"; + mail-directory = "${mail-directory}/mailboxes"; + state-directory = "${mail-directory}/var"; ldap-server-urls = [ "ldap://france.fudo.org" ]; @@ -169,6 +215,18 @@ in { ssl-certificate = cert-copy.certificate; 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 = { @@ -181,8 +239,6 @@ in { }; networking = { - useDHCP = false; - interfaces = { intif0 = { ipv4.addresses = [{ @@ -196,10 +252,6 @@ in { address = primary-ip; prefixLength = 28; } - { - address = git-server-ip; - prefixLength = 32; - } ]; }; }; @@ -218,6 +270,22 @@ in { enableACME = true; 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; + ''; + }; + }; }; }; }; diff --git a/config/host-config/france/auth.nix b/config/host-config/france/auth.nix index 648ee20..da029f0 100644 --- a/config/host-config/france/auth.nix +++ b/config/host-config/france/auth.nix @@ -11,8 +11,6 @@ let concatGenAttrs = lst: f: foldr (a0: a1: a0 // a1) {} (map f lst); - passwd = import ../../../lib/passwd.nix { inherit lib; }; - secrets = config.fudo.secrets.host-secrets.${hostname}; cfg = config.fudo.france; @@ -69,45 +67,47 @@ in { user = config.fudo.auth.kdc.user; }; - auth = { - ldap-server = { - enable = true; - base = "dc=fudo,dc=org"; - organization = "Fudo"; - rootpw-file = cfg.ldap.root-password-file; - kerberos-host = fqdn; - kerberos-keytab = cfg.ldap.keytab; - ssl-certificate = cfg.ldap.ssl-certificate; - ssl-private-key = cfg.ldap.ssl-private-key; - ssl-ca-certificate = cfg.ldap.ssl-ca-certificate; + # auth = { + # ldap-server = { + # enable = true; + # base = "dc=fudo,dc=org"; + # organization = "Fudo"; + # rootpw-file = cfg.ldap.root-password-file; + # kerberos-host = fqdn; + # kerberos-keytab = cfg.ldap.keytab; + # ssl-certificate = cfg.ldap.ssl-certificate; + # ssl-private-key = cfg.ldap.ssl-private-key; + # ssl-ca-certificate = cfg.ldap.ssl-ca-certificate; - listen-uris = [ "ldap:///" "ldaps:///" "ldapi:///" ]; + # listen-uris = [ "ldap:///" "ldaps:///" "ldapi:///" ]; - users = config.fudo.users; - groups = config.fudo.groups; - system-users = config.fudo.system-users; - }; + # users = config.fudo.users; + # groups = config.fudo.groups; + # system-users = config.fudo.system-users; - # TODO: let build hosts create keys? - kdc = { - enable = true; - realm = config.fudo.domains.${domain-name}.gssapi-realm; - state-directory = cfg.kdc.state-directory; - master-key-file = secrets.kdc-master-key.target-file; - acl = let - admin-entries = concatGenAttrs - config.instance.local-admins - (admin: { - "${admin}" = { perms = [ "add" "list" "change-password" ]; }; - "${admin}/root" = { perms = [ "all" ]; }; - }); - in { - "host/*.fudo.org" = { perms = [ "add" ]; }; - "pam_migrate/*.fudo.org" = { perms = [ "add" "change-password" ]; }; - } // admin-entries; - bind-addresses = cfg.kdc.listen-ips; - }; - }; + # database-directory = "/state/openldap"; + # }; + + # # TODO: let build hosts create keys? + # kdc = { + # enable = true; + # realm = config.fudo.domains.${domain-name}.gssapi-realm; + # state-directory = cfg.kdc.state-directory; + # master-key-file = secrets.kdc-master-key.target-file; + # acl = let + # admin-entries = concatGenAttrs + # config.instance.local-admins + # (admin: { + # "${admin}" = { perms = [ "add" "list" "change-password" ]; }; + # "${admin}/root" = { perms = [ "all" ]; }; + # }); + # in { + # "host/*.fudo.org" = { perms = [ "add" ]; }; + # "pam_migrate/*.fudo.org" = { perms = [ "add" "change-password" ]; }; + # } // admin-entries; + # bind-addresses = cfg.kdc.listen-ips; + # }; + # };o }; }; } diff --git a/config/host-config/france/backplane.nix b/config/host-config/france/backplane.nix index 3106570..2c1bcec 100644 --- a/config/host-config/france/backplane.nix +++ b/config/host-config/france/backplane.nix @@ -10,14 +10,12 @@ let backplane-dns-user = "backplane-dns"; generate-role-passwd = role: - lib.fudo.passwd.stablerandom-password-file + pkgs.lib.fudo.passwd.stablerandom-passwd-file "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"; - backplane-dns-xmpp-password = generate-role-passwd "backplane-dns-xmpp"; - backplane-dns-db-password = generate-role-passwd "backplane-dns-db"; secrets = config.fudo.secrets.host-secrets.france; @@ -36,6 +34,11 @@ in { description = "List of IPv6s on which to listen for incoming backplane connections."; default = []; }; + + backplane-dns-password-file = mkOption { + type = str; + description = "Path to file containing the password for connecting to the XMPP backplane."; + }; }; config = { @@ -64,19 +67,13 @@ in { powerdns-password = { source-file = powerdns-password; 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 = { source-file = backplane-dns-db-password; target-file = "/run/backplane/dns/db.passwd"; - user = config.fudo.backplane.dns.backplane.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; + user = config.fudo.backplane.dns.user; }; }; @@ -98,7 +95,7 @@ in { }; }; ${backplane-dns-user} = { - password-file = secrets.backplane-dns-db-password; + password-file = secrets.backplane-dns-db-password.target-file; databases = { backplane_dns = { access = "CONNECT"; @@ -134,7 +131,7 @@ in { backplane = { host = "backplane.fudo.org"; role = "service-dns"; - password-file = secrets.backplane-dns-xmpp-password.target-file; + password-file = cfg.backplane-dns-password-file; database = { username = backplane-dns-user; database = backplane-dns-user; diff --git a/config/host-config/france/chat.nix b/config/host-config/france/chat.nix new file mode 100644 index 0000000..24841d9 --- /dev/null +++ b/config/host-config/france/chat.nix @@ -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; + }; + }; + }; + }; +} diff --git a/config/host-config/france/dns.nix b/config/host-config/france/dns.nix new file mode 100644 index 0000000..b64eb77 --- /dev/null +++ b/config/host-config/france/dns.nix @@ -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; + }; + }; + }; + }; + }; +} diff --git a/config/host-config/france/git.nix b/config/host-config/france/git.nix index 0e8bcb8..d7d65b2 100644 --- a/config/host-config/france/git.nix +++ b/config/host-config/france/git.nix @@ -46,7 +46,7 @@ in { config.fudo = { 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" "${hostname}-gitea-database-passwd-${config.instance.build-seed}"; target-file = "/var/gitea/database.passwd"; @@ -55,7 +55,7 @@ in { postgresql = { databases.fudo_git.users = - config.instance.local_admins; + config.instance.local-admins; users.fudo_git = { password-file = diff --git a/config/host-config/france/jabber.nix b/config/host-config/france/jabber.nix index fbd15a3..5b2cbdc 100644 --- a/config/host-config/france/jabber.nix +++ b/config/host-config/france/jabber.nix @@ -5,23 +5,23 @@ let hostname = config.instance.hostname; secrets = config.fudo.secrets.host-secrets.${hostname}; - cfg = config.fudo.france; + cfg = config.fudo.france.jabber; generate-auth-file = name: files: let make-entry = name: passwd-file: ''("${name}" . "${readFile passwd-file}")''; entries = mapAttrsToList make-entry files; 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; - service-auth-file = generate-auth-filre "service" cfg.service-passwd-files; + host-auth-file = generate-auth-file "host" cfg.backplane.host-passwd-files; + service-auth-file = generate-auth-file "service" cfg.backplane.service-passwd-files; 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 = - 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 { options.fudo.france = with types; { @@ -41,28 +41,28 @@ in { type = listOf str; description = "IPs on which to listen for incoming connections."; }; - }; - backplane = { - host-passwd-files = mkOption { - type = attrsOf str; - description = "Map of hostname to password file, for backplane host authentication."; - default = {}; - }; + backplane = { + host-passwd-files = mkOption { + type = attrsOf str; + description = "Map of hostname to password file, for backplane host authentication."; + default = {}; + }; - service-passwd-files = mkOption { - type = attrsOf str; - description = "Map of service to password file, for backplane service authentication."; - default = {}; + service-passwd-files = mkOption { + type = attrsOf str; + description = "Map of service to password file, for backplane service authentication."; + default = {}; + }; }; }; }; config = { fudo = { - system-users.${cfg.jabber.ldap-user} = { + system-users.${cfg.ldap-user} = { description = "ejabberd authentication user."; - hashed-password = ldap-hashed-password; + ldap-hashed-password = ldap-hashed-password; }; secrets.host-secrets.${hostname} = let @@ -88,7 +88,7 @@ in { jabber = { enable = true; - listen-ips = cfg.jabber.listen-ips; + listen-ips = cfg.listen-ips; environment = { FUDO_HOST_PASSWD_FILE = secrets.host-auth.target-file; @@ -103,9 +103,9 @@ in { "fudo.im" = { site-config = { auth_method = "ldap"; - ldap_servers = cfg.jabber.ldap-servers; + ldap_servers = cfg.ldap-servers; 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_base = "ou=members,dc=fudo,dc=org"; ldap_filter = "(objectClass=posixAccount)"; diff --git a/config/host-config/france/mail-server.nix b/config/host-config/france/mail-server.nix index 52e2202..76d1b9b 100644 --- a/config/host-config/france/mail-server.nix +++ b/config/host-config/france/mail-server.nix @@ -5,6 +5,8 @@ let hostname = config.instance.hostname; domain-name = config.instance.local-domain; + cfg = config.fudo.france.mail; + secrets = config.fudo.secrets.host-secrets.${hostname}; mail-reader-dn = "mail-auth-reader"; @@ -26,35 +28,63 @@ in { }; }; - config.fudo = { - system-users = { - username = mail-reader-dn; + config.fudo = let + mail-reader-password = + 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."; ldap-hashed-password = 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; monitoring = true; domain = domain-name; mail-hostname = "mail.${domain-name}"; + trusted-networks = config.instance.local-networks; + dovecot = { ldap = { - reader-dn = "cn=${mail-reader-dn},${config.fudo.auth.ldap.base}"; - reader-password-file = secrets.mail-reader-passwd.target-file; + reader-dn = "cn=${mail-reader-dn},${config.fudo.authentication.base}"; + reader-password-file = mail-reader-password; 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; mail-directory = cfg.mail-directory; clamav.enable = true; dkim.signing = true; + + ssl = { + certificate = ssl-certificate; + private-key = ssl-private-key; + }; }; }; } diff --git a/config/host-config/france/postgres.nix b/config/host-config/france/postgres.nix index 05a6009..7d18afc 100644 --- a/config/host-config/france/postgres.nix +++ b/config/host-config/france/postgres.nix @@ -4,6 +4,7 @@ with lib; let hostname = config.instance.hostname; secrets = config.fudo.secrets.host-secrets.${hostname}; + cfg = config.fudo.france.postgresql; in { options.fudo.france.postgresql = with types; { ssl-certificate = mkOption { diff --git a/config/host-config/france/webmail.nix b/config/host-config/france/webmail.nix index 7f283f1..d5a6b05 100644 --- a/config/host-config/france/webmail.nix +++ b/config/host-config/france/webmail.nix @@ -8,11 +8,10 @@ let 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; - - db-host = config.france.webmail.database.hostname; + cfg = config.fudo.france.webmail; db-passwd = pkgs.lib.fudo.passwd.random-passwd-file "webmail" 40; @@ -39,12 +38,12 @@ in { "webmail.fudo.link" = { title = "Fudo Link Webmail"; favicon = "${static}/fudo.link/favicon.ico"; - mail-server = mail-hostname; + mail-server = cfg.mail-server; domain = "fudo.link"; edit-mode = "Plain"; layout-mode = "bottom"; database = { - hostname = db-host; + hostname = cfg.database.hostname; password-file = db-passwd; }; }; @@ -52,11 +51,11 @@ in { "webmail.test.fudo.org" = { title = "Fudo Webmail"; favicon = "${static}/fudo.org/favicon.ico"; - mail-server = mail-hostname; + mail-server = cfg.mail-server; domain = "fudo.org"; edit-mode = "Plain"; database = { - hostname = db-host; + hostname = cfg.database.hostname; password-file = db-passwd; }; }; @@ -64,11 +63,11 @@ in { "webmail.fudo.org" = { title = "Fudo Webmail"; favicon = "${static}/fudo.org/favicon.ico"; - mail-server = mail-hostname; + mail-server = cfg.mail-server; domain = "fudo.org"; edit-mode = "Plain"; database = { - hostname = db-host; + hostname = cfg.database.hostname; password-file = db-passwd; }; }; @@ -76,10 +75,10 @@ in { "webmail.test.selby.ca" = { title = "Selby Webmail"; favicon = "${static}/selby.ca/favicon.ico"; - mail-server = mail-hostname; + mail-server = cfg.mail-server; domain = "selby.ca"; database = { - hostname = db-host; + hostname = cfg.database.hostname; password-file = db-passwd; }; }; @@ -87,10 +86,10 @@ in { "webmail.selby.ca" = { title = "Selby Webmail"; favicon = "${static}/selby.ca/favicon.ico"; - mail-server = mail-hostname; + mail-server = cfg.mail-server; domain = "selby.ca"; database = { - hostname = db-host; + hostname = cfg.database.hostname; password-file = db-passwd; }; }; diff --git a/config/host-config/legatus.nix b/config/host-config/legatus.nix new file mode 100644 index 0000000..95afa7d --- /dev/null +++ b/config/host-config/legatus.nix @@ -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" + # ]; + # }; + }; +} diff --git a/config/host-config/mail-container.nix b/config/host-config/mail-container.nix new file mode 100644 index 0000000..865d469 --- /dev/null +++ b/config/host-config/mail-container.nix @@ -0,0 +1,5 @@ +{ config, lib, pkgs, ... }: + +{ + +} diff --git a/config/host-config/nostromo.nix b/config/host-config/nostromo.nix index b4cf48f..00706d3 100644 --- a/config/host-config/nostromo.nix +++ b/config/host-config/nostromo.nix @@ -50,11 +50,12 @@ in { ]; }; - fudo.ipfs = { - enable = true; - users = [ "niten" ]; - api-address = "/ip4/0.0.0.0/tcp/5001"; - }; + ## Until I can figure out how to use one common host API, forget this + # fudo.ipfs = { + # enable = true; + # users = [ "niten" ]; + # api-address = "/ip4/0.0.0.0/tcp/5001"; + # }; virtualisation = { libvirtd = { diff --git a/config/host-config/procul.nix b/config/host-config/procul.nix index 2906aa2..e1a7f76 100644 --- a/config/host-config/procul.nix +++ b/config/host-config/procul.nix @@ -164,7 +164,7 @@ in { debug = true; domain = domain-name; - hostname = "${host-fqdn}"; + mail-hostname = "${host-fqdn}"; monitoring = false; mail-user = "mailuser"; mail-user-id = 525; diff --git a/config/hosts/legatus.nix b/config/hosts/legatus.nix new file mode 100644 index 0000000..e116500 --- /dev/null +++ b/config/hosts/legatus.nix @@ -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"; + # }; + # }; +} diff --git a/config/hosts/mail-container.nix b/config/hosts/mail-container.nix new file mode 100644 index 0000000..a6f0ffe --- /dev/null +++ b/config/hosts/mail-container.nix @@ -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; +} diff --git a/config/hosts/procul.nix b/config/hosts/procul.nix index a904c9e..6cb3d10 100644 --- a/config/hosts/procul.nix +++ b/config/hosts/procul.nix @@ -1,8 +1,8 @@ { description = "informis.land server."; docker-server = true; - rp = "niten"; - admin-email = "niten@fudo.org"; + rp = "viator"; + admin-email = "viator@fudo.org"; domain = "informis.land"; site = "joes-datacenter-0"; profile = "server"; diff --git a/config/networks.nix b/config/networks.nix index cfc4912..f1d2fd1 100644 --- a/config/networks.nix +++ b/config/networks.nix @@ -2,8 +2,10 @@ { config.fudo.networks = { + "fudo.org" = import ./networks/fudo.org.nix; "rus.selby.ca" = import ./networks/rus.selby.ca.nix; "sea.fudo.org" = import ./networks/sea.fudo.org.nix; "informis.land" = import ./networks/informis.land.nix; + "eur.fudo.org" = import ./networks/eur.fudo.org.nix; }; } diff --git a/config/networks/eur.fudo.org.nix b/config/networks/eur.fudo.org.nix new file mode 100644 index 0000000..0981ce8 --- /dev/null +++ b/config/networks/eur.fudo.org.nix @@ -0,0 +1,7 @@ +{ + mx = [ "mail.fudo.org" ]; + + hosts = { + legatus.ipv4-address = "91.229.23.204"; + }; +} diff --git a/config/networks/fudo.org.nix b/config/networks/fudo.org.nix index 11fe827..b6f537d 100644 --- a/config/networks/fudo.org.nix +++ b/config/networks/fudo.org.nix @@ -1,8 +1,4 @@ { - mx = [ "mail.fudo.org" ]; - - default-host = "208.81.3.117"; - aliases = { pop = "mail.fudo.org."; smtp = "mail.fudo.org."; @@ -27,13 +23,11 @@ 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 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 = { tcp = { domain = [ diff --git a/config/profile-config/build/build-seed.nix b/config/profile-config/build/build-seed.nix new file mode 100644 index 0000000..50b7a99 --- /dev/null +++ b/config/profile-config/build/build-seed.nix @@ -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; + }; +} diff --git a/config/profile-config/common.nix b/config/profile-config/common.nix index 1c486f8..6b042f3 100644 --- a/config/profile-config/common.nix +++ b/config/profile-config/common.nix @@ -14,131 +14,154 @@ let wget ]; + import-paths = [ + ./build + ./host + ./user + ]; + 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 = '' - # ${pkgs.gnupg}/bin/gpg-connect-agent /bye - # export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket) - # ''; - }; + config = { + environment = { + etc.nixos-live.source = ../../.; - system.autoUpgrade.enable = false; + systemPackages = global-packages; - nix = { - package = pkgs.nixFlakes; - extraOptions = '' + # shellInit = '' + # ${pkgs.gnupg}/bin/gpg-connect-agent /bye + # 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 ''; - }; - - nixpkgs.config.allowUnfree = true; - security.acme.acceptTerms = true; - hardware.enableRedistributableFirmware = true; - - krb5 = { - enable = true; - - appdefaults = { - forwardable = true; - proxiable = true; - encrypt = true; - forward = true; }; - libdefaults = { - allow_weak_crypto = true; - dns_lookup_kdc = true; - dns_lookup_realm = true; - forwardable = true; - proxiable = true; - }; + nixpkgs.config.allowUnfree = true; + security.acme.acceptTerms = true; + hardware.enableRedistributableFirmware = true; - kerberos = pkgs.heimdalFull; - }; - - services = { - openssh = { + krb5 = { enable = true; - startWhenNeeded = true; - useDns = true; - permitRootLogin = "prohibit-password"; - extraConfig = '' + + appdefaults = { + forwardable = true; + 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 GSSAPICleanupCredentials yes GSSAPIKeyExchange 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 - domain-name = config.fudo.hosts.${config.instance.hostname}.domain; - in { - enable = config.networking.firewall.enable; - bantime-increment.enable = true; - ignoreIP = config.instance.local-networks; + networking.firewall = { + # Allow mosh connections if the firewall is enabled + allowedUDPPortRanges = [{ + from = 60000; + to = 60100; + }]; }; - xserver = { - layout = "us"; - xkbVariant = "dvp"; - xkbOptions = "ctrl:nocaps"; - }; + console.useXkbConfig = true; - # pcscd.enable = true; - # udev.packages = with pkgs; [ yubikey-personalization ]; - }; + i18n.defaultLocale = "en_US.UTF-8"; - networking.firewall = { - # Allow mosh connections if the firewall is enabled - allowedUDPPortRanges = [{ - from = 60000; - to = 60100; - }]; - }; + programs = { + mosh.enable = true; - console.useXkbConfig = true; + bash.enableCompletion = true; - i18n.defaultLocale = "en_US.UTF-8"; + fish.enable = true; - programs = { - mosh.enable = true; + gnupg.agent = { + 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 = { - enable = true; - # enableSSHSupport = true; - # pinentryFlavor = if cfg.enable-gui then "gnome3" else "curses"; - }; - - ssh = { - startAgent = true; - - package = pkgs.openssh_gssapi; - - extraConfig = '' + extraConfig = '' GSSAPIAuthentication 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; + }; }; } diff --git a/config/profile-config/container.nix b/config/profile-config/container.nix new file mode 100644 index 0000000..a9245d8 --- /dev/null +++ b/config/profile-config/container.nix @@ -0,0 +1,7 @@ +{ config, lib, pkgs, ... }: + +{ + config = { + + }; +} diff --git a/config/profile-config/host/backplane-client.nix b/config/profile-config/host/backplane-client.nix new file mode 100644 index 0000000..2fc6f26 --- /dev/null +++ b/config/profile-config/host/backplane-client.nix @@ -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; + }; +} diff --git a/config/profile-config/host/kerberos.nix b/config/profile-config/host/kerberos.nix new file mode 100644 index 0000000..634ddb6 --- /dev/null +++ b/config/profile-config/host/kerberos.nix @@ -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"; + }; + }; + }; +} diff --git a/config/profile-config/host/ssh.nix b/config/profile-config/host/ssh.nix new file mode 100644 index 0000000..0163690 --- /dev/null +++ b/config/profile-config/host/ssh.nix @@ -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; + }); +} diff --git a/config/profile-config/server.nix b/config/profile-config/server.nix index 4183d7c..441927a 100644 --- a/config/profile-config/server.nix +++ b/config/profile-config/server.nix @@ -45,7 +45,7 @@ in { config = { environment = { - serverPackages = with pkgs; + systemPackages = with pkgs; [ emacs-nox reboot-if-necessary test-config ]; }; diff --git a/config/profile-config/user/ipfs.nix b/config/profile-config/user/ipfs.nix new file mode 100644 index 0000000..819c30a --- /dev/null +++ b/config/profile-config/user/ipfs.nix @@ -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; + }; +} diff --git a/config/profile-config/user/kerberos.nix b/config/profile-config/user/kerberos.nix new file mode 100644 index 0000000..a78ecf8 --- /dev/null +++ b/config/profile-config/user/kerberos.nix @@ -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; + }; +} diff --git a/config/site-config/seattle.nix b/config/site-config/seattle.nix index 2bd9fc9..9d7b7fb 100644 --- a/config/site-config/seattle.nix +++ b/config/site-config/seattle.nix @@ -3,16 +3,16 @@ let local-domain = "sea.fudo.org"; in { fileSystems = { - "/mnt/documents" = { - device = "whitedwarf.${local-domain}:/volume1/Documents"; - fsType = "nfs4"; - options = [ "comment=systemd.automount" ]; - }; - "/mnt/downloads" = { - device = "whitedwarf.${local-domain}:/volume1/Downloads"; - fsType = "nfs4"; - options = [ "comment=systemd.automount" ]; - }; + # "/mnt/documents" = { + # device = "whitedwarf.${local-domain}:/volume1/Documents"; + # fsType = "nfs4"; + # options = [ "comment=systemd.automount" ]; + # }; + # "/mnt/downloads" = { + # device = "whitedwarf.${local-domain}:/volume1/Downloads"; + # fsType = "nfs4"; + # options = [ "comment=systemd.automount" ]; + # }; "/mnt/music" = { device = "doraemon.${local-domain}:/volume1/Music"; fsType = "nfs4"; diff --git a/config/sites.nix b/config/sites.nix index 73e1650..9423a8a 100644 --- a/config/sites.nix +++ b/config/sites.nix @@ -59,5 +59,17 @@ ]; 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"; + }; }; } diff --git a/config/users.nix b/config/users.nix index ed3c2bf..280f476 100644 --- a/config/users.nix +++ b/config/users.nix @@ -27,6 +27,20 @@ in { "niten/root@RUS.SELBY.CA" ]; 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 = { @@ -99,6 +113,7 @@ in { ldap-hashed-passwd = "{SSHA}YvtkEpqsReXcMdrzlui/ZmhIUKN42YO1"; login-hashed-passwd = "$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf."; + email-aliases = [ "kselby@selby.ca" ]; }; reaper = { @@ -112,6 +127,12 @@ in { k5login = [ "reaper@FUDO.ORG" "reaper/root@FUDO.ORG" "reaper/admin@FUDO.ORG" ]; email = "reaper@fudo.org"; + email-aliases = [ + "cricket@fudo.org" + "jstewart@fudo.org" + "jonathan@fudo.org" + "reaper@fudo.link" + ]; }; slickoil = { @@ -133,6 +154,7 @@ in { primary-group = "fudo"; common-name = "Mark Swaffer"; ldap-hashed-passwd = "{MD5}C5gIsLsaKSvIPydu4uzhNg=="; + email-aliases = [ "mark@fudo.org" ]; }; brian = { @@ -192,6 +214,13 @@ in { login-hashed-passwd = "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; email = "xiaoxuan@fudo.org"; + email-aliases = [ + "xixi@fudo.org" + "claire@fudo.org" + + "xixi@selby.ca" + "claire@selby.ca" + ]; }; thibor = { @@ -311,6 +340,9 @@ in { primary-group = "selby"; common-name = "Vee Selby"; ldap-hashed-passwd = "snoinuer"; + email-aliases = [ + "virginia@selby.ca" + ]; }; dabar = { diff --git a/lib/fudo/acme-certs.nix b/lib/fudo/acme-certs.nix index 6af407d..58b5766 100644 --- a/lib/fudo/acme-certs.nix +++ b/lib/fudo/acme-certs.nix @@ -2,34 +2,6 @@ with lib; 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 domain = name; in { @@ -140,14 +112,14 @@ in { perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500"; copy-paths = mapAttrsToList (copy: copyOpts: 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) [ copyOpts.certificate copyOpts.full-certificate copyOpts.chain copyOpts.private-key ]) copies; - in unique copy-paths; + in unique (concatMap (i: unique i) copy-paths); services = concatMapAttrs (domain: domainOpts: mapAttrs' (copy: copyOpts: let @@ -169,7 +141,7 @@ in { ''; remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" '' rm -f ${copyOpts.private-key} - rm -f ${copyOpts.chainy} + rm -f ${copyOpts.chain} rm -f ${copyOpts.full-certificate} rm -f ${copyOpts.certificate} ''; diff --git a/lib/fudo/backplane/dns.nix b/lib/fudo/backplane/dns.nix index 759f630..c9dda2a 100644 --- a/lib/fudo/backplane/dns.nix +++ b/lib/fudo/backplane/dns.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.fudo.backplane.dns; - powerdns-conf-dir = "${cfg.powerdns-home}/conf.d"; + powerdns-conf-dir = "${cfg.powerdns.home}/conf.d"; backplaneOpts = { ... }: { options = { @@ -90,6 +90,7 @@ in { type = listOf str; description = "A list of services required before the DNS server can start."; + default = [ ]; }; user = mkOption { @@ -109,16 +110,24 @@ in { description = "Database settings for the DNS server."; }; - powerdns-home = mkOption { - type = str; - description = "Directory at which to store powerdns configuration and state."; - default = "/run/backplane-dns/powerdns"; - }; - backplane = mkOption { type = submodule backplaneOpts; 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 { @@ -130,16 +139,16 @@ in { createHome = true; home = "/var/home/${cfg.user}"; }; - backplane-powerdns = { + ${cfg.powerdns.user} = { isSystemUser = true; - home = cfg.powerdns-home; + home = cfg.powerdns.home; createHome = true; }; }; groups = { "${cfg.group}" = { members = [ cfg.user ]; }; - backplane-powerdns = { members = [ "backplane-powerdns" ]; }; + ${cfg.powerdns.user} = { members = [ cfg.powerdns.user ]; }; }; }; @@ -156,7 +165,7 @@ in { preStart = '' 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 @@ -175,7 +184,7 @@ in { fi touch $TMPCONF - chown backplane-powerdns:backplane-powerdns $TMPCONF + chown ${cfg.powerdns.user}:${cfg.powerdns.user} $TMPCONF chmod go-rwx $TMPCONF PASSWORD=$(cat ${cfg.database.password-file}) 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 = { description = "Fudo DNS Backplane Server"; restartIfChanged = true; @@ -220,7 +210,7 @@ in { user = cfg.user; group = cfg.group; partOf = [ "backplane-dns.target" ]; - requires = [ "postgresql.service" ]; + requires = cfg.required-services ++ [ "postgresql.service" ]; environment = { FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; @@ -243,7 +233,30 @@ in { backplane-dns = { description = "Fudo DNS backplane services."; 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}"; + }; }; }; }; diff --git a/lib/fudo/chat.nix b/lib/fudo/chat.nix index 121d46b..b885373 100644 --- a/lib/fudo/chat.nix +++ b/lib/fudo/chat.nix @@ -1,67 +1,71 @@ { pkgs, lib, config, ... }: with lib; -let cfg = config.fudo.chat; +let + cfg = config.fudo.chat; + mattermost-config-target = "/run/chat/mattermost/mattermost-config.json"; in { - options.fudo.chat = { + options.fudo.chat = with types; { enable = mkEnableOption "Enable chat server"; hostname = mkOption { - type = types.str; + type = str; description = "Hostname at which this chat server is accessible."; example = "chat.mydomain.com"; }; site-name = mkOption { - type = types.str; + type = str; description = "The name of this chat server."; example = "My Fancy Chat Site"; }; - smtp-server = mkOption { - type = types.str; - description = "SMTP server to use for sending notification emails."; - example = "mail.my-site.com"; - }; + smtp = { + server = mkOption { + type = str; + description = "SMTP server to use for sending notification emails."; + example = "mail.my-site.com"; + }; - smtp-user = mkOption { - type = types.str; - description = "Username with which to connect to the SMTP server."; - }; + user = mkOption { + type = str; + description = "Username with which to connect to the SMTP server."; + }; - smtp-password-file = mkOption { - type = types.str; - description = - "Path to a file containing the password to use while connecting to the SMTP server."; + password-file = mkOption { + type = str; + description = + "Path to a file containing the password to use while connecting to the SMTP server."; + }; }; state-directory = mkOption { - type = types.str; + type = str; description = "Path at which to store server state data."; default = "/var/lib/mattermost"; }; database = mkOption { - type = (types.submodule { + type = (submodule { options = { name = mkOption { - type = types.str; + type = str; description = "Database name."; }; hostname = mkOption { - type = types.str; + type = str; description = "Database host."; }; user = mkOption { - type = types.str; + type = str; description = "Database user."; }; password-file = mkOption { - type = types.str; + type = str; description = "Path to file containing database password."; }; }; @@ -85,11 +89,11 @@ in { TeamSettings.SiteName = cfg.site-name; EmailSettings = { RequireEmailVerification = true; - SMTPServer = cfg.smtp-server; + SMTPServer = cfg.smtp.server; SMTPPort = 587; EnableSMTPAuth = true; - SMTPUsername = cfg.smtp-user; - SMTPPassword = (fileContents cfg.smtp-password-file); + SMTPUsername = cfg.smtp.user; + SMTPPassword = "__SMTP_PASSWD__"; SendEmailNotifications = true; ConnectionSecurity = "STARTTLS"; FeedbackEmail = "chat@fudo.org"; @@ -97,15 +101,26 @@ in { }; EnableEmailInvitations = true; SqlSettings.DriverName = "postgres"; - SqlSettings.DataSource = "postgres://${cfg.database.user}:${ - fileContents cfg.database.password-file - }@${cfg.database.hostname}:5432/${cfg.database.name}"; + SqlSettings.DataSource = "postgres://${ + cfg.database.user + }:__DATABASE_PASSWORD__@${ + cfg.database.hostname + }:5432/${ + cfg.database.name + }"; }; - mattermost-config-file = - pkgs.writeText "mattermost-config.json" (builtins.toJSON modified-config); + mattermost-config-file-template = + pkgs.writeText "mattermost-config.json.template" (builtins.toJSON modified-config); mattermost-user = "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 { users = { users = { @@ -118,48 +133,75 @@ in { groups = { ${mattermost-group} = { members = [ mattermost-user ]; }; }; }; - system.activationScripts.mattermost = '' - mkdir -p ${cfg.state-directory} - ''; - - systemd.services.mattermost = { + fudo.system.services.mattermost = { description = "Mattermost Chat Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; preStart = '' - mkdir -p ${cfg.state-directory}/config - cp ${mattermost-config-file} ${cfg.state-directory}/config/config.json - ln -sf ${pkg}/bin ${cfg.state-directory} - ln -sf ${pkg}/fonts ${cfg.state-directory} - ln -sf ${pkg}/i18n ${cfg.state-directory} - ln -sf ${pkg}/templates ${cfg.state-directory} + ${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 -R ${mattermost-user}:${mattermost-group} ${cfg.state-directory} - chmod u+w -R ${cfg.state-directory}/client - chmod o-rwx -R ${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; - }; + execStart = "${pkg}/bin/mattermost"; + workingDirectory = cfg.state-directory; + 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 = { enable = true; 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 = { "${cfg.hostname}" = { @@ -170,48 +212,48 @@ in { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' - client_max_body_size 50M; - proxy_set_header Connection ""; - 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 $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Frame-Options SAMEORIGIN; - proxy_buffers 256 16k; - proxy_buffer_size 16k; - proxy_read_timeout 600s; - proxy_cache mattermost_cache; - proxy_cache_revalidate on; - proxy_cache_min_uses 2; - proxy_cache_use_stale timeout; - proxy_cache_lock on; - proxy_http_version 1.1; - ''; + client_max_body_size 50M; + proxy_set_header Connection ""; + 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 $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_cache mattermost_cache; + proxy_cache_revalidate on; + proxy_cache_min_uses 2; + proxy_cache_use_stale timeout; + proxy_cache_lock on; + proxy_http_version 1.1; + ''; }; locations."~ /api/v[0-9]+/(users/)?websocket$" = { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - client_max_body_size 50M; - 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 $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Frame-Options SAMEORIGIN; - proxy_buffers 256 16k; - proxy_buffer_size 16k; - client_body_timeout 60; - send_timeout 300; - lingering_timeout 5; - proxy_connect_timeout 90; - proxy_send_timeout 300; - proxy_read_timeout 90s; - ''; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 50M; + 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 $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + client_body_timeout 60; + send_timeout 300; + lingering_timeout 5; + proxy_connect_timeout 90; + proxy_send_timeout 300; + proxy_read_timeout 90s; + ''; }; }; }; diff --git a/lib/fudo/hosts.nix b/lib/fudo/hosts.nix index f9ce3a5..2c3a018 100644 --- a/lib/fudo/hosts.nix +++ b/lib/fudo/hosts.nix @@ -8,7 +8,14 @@ let 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 { options.fudo.hosts = with types; @@ -37,9 +44,12 @@ in { #defaultGateway = site.gateway-v4; #defaultGateway6 = site.gateway-v6; - firewall = { - enable = (length host-cfg.external-interfaces) > 0; - allowedTCPPorts = [ 22 ]; + firewall = mkIf ((length host-cfg.external-interfaces) > 0) { + enable = true; + 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) @@ -79,6 +89,8 @@ in { in concatStringsSep "\n" sorted-unique; build-timestamp.text = toString config.instance.build-timestamp; + build-seed-hash.source = + generate-string-hash "build-seed" config.instance.build-seed; }; systemPackages = with pkgs; @@ -89,7 +101,10 @@ in { 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) { enable = true; @@ -97,56 +112,11 @@ in { 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; users.groups.adbusers = mkIf host-cfg.android-dev { members = config.instance.local-admins; }; 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; - }; }; } diff --git a/lib/fudo/ipfs.nix b/lib/fudo/ipfs.nix index 23497dc..b60ac0c 100644 --- a/lib/fudo/ipfs.nix +++ b/lib/fudo/ipfs.nix @@ -7,9 +7,6 @@ let user-group-entry = group: user: nameValuePair user { extraGroups = [ group ]; }; - user-home-entry = ipfs-path: user: - nameValuePair user { home.sessionVariables = { IPFS_PATH = ipfs-path; }; }; - in { options.fudo.ipfs = with types; { enable = mkEnableOption "Fudo IPFS"; @@ -53,7 +50,8 @@ in { 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 = { enable = true; @@ -64,8 +62,5 @@ in { group = cfg.group; dataDir = cfg.data-dir; }; - - home-manager.users = - listToAttrs (map (user-home-entry cfg.data-dir) cfg.users); }; } diff --git a/lib/fudo/ldap.nix b/lib/fudo/ldap.nix index 1ca3dd4..31ed896 100644 --- a/lib/fudo/ldap.nix +++ b/lib/fudo/ldap.nix @@ -5,100 +5,7 @@ let cfg = config.fudo.auth.ldap-server; - ldapSystemUserOpts = { name, ... }: { - 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 = ""; - }; - }; - }; + user-type = import ../types/user.nix { inherit lib; }; stringJoin = joiner: attrList: if (length attrList) == 0 then @@ -107,15 +14,15 @@ let foldr (lAttr: rAttr: "${lAttr}${joiner}${rAttr}") (last 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; mkHomeDir = username: user-opts: - if (user-opts.group == "admin") then + if (user-opts.primary-group == "admin") then "/home/${username}" else - "/home/${user-opts.group}/${username}"; + "/home/${user-opts.primary-group}/${username}"; userLdif = base: name: group-map: opts: '' dn: uid=${name},ou=members,${base} @@ -131,7 +38,7 @@ let shadowLastChange: 12230 shadowMax: 99999 shadowWarning: 7 - userPassword: ${opts.hashed-password} + userPassword: ${opts.ldap-hashed-passwd} ''; systemUserLdif = base: name: opts: '' @@ -140,7 +47,7 @@ let objectClass: simpleSecurityObject cn: ${name} description: ${opts.description} - userPassword: ${opts.hashed-password} + userPassword: ${opts.ldap-hashed-password} ''; toMemberList = userList: @@ -242,7 +149,7 @@ in { }; users = mkOption { - type = attrsOf (submodule ldapUserOpts); + type = attrsOf (submodule user-type.userOpts); example = { tester = { uid = 10099; @@ -258,7 +165,7 @@ in { groups = mkOption { default = { }; - type = attrsOf (submodule ldapGroupOpts); + type = attrsOf (submodule user-type.groupOpts); example = { admin = { gid = 1099; @@ -272,16 +179,19 @@ in { system-users = mkOption { default = { }; - type = attrsOf (submodule ldapSystemUserOpts); + type = attrsOf (submodule user-type.systemUserOpts); example = { replicator = { description = "System user for database sync"; - hashed-password = ""; + ldap-hashed-password = ""; }; }; - description = '' - System users to be added to the Fudo LDAP database. - ''; + description = "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 = { enable = true; - suffix = cfg.base; - rootdn = "cn=admin,${cfg.base}"; - rootpwFile = "${cfg.rootpw-file}"; urlList = cfg.listen-uris; - database = "mdb"; settings = let - makeAccessLine = i: attrs: perm-map: let - perm-strings = mapAttrs (dn: perm: "by ${dn} ${perm}") perm-map; - perm-string = concatStringsSep " " perm-strings; - in "${i}to ${attrs} ${perm-string}"; + makePermEntry = dn: perm: "by ${dn} ${perm}"; + + makeAccessLine = target: perm-map: let + perm-entries = mapAttrsToList makePermEntry perm-map; + in "to ${target} ${concatStringsSep " " perm-entries} done"; makeAccess = access-map: let - pairs = mapAttrsToList (target: perm-map: [target perm-map]) access-map; - in imap0 (i: pair: makeAccessLine i pair[0] pair[1]) pairs; + access-lines = mapAttrsToList makeAccessLine; + numbered-access-lines = imap0 (i: line: "{${toString i}}${line}"); + in numbered-access-lines (access-lines access-map); in { attrs = { @@ -363,8 +271,8 @@ in { olcTLSCACertificateFile = cfg.ssl-ca-certificate; olcSaslSecProps = "noplain,noanonymous"; olcAuthzRegexp = let - authz-regex-entry = i: { regex, target}: - "{${i}}\"${rx}\" \"${target}\""; + authz-regex-entry = i: { regex, target }: + "{${toString i}}\"${regex}\" \"${target}\""; in imap0 authz-regex-entry [ { regex = "^uid=auth/([^.]+).fudo.org,cn=fudo.org,cn=gssapi,cn=auth$"; @@ -396,7 +304,7 @@ in { olcAccess = makeAccess { "*" = { "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"; }; }; @@ -462,30 +370,32 @@ in { }; }; - declarativeContents = '' - dn: ${cfg.base} - objectClass: top - objectClass: dcObject - objectClass: organization - o: ${cfg.organization} + declarativeContents = { + "dc=fudo,dc=org" = '' + dn: ${cfg.base} + objectClass: top + objectClass: dcObject + objectClass: organization + o: ${cfg.organization} - dn: ou=groups,${cfg.base} - objectClass: organizationalUnit - description: ${cfg.organization} groups + dn: ou=groups,${cfg.base} + objectClass: organizationalUnit + description: ${cfg.organization} groups - dn: ou=members,${cfg.base} - objectClass: organizationalUnit - description: ${cfg.organization} members + dn: ou=members,${cfg.base} + objectClass: organizationalUnit + description: ${cfg.organization} members - dn: cn=admin,${cfg.base} - objectClass: organizationalRole - cn: admin - description: "Admin User" + dn: cn=admin,${cfg.base} + objectClass: organizationalRole + cn: admin + description: "Admin User" - ${systemUsersLdif cfg.base cfg.system-users} - ${groupsLdif cfg.base cfg.groups} - ${usersLdif cfg.base cfg.groups cfg.users} - ''; + ${systemUsersLdif cfg.base cfg.system-users} + ${groupsLdif cfg.base cfg.groups} + ${usersLdif cfg.base cfg.groups cfg.users} + ''; + }; }; }; } diff --git a/lib/fudo/mail-container.nix b/lib/fudo/mail-container.nix index 1b7ed5a..8a890ab 100644 --- a/lib/fudo/mail-container.nix +++ b/lib/fudo/mail-container.nix @@ -1,4 +1,4 @@ -{ lib, config, ... }: +{ pkgs, lib, config, ... }: with lib; let hostname = config.instance.hostname; @@ -11,22 +11,18 @@ let container-mail-user-id = 542; 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 { config = mkIf (cfg.enableContainer) { # Disable postfix on this host--it'll be run in the container instead 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 { enable = true; @@ -36,10 +32,10 @@ in rec { proxy_set_header Host $host; ''; trusted-network-string = - optionalString ((length config.instance.local-networks) > 0) + optionalString ((length local-networks) > 0) (concatStringsSep "\n" (map (network: "allow ${network};") - config.instance.local-networks)) + '' + local-networks)) + '' deny all;''; @@ -85,9 +81,7 @@ in rec { autoStart = true; - bindMounts = let - cert-copies = config.fudo.acme.host-domains.${hostname}.${cfg.mail-hostname}.local-copies; - in { + bindMounts = { "${container-maildir}" = { hostPath = cfg.mail-directory; isReadOnly = false; @@ -98,53 +92,54 @@ in rec { isReadOnly = false; }; - "/etc/${container-shared}" = { - hostPath = "/etc/${container-shared}"; - isReadOnly = true; - }; - "/run/mail/certs/postfix/cert.pem" = { - hostPath = cert-copies.postfix.certificate; + hostPath = cfg.ssl.certificate; isReadOnly = true; }; "/run/mail/certs/postfix/key.pem" = { - hostPath = cert-copies.postfix.private-key; + hostPath = cfg.ssl.private-key; isReadOnly = true; }; "/run/mail/certs/dovecot/cert.pem" = { - hostPath = cert-copies.dovecot.certificate; + hostPath = cfg.ssl.certificate; isReadOnly = true; }; "/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; }; }; - 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, ... }: { + 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 = { "mail-server/postfix/cert.pem" = { source = "/run/mail/certs/postfix/cert.pem"; @@ -158,59 +153,67 @@ in rec { }; "mail-server/dovecot/cert.pem" = { source = "/run/mail/certs/dovecot/cert.pem"; - user = config.services.dovecot.user; + user = config.services.dovecot2.user; mode = "0444"; }; "mail-server/dovecot/key.pem" = { source = "/run/mail/certs/dovecot/key.pem"; - user = config.services.dovecot.user; + user = config.services.dovecot2.user; 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 = { - enable = true; - hostname = cfg.hostname; - domain = cfg.domain; + mail-server = { + enable = true; + mail-hostname = cfg.mail-hostname; + domain = cfg.domain; - debug = cfg.debug; - monitoring = cfg.monitoring; + debug = cfg.debug; + monitoring = cfg.monitoring; - state-directory = container-statedir; - mail-directory = container-maildir; + state-directory = container-statedir; + mail-directory = container-maildir; - postfix = { - ssl-certificate = "/etc/mail-server/postfix/cert.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; + postfix = { + ssl-certificate = "/etc/mail-server/postfix/cert.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-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; }; }; }; diff --git a/lib/fudo/mail.nix b/lib/fudo/mail.nix index e48472a..70410cd 100644 --- a/lib/fudo/mail.nix +++ b/lib/fudo/mail.nix @@ -7,7 +7,7 @@ let in { - options.fudo.mail-server = { + options.fudo.mail-server = with types; { enable = mkEnableOption "Fudo Email Server"; enableContainer = mkEnableOption '' @@ -17,18 +17,17 @@ in { ''; domain = mkOption { - type = types.str; + type = str; description = "The main and default domain name for this email server."; }; mail-hostname = mkOption { - type = types.str; + type = str; description = "The domain name to use for the mail server."; }; - ldap-url = mkOption { - type = types.str; + type = str; description = "URL of the LDAP server to use for authentication."; example = "ldaps://auth.fudo.org/"; }; @@ -36,23 +35,25 @@ in { monitoring = mkEnableOption "Enable monitoring for the mail server."; mail-user = mkOption { - type = types.str; + type = str; description = "User to use for mail delivery."; + default = "mailuser"; }; # No group id, because NixOS doesn't seem to use it mail-group = mkOption { - type = types.str; + type = str; description = "Group to use for mail delivery."; + default = "mailgroup"; }; mail-user-id = mkOption { - type = types.int; + type = int; description = "UID of mail-user."; }; local-domains = mkOption { - type = with types; listOf str; + type = listOf str; description = "A list of domains for which we accept mail."; default = ["localhost" "localhost.localdomain"]; example = [ @@ -64,17 +65,17 @@ in { }; mail-directory = mkOption { - type = types.str; + type = str; description = "Path to use for mail storage."; }; state-directory = mkOption { - type = types.str; + type = str; description = "Path to use for state data."; }; 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."; example = [ "10.0.0.0/16" @@ -83,7 +84,7 @@ in { }; sender-blacklist = mkOption { - type = with types; listOf str; + type = listOf str; description = "A list of email addresses for whom we will not send email."; default = []; example = [ @@ -93,7 +94,7 @@ in { }; recipient-blacklist = mkOption { - type = with types; listOf str; + type = listOf str; description = "A list of email addresses for whom we will not accept email."; default = []; example = [ @@ -103,14 +104,14 @@ in { }; message-size-limit = mkOption { - type = types.int; + type = int; description = "Size of max email in megabytes."; default = 30; }; user-aliases = mkOption { - type = with types; attrsOf(listOf str); - description = "A map of real user to list of aliases."; + type = attrsOf (listOf str); + description = "A map of real user to list of alias emails."; default = {}; example = { someuser = ["alias0" "alias1"]; @@ -118,7 +119,7 @@ in { }; alias-users = mkOption { - type = with types; attrsOf(listOf str); + type = attrsOf (listOf str); description = "A map of email alias to a list of users."; example = { alias = ["realuser0" "realuser1"]; @@ -164,15 +165,27 @@ in { debug = mkOption { description = "Enable debugging on mailservers."; - type = types.bool; + type = bool; default = false; }; max-user-connections = mkOption { description = "Max simultaneous connections per user."; - type = types.int; + type = int; 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 = [ @@ -184,22 +197,26 @@ in { ]; config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d ${cfg.mail-directory} 770 ${cfg.mail-user} ${cfg.mail-group} - -" + ]; + networking.firewall = { allowedTCPPorts = [ 25 110 143 587 993 995 ]; }; users = { users = { - mailuser = { + ${cfg.mail-user} = { isSystemUser = true; uid = cfg.mail-user-id; - group = "mailgroup"; + group = cfg.mail-group; }; }; groups = { - mailgroup = { - members = ["mailuser"]; + ${cfg.mail-group} = { + members = [ cfg.mail-user ]; }; }; }; diff --git a/lib/fudo/mail/dovecot.nix b/lib/fudo/mail/dovecot.nix index 8724f07..e33050e 100644 --- a/lib/fudo/mail/dovecot.nix +++ b/lib/fudo/mail/dovecot.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.fudo.mail-server; - state-directory = "${cfg.state-directory}/dovecot"; + sieve-path = "${cfg.state-directory}/dovecot/imap_sieve"; pipe-bin = pkgs.stdenv.mkDerivation { name = "pipe_bin"; @@ -23,35 +23,47 @@ let ''; }; - ldap-conf = filename: config: + ldap-conf-template = ldap-cfg: let - ssl-config = if config.ca == null then '' + ssl-config = if (ldap-cfg.ca == null) then '' tls = no tls_require_cert = try '' else '' - tls_ca_cert_file = ${config.ca} + tls_ca_cert_file = ${ldap-cfg.ca} tls = yes tls_require_cert = try ''; - in - pkgs.writeText filename '' - uris = ${concatStringsSep " " config.server-urls} - ldap_version = 3 - dn = ${config.reader-dn} - dnpass = ${config.reader-passwd} - auth_bind = yes - auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org - base = dc=fudo,dc=org - ${ssl-config} - ''; + pkgs.writeText "dovecot2-ldap-config.conf.template" '' + uris = ${concatStringsSep " " ldap-cfg.server-urls} + ldap_version = 3 + dn = ${ldap-cfg.reader-dn} + dnpass = __LDAP_READER_PASSWORD__ + auth_bind = yes + auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org + base = dc=fudo,dc=org + ${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: '' - passdb { - driver = ldap - args = ${ldap-conf "ldap-passdb.conf" ldap-config} - } - ''; + passdb { + driver = ldap + args = ${ldap-conf "ldap-passdb.conf" ldap-config} + } + ''; ldapOpts = { options = with types; { @@ -61,6 +73,12 @@ let default = null; }; + base = mkOption { + type = str; + description = "Base of the LDAP server database."; + example = "dc=fudo,dc=org"; + }; + server-urls = mkOption { type = listOf str; description = "A list of LDAP server URLs used for authentication."; @@ -69,16 +87,20 @@ let reader-dn = mkOption { type = str; description = '' - DN to use for reading user information. Needs access to homeDirectory, - uidNumber, gidNumber, and uid, but not password attributes. - ''; + DN to use for reading user information. Needs access to homeDirectory, + uidNumber, gidNumber, and uid, but not password attributes. + ''; }; - reader-passwd = mkOption { + reader-password-file = mkOption { type = str; - description = '' - Password for the user specified in ldap-reader-dn. - ''; + description = "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 - ${optionalString (cfg.dovecot.ldap != null) - (ldap-passwd-entry cfg.dovecot.ldap)} + ${optionalString (cfg.dovecot.ldap != null) '' + passdb { + driver = ldap + args = ${cfg.dovecot.ldap.generated-ldap-config} + } + ''} userdb { driver = static args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u @@ -249,12 +275,12 @@ in { # From elsewhere to Spam folder imapsieve_mailbox1_name = Junk 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 imapsieve_mailbox2_name = * imapsieve_mailbox2_from = Junk 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_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment } @@ -268,19 +294,21 @@ in { ''; }; - systemd.services.dovecot2.preStart = '' - mkdir -p '${state-directory}' - chown ${dovecot-user}:${cfg.mail-group} '${state-directory}' - 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' + systemd = { + tmpfiles.rules = [ + "d ${sieve-path} 750 ${dovecot-user} ${cfg.mail-group} - -" + ]; - chown '${cfg.mail-user}:${cfg.mail-group}' ${cfg.mail-directory} - chmod g+w ${cfg.mail-directory} - ''; + services.dovecot2.preStart = '' + 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)} + ''; + }; }; } diff --git a/lib/fudo/mail/postfix.nix b/lib/fudo/mail/postfix.nix index 7b96652..7525f4d 100644 --- a/lib/fudo/mail/postfix.nix +++ b/lib/fudo/mail/postfix.nix @@ -109,7 +109,7 @@ in { enable = true; domain = cfg.domain; origin = cfg.domain; - hostname = cfg.hostname; + hostname = cfg.mail-hostname; destination = ["localhost" "localhost.localdomain"]; # destination = ["localhost" "localhost.localdomain" cfg.hostname] ++ # cfg.local-domains;; @@ -150,7 +150,7 @@ in { # mail_spool_directory = "${cfg.mail-directory}/"; 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_ultra_curve = "secp384r1"; diff --git a/lib/fudo/postgres.nix b/lib/fudo/postgres.nix index b975759..669aa70 100644 --- a/lib/fudo/postgres.nix +++ b/lib/fudo/postgres.nix @@ -4,6 +4,11 @@ with lib; let 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"; userDatabaseOpts = { database, ... }: { @@ -28,15 +33,15 @@ let }; userOpts = { username, ... }: { - options = { + options = with types; { password-file = mkOption { - type = with types; nullOr str; + type = nullOr str; description = "A file containing the user's (plaintext) password."; default = null; }; databases = mkOption { - type = with types; attrsOf (submodule userDatabaseOpts); + type = attrsOf (submodule userDatabaseOpts); description = "Map of databases to required database/table perms."; default = { }; example = { @@ -50,9 +55,9 @@ let }; databaseOpts = { dbname, ... }: { - options = { + options = with types; { users = mkOption { - type = with types; listOf str; + type = listOf str; description = "A list of users who should have full access to this database."; default = [ ]; @@ -74,9 +79,7 @@ let ''; passwords-setter-script = users: - pkgs.writeScriptBin "postgres-set-passwords.sh" '' - #!${pkgs.bash}/bin/bash - + pkgs.writeScript "postgres-set-passwords.sh" '' if [ $# -ne 1 ]; then echo "usage: $0 output-file.sql" exit 1 @@ -99,7 +102,7 @@ let nameValuePair "DATABASE ${database}" databaseOpts.access) databases; 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); @@ -263,8 +266,8 @@ in { local all all ident # host-local - host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG - host all all ::1/128 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=${gssapi-realm} # local networks ${makeNetworksEntry cfg.local-networks} @@ -278,8 +281,7 @@ in { postgresql-password-setter = let passwords-script = passwords-setter-script cfg.users; password-wrapper-script = - pkgs.writeScriptBin "password-script-wrapper.sh" '' - #!${pkgs.bash}/bin/bash + pkgs.writeScript "password-script-wrapper.sh" '' TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t postgres-XXXXXXXXXX) echo "using temp dir $TMPDIR" PASSWORD_SQL_FILE=$TMPDIR/user-passwords.sql @@ -287,7 +289,7 @@ in { touch $PASSWORD_SQL_FILE chown ${config.services.postgresql.superUser} $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" ${pkgs.postgresql}/bin/psql --port ${ toString config.services.postgresql.port @@ -306,7 +308,7 @@ in { Type = "oneshot"; User = config.services.postgresql.superUser; }; - script = "${password-wrapper-script}/bin/password-script-wrapper.sh"; + script = "${password-wrapper-script}"; }; postgresql.postStart = let diff --git a/lib/fudo/prometheus.nix b/lib/fudo/prometheus.nix index 3a98d06..450baaf 100644 --- a/lib/fudo/prometheus.nix +++ b/lib/fudo/prometheus.nix @@ -86,15 +86,17 @@ in { locations."/" = { 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 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; - ${optionalString ((length fudo-cfg.local-networks) > 0) - (concatStringsSep "\n" (map (network: "allow ${network};") fudo-cfg.local-networks)) + "\ndeny all;"} + ${optionalString ((length local-networks) > 0) + (concatStringsSep "\n" (map (network: "allow ${network};") local-networks)) + "\ndeny all;"} ''; }; }; diff --git a/lib/fudo/sites.nix b/lib/fudo/sites.nix index c5ad080..384203f 100644 --- a/lib/fudo/sites.nix +++ b/lib/fudo/sites.nix @@ -185,18 +185,6 @@ in { }; 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 = mkIf site-cfg.enable-ssh-backdoor [ site-cfg.dropbear-ssh-port ]; diff --git a/lib/fudo/ssh.nix b/lib/fudo/ssh.nix index 2d573e4..3f1f965 100644 --- a/lib/fudo/ssh.nix +++ b/lib/fudo/ssh.nix @@ -1,65 +1,9 @@ { config, lib, pkgs, ... }: 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 = { - 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 - keyed-hosts = filterAttrs (h: o: o.ssh-pubkeys != []) config.fudo.hosts; @@ -75,7 +19,7 @@ in { in mapAttrs (hostname: hostOpts: { publicKeyFile = builtins.head hostOpts.ssh-pubkeys; - hostNames = all-hostnames hostname host-cfg; + hostNames = all-hostnames hostname hostOpts; }) keyed-hosts; }; } diff --git a/lib/fudo/users-common.nix b/lib/fudo/users-common.nix deleted file mode 100644 index 0dffd6a..0000000 --- a/lib/fudo/users-common.nix +++ /dev/null @@ -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; -} diff --git a/lib/fudo/users.nix b/lib/fudo/users.nix index 82c7c17..4ce924b 100644 --- a/lib/fudo/users.nix +++ b/lib/fudo/users.nix @@ -65,34 +65,17 @@ in { }; }; - imports = [ ./users-common.nix ]; - config = let sys = config.instance; in { - fudo.auth.ldap-server = let - ldapUsers = (filterAttrs - (username: userOpts: userOpts.ldap-hashed-password != null)) + fudo.auth.ldap-server = { + users = filterAttrs + (username: userOpts: userOpts.ldap-hashed-passwd != null) config.fudo.users; - in { - users = mapAttrs (username: userOpts: { - uid = userOpts.uid; - group = userOpts.primary-group; - common-name = userOpts.common-name; - hashed-password = userOpts.ldap-hashed-password; - }) ldapUsers; + groups = config.fudo.groups; - groups = mapAttrs (groupname: groupOpts: { - 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; + system-users = config.fudo.system-users; }; programs.ssh.extraConfig = mkAfter '' @@ -161,9 +144,6 @@ in { 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 systemd.services = let ensure-group-directories = group: diff --git a/lib/fudo/webmail.nix b/lib/fudo/webmail.nix index ae04523..240efeb 100644 --- a/lib/fudo/webmail.nix +++ b/lib/fudo/webmail.nix @@ -302,7 +302,7 @@ in { (site: site-cfg: let 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" '' imap_host = "${site-cfg.mail-server}" @@ -342,7 +342,7 @@ in { link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg: let 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 '' ${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 diff --git a/lib/instance.nix b/lib/instance.nix index 4536992..ed87475 100644 --- a/lib/instance.nix +++ b/lib/instance.nix @@ -52,6 +52,11 @@ in { 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 { type = str; description = "Seed used to generate configuration."; @@ -87,25 +92,23 @@ in { filterAttrs (host: hostOpts: hostOpts.site == local-site) config.fudo.hosts; local-networks = - host.local-networks // - config.fudo.domains.${local-domain}.local-networks // + host.local-networks ++ + config.fudo.domains.${local-domain}.local-networks ++ config.fudo.sites.${local-site}.local-networks; local-profile = host.profile; - build-seed = builtins.readFile config.fudo.secrets.files.build-seed; - in { instance = { inherit - build-seed local-domain local-site local-users local-admins local-groups local-hosts - local-profile; + local-profile + local-networks; }; }; } diff --git a/lib/network.nix b/lib/network.nix new file mode 100644 index 0000000..412c08d --- /dev/null +++ b/lib/network.nix @@ -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}"; +} diff --git a/lib/overlay.nix b/lib/overlay.nix index 6c613a9..1646cf3 100644 --- a/lib/overlay.nix +++ b/lib/overlay.nix @@ -4,9 +4,10 @@ lib = prev.lib; in { ip = import ./ip.nix { pkgs = prev; }; - dns = import ./dns.nix { pkgs = prev;}; - passwd = import ./passwd.nix { pkgs = prev;}; - lisp = import ./lisp.nix { pkgs = prev;}; + dns = import ./dns.nix { pkgs = prev; }; + passwd = import ./passwd.nix { pkgs = prev; }; + lisp = import ./lisp.nix { pkgs = prev; }; + network = import ./network.nix { pkgs = prev; }; }; }; }) diff --git a/lib/passwd.nix b/lib/passwd.nix index 720ed18..97c2088 100644 --- a/lib/passwd.nix +++ b/lib/passwd.nix @@ -5,23 +5,17 @@ let hash-ldap-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation { name = "${name}-ldap-passwd"; - phases = [ "buildPhase" "installPhase" ]; + phases = [ "installPhase" ]; buildInputs = with pkgs; [ openldap ]; - buildPhase = '' - slappasswd -T ${passwd-file} > ldap-passwd - ''; - installPhase = '' - mkdir -p $out - mv ldap-passwd $out + slappasswd -T ${passwd-file} > $out ''; }; - hash-ldap-passwd = name: passwd-file: let - passwd-pkgs = hash-ldap-passwd-pkg name passwd-file; - in builtins.readFile "${passwd-pkgs}/ldap-passwd"; + hash-ldap-passwd = name: passwd-file: + builtins.readFile "${hash-ldap-passwd-pkg name passwd-file}"; generate-random-passwd = name: length: pkgs.stdenv.mkDerivation { name = "${name}-random-passwd"; @@ -31,7 +25,7 @@ let buildInputs = with pkgs; [ pwgen ]; installPhase = '' - pwgen --secure --num-passwords=1 ${length} > $out + pwgen --secure --num-passwords=1 ${toString length} > $out ''; }; diff --git a/lib/types/user.nix b/lib/types/user.nix index b85cab5..c454af3 100644 --- a/lib/types/user.nix +++ b/lib/types/user.nix @@ -108,15 +108,21 @@ rec { description = "User's primary email address."; 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; { - # group-name = mkOption { - # description = "Group name."; - # default = group-name; - # }; + group-name = mkOption { + description = "Group name."; + default = name; + }; description = mkOption { type = str;