diff --git a/common/options.nix b/common/options.nix index 252192ea..1713fdf8 100644 --- a/common/options.nix +++ b/common/options.nix @@ -7,8 +7,8 @@ type = lib.types.nullOr lib.types.str; }; - skipVhosts = lib.mkOption { - description = "Skip compiling the list of vhosts. Useful for development boxes"; + skipCustomVhosts = lib.mkOption { + description = "Skip all vhosts that are not based on the TLD. Useful for development boxes"; default = false; defaultText = "False (compile the vhosts)"; type = lib.types.nullOr lib.types.bool; @@ -20,5 +20,17 @@ defaultText = "Null (this is a master)"; type = lib.types.nullOr lib.types.str; }; + + smtpBindAddress = lib.mkOption { + description = "Address that Postfix expects to send and receive mail on"; + default = "192.168.0.158"; + type = lib.types.str; + }; + + smtpExternalAddress = lib.mkOption { + description = "The appropriate public IP forwarding port 587/993 for this mail host"; + default = "136.206.15.3"; + type = lib.types.str; + }; }; } diff --git a/common/sysconfig.nix b/common/sysconfig.nix index 5fde9858..2683afc1 100644 --- a/common/sysconfig.nix +++ b/common/sysconfig.nix @@ -3,7 +3,10 @@ let common = import ./variables.nix; tld = config.redbrick.tld; in { - imports = [ ./options.nix ]; + imports = [ + ./options.nix + ../packages/overlays + ]; time.timeZone = "Europe/Dublin"; i18n.defaultLocale = "en_IE.UTF-8"; @@ -57,6 +60,14 @@ in { ]; }; + # Enabled Spare cpu cycles to be used for folding@home + services.foldingathome = { + enable = true; + user = "redbrick"; + team = 43166; + extraArgs = [ "--power" "light" ]; + }; + # Enable LDAP users.ldap.enable = true; users.ldap.timeLimit = 2; diff --git a/common/variables.nix b/common/variables.nix index 644f2485..aa9a5dd4 100644 --- a/common/variables.nix +++ b/common/variables.nix @@ -13,11 +13,9 @@ rec { "djbdns.now.ie" = "djbdns.now.ie"; "romana.now.ie" = "djbdns.now.ie"; "www.luxgaa.lu" = "www.luxgaa.lu"; - "www.iahpc.ie" = "www.iahpc.ie"; "techweek.dcu.ie" = "techweek.dcu.ie"; "games.dcu.ie" = "www.games.dcu.ie"; "www.games.dcu.ie" = "www.games.dcu.ie"; - "interlan.dcu.ie" = "interlan.dcu.ie"; }; userWebtree = uid: "${webtreeDir}/${builtins.substring 0 1 uid}/${uid}"; @@ -40,10 +38,6 @@ rec { sslServerCert = "${certsDir}/${domain}/fullchain.pem"; }; - dovecotHost = "192.168.0.135"; - dovecotSaslPort = 3659; - dovecotLmtpPort = 24; - # Hard coded otherwise NSCD will crash systems during boot if network is down # 50 = daedalus ldapHostIp = "192.168.0.50"; diff --git a/hosts/hardcase/configuration.nix b/hosts/hardcase/configuration.nix index 0f101244..16d7f35c 100644 --- a/hosts/hardcase/configuration.nix +++ b/hosts/hardcase/configuration.nix @@ -12,6 +12,8 @@ in { ../../services/thelounge.nix ../../services/certs ../../services/httpd + ../../services/postfix + ../../services/dovecot ../../services/grafana ../../services/loki.nix ../../services/prometheus.nix diff --git a/hosts/hardcase/hardware-configuration.nix b/hosts/hardcase/hardware-configuration.nix index 02ef1bd4..96324b2f 100644 --- a/hosts/hardcase/hardware-configuration.nix +++ b/hosts/hardcase/hardware-configuration.nix @@ -29,6 +29,13 @@ systemd.targets.nfs-client.requiredBy = [ "storage.mount" ]; systemd.targets.nfs-client.before = [ "storage.mount" ]; + fileSystems."/var/spool/mail" = + { device = "icarus.internal:/zbackup/mail"; + fsType = "nfs"; + }; + systemd.services.dovecot2.requires = [ "var-spool-mail.mount" ]; + systemd.services.dovecot2.after = [ "var-spool-mail.mount" ]; + # zfs create -o dedup=off -o mountpoint=legacy -o recordsize=4K zroot/postgres fileSystems."/var/db/postgres" = { device = "zroot/postgres"; diff --git a/hosts/m1vm/configuration.nix b/hosts/m1vm/configuration.nix index 918111f4..c8d63b07 100644 --- a/hosts/m1vm/configuration.nix +++ b/hosts/m1vm/configuration.nix @@ -9,7 +9,6 @@ ../../services/postfix ../../services/dovecot ../../services/certs - ../../services/thelounge.nix ../../services/postgres.nix ]; @@ -17,7 +16,7 @@ # compatible, in order to avoid breaking some software such as database # servers. You should change this only after NixOS release notes say you # should. - system.stateVersion = "19.09"; + system.stateVersion = "20.09"; # Use the GRUB 2 boot loader. boot.loader.grub.enable = true; @@ -35,8 +34,10 @@ }; # Dev box, skip loading vhosts - redbrick.skipVhosts = true; - redbrick.tld = "redbricktest.ml"; + redbrick.tld = "redbricktest.cf"; + redbrick.skipCustomVhosts = true; + redbrick.smtpBindAddress = "192.168.0.135"; + redbrick.smtpExternalAddress = "136.206.15.5"; users.users.lucasade = { isNormalUser = true; diff --git a/hosts/m1vm/hardware-configuration.nix b/hosts/m1vm/hardware-configuration.nix index ef6d8db4..3b9ffdbb 100644 --- a/hosts/m1vm/hardware-configuration.nix +++ b/hosts/m1vm/hardware-configuration.nix @@ -24,6 +24,13 @@ systemd.targets.nfs-client.requiredBy = [ "storage.mount" ]; systemd.targets.nfs-client.before = [ "storage.mount" ]; + fileSystems."/var/spool/mail" = + { device = "icarus.internal:/zbackup/mailtest"; + fsType = "nfs"; + }; + systemd.services.dovecot2.requires = [ "var-spool-mail.mount" ]; + systemd.services.dovecot2.after = [ "var-spool-mail.mount" ]; + swapDevices = [ { device = "/dev/disk/by-uuid/7ca217d6-538e-4919-a57f-c5cbaeb93832"; } ]; diff --git a/hosts/metharme/configuration.nix b/hosts/metharme/configuration.nix new file mode 100644 index 00000000..c1137d79 --- /dev/null +++ b/hosts/metharme/configuration.nix @@ -0,0 +1,31 @@ +{ config, pkgs, ... }: +let + variables = import ../../common/variables.nix; +in { + imports = [ + ./hardware-configuration.nix + ../../common/sysconfig.nix + ../../services/ssh.nix + ]; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "20.09"; + + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.devices = [ "/dev/sda" "/dev/sdb" ]; + + networking = { + hostName = "metharme"; + hostId = "1f03060e"; + defaultGateway = "192.168.0.254"; + interfaces.eno2.ipv4.addresses = [{ + address = "192.168.0.156"; + prefixLength = 24; + }]; + }; +} diff --git a/hosts/metharme/hardware-configuration.nix b/hosts/metharme/hardware-configuration.nix new file mode 100644 index 00000000..57cd2d22 --- /dev/null +++ b/hosts/metharme/hardware-configuration.nix @@ -0,0 +1,32 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix> + ]; + + boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "mpt3sas" "usbhid" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "zroot/nixos"; + fsType = "zfs"; + }; + + fileSystems."/nix" = + { device = "zroot/nixos/store"; + fsType = "zfs"; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/b99bbf80-002b-4c10-9d72-96195bb64f4f"; priority = 100; } + { device = "/dev/disk/by-uuid/49a2deff-5fcc-4736-895b-130202648b59"; priority = 100; } + ]; + + nix.maxJobs = lib.mkDefault 16; +} diff --git a/hosts/motherlode/configuration.nix b/hosts/motherlode/configuration.nix new file mode 100644 index 00000000..678b930d --- /dev/null +++ b/hosts/motherlode/configuration.nix @@ -0,0 +1,28 @@ +{ config, pkgs, ... }: +let + variables = import ../../common/variables.nix; +in { + imports = [ + ./hardware-configuration.nix + ../../common/sysconfig.nix + ../../services/ssh.nix + ../../services/libvirt.nix + ]; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "20.09"; + + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/sda"; + + networking = { + hostName = "motherlode"; + hostId = "fccc9415"; + defaultGateway = "192.168.0.254"; + } // (variables.bondConfig [ "eno1" "eno2" ] "192.168.0.130"); +} diff --git a/hosts/motherlode/hardware-configuration.nix b/hosts/motherlode/hardware-configuration.nix new file mode 100644 index 00000000..bb5d5509 --- /dev/null +++ b/hosts/motherlode/hardware-configuration.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +{ + imports = + [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix> + ]; + + boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "megaraid_sas" "usbhid" "sd_mod" "sr_mod" ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/8c34aaec-4f61-4b29-a451-cd8e8d2bd394"; + fsType = "ext4"; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/28c3f2fd-7b98-4bf6-a778-f73b8064c381"; } + ]; + + nix.maxJobs = lib.mkDefault 8; +} diff --git a/nixops.nix b/nixops.nix new file mode 100644 index 00000000..37ed284d --- /dev/null +++ b/nixops.nix @@ -0,0 +1,5 @@ +{ + icarus = { config, pkgs, ... }: { + deployment.targetHost = "192.168.0.150"; + }; +} diff --git a/packages/overlays/default.nix b/packages/overlays/default.nix new file mode 100644 index 00000000..597c1440 --- /dev/null +++ b/packages/overlays/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./rsyslog.nix + ]; +} diff --git a/packages/overlays/rsyslog.nix b/packages/overlays/rsyslog.nix new file mode 100644 index 00000000..76f68dd1 --- /dev/null +++ b/packages/overlays/rsyslog.nix @@ -0,0 +1,11 @@ +{ + # libksi has an outdated dependency on openssl_1_0_2 + # We don't need it, so remove it from rsyslog + nixpkgs.overlays = [ + (self: super: { + rsyslog = super.rsyslog.override { + libksi = null; + }; + }) + ]; +} diff --git a/services/dns/default.nix b/services/dns/default.nix index d743fff7..7428e98a 100644 --- a/services/dns/default.nix +++ b/services/dns/default.nix @@ -35,13 +35,13 @@ in { { # Not using tld here becaue we actually want to configure # a specific domain - file = "${zonePath}/redbricktest.ml"; + file = "${zonePath}/redbricktest.cf"; master = true; - name = "redbricktest.ml"; + name = "redbricktest.cf"; extraConfig = "allow-update { key ${keyName}; };"; } { - file = "${zonePath}/redbricktest.ml.rr"; + file = "${zonePath}/redbricktest.cf.rr"; master = true; name = "15.206.136.in-addr.arpa"; } diff --git a/services/dns/redbricktest.ml b/services/dns/redbricktest.ml index d9a5ef4e..cee9ebfd 100644 --- a/services/dns/redbricktest.ml +++ b/services/dns/redbricktest.ml @@ -1,7 +1,7 @@ $ORIGIN redbricktest.ml. $TTL 300 @ IN SOA ns1.redbricktest.ml. admins.redbricktest.ml. ( - 2019060506 ; Serial + 2019060507 ; Serial 1M ; Slave refresh interval 5M ; Query retry interval 1H ; Expiry @@ -20,3 +20,4 @@ mail IN A 136.206.15.5 www IN CNAME server1 wiki IN CNAME server1 +lists IN CNAME mail diff --git a/services/dovecot/auth.nix b/services/dovecot/auth.nix index ee8b7b21..ab7da526 100644 --- a/services/dovecot/auth.nix +++ b/services/dovecot/auth.nix @@ -1,11 +1,10 @@ -{common, pkgs, vmailUserName, ...}: +{common, pkgs, tld, ...}: let bindCreds = import /var/secrets/dovecot_auth.nix; ldapConfig = pkgs.writeText "dovecot-ldap-config" '' + !include /var/secrets/dovecot_auth.conf hosts = ${common.ldapHost} - dn = ${bindCreds.dn} - dnpass = ${bindCreds.password} ldap_version = 3 auth_bind = no base = ou=accounts,o=redbrick @@ -15,6 +14,8 @@ let user_filter = (&(objectclass=posixAccount)(uid=%n)) pass_attrs = uid=uid,homeDirectory=home,userPassword=password pass_filter = (&(objectclass=posixAccount)(uid=%n)) + iterate_attrs = =user=%{ldap:uid} + iterate_filter = (objectClass=posixAccount) default_pass_scheme = CRYPT ''; @@ -24,9 +25,15 @@ in pkgs.writeText "dovecot-auth-config" '' auth_cache_ttl = 1 hour auth_cache_negative_ttl = 1 hour + # Set domain for login names without a domain specified + auth_default_realm = ${tld} + # only use plain username/password auth - OK since everything is over TLS auth_mechanisms = plain + # Don't strip domain from username. Means that mail_location can reference %d + auth_username_format = %Lu + # passdb specifies how users are authenticated - LDAP in my case passdb { driver = ldap diff --git a/services/dovecot/default.nix b/services/dovecot/default.nix index 2c6adee7..85dffc96 100644 --- a/services/dovecot/default.nix +++ b/services/dovecot/default.nix @@ -4,21 +4,54 @@ let common = import ../../common/variables.nix; - commonDovecot = import ./variables.nix; + sieveConfig = import ./sieve.nix { inherit pkgs; }; + authConfig = import ./auth.nix { inherit common pkgs tld; }; + masterConfig = import ./master.nix { inherit pkgs; }; + + # Fixed uid + gid so that the config can roam systems safely + vmailId = 975; + + # Fix mtime comparison so that scripts can be precompiled + # See https://github.com/NixOS/nixpkgs/pull/35536/files + # and https://github.com/dovecot/pigeonhole/pull/4 + pigeonhole = pkgs.dovecot_pigeonhole.overrideAttrs (old: { + patches = [ + (pkgs.fetchpatch { + name = "binary-mtime.patch"; + url = https://github.com/dovecot/pigeonhole/commit/3defbec146e195edad336a2c218f108462b0abd7.patch; + sha256 = "09mvdw8gjzq9s2l759dz4aj9man8q1akvllsq2j1xa2qmwjfxarp"; + }) + ]; + }); - vmailUserName = "vmail"; - - authConfig = import ./auth.nix { inherit common pkgs vmailUserName; }; - masterConfig = import ./master.nix { inherit common pkgs; }; in { - networking.firewall.allowedTCPPorts = [ 993 common.dovecotSaslPort common.dovecotLmtpPort ]; + networking.firewall.allowedTCPPorts = [ 993 ]; security.dhparams.enable = true; # Name found in https://github.com/NixOS/nixpkgs/blob/d7752fc0ebf9d49dc47c70ce4e674df024a82cfa/nixos/modules/services/mail/dovecot.nix#L26 security.dhparams.params.dovecot2.bits = 2048; + # Create the user + group that will own /var/mail + # Uid not actually used. If the year is 2021 and this config is in production + # you can remove uid=uid from user_attrs in auth.nix and simplify the perms + # in /var/mail. Read the blog post for more info + users.users.vmail = { + description = "Owns mail written by dovecot2"; + isSystemUser = true; + group = "vmail"; + shell = "/dev/null"; + home = "/dev/null"; + uid = vmailId; + }; + users.groups.vmail.gid = vmailId; + + # Increase ulimit due to service_auth client_limit (2000) + systemd.services.dovecot2.serviceConfig.LimitNOFILE = 2500; + services.dovecot2 = { enable = true; + modules = [ pigeonhole ]; + enableImap = true; enableLmtp = true; enablePAM = false; @@ -30,9 +63,13 @@ in { # We don't want all members to be able to read other member's mail # Force a specific group - mailGroup = vmailUserName; + createMailUser = false; + mailUser = "vmail"; + mailGroup = "vmail"; - mailLocation = "mdbox:~/mdbox"; + # ENSURE /var/mail IS CHMOD 3770 + # See https://wiki.dovecot.org/SharedMailboxes/Permissions + mailLocation = "mdbox:/var/mail/%d/%n"; mailboxes = [{ name = "Junk"; @@ -49,13 +86,9 @@ in { }]; extraConfig = '' - # to improve performance, disable fsync globally - we will enable it for - # some specific services later on - mail_fsync = never - - auth_verbose = yes - - mail_debug = yes + # Having trouble? Try enabling these + auth_verbose = no + mail_debug = no namespace inbox { separator = / @@ -66,21 +99,26 @@ in { # max IMAP connections per IP address mail_max_userip_connections = 50 # imap_sieve will be used for spam training by rspamd - mail_plugins = $mail_plugins # imap_sieve + mail_plugins = $mail_plugins imap_sieve } protocol lmtp { mail_fsync = optimized - mail_plugins = $mail_plugins + mail_plugins = $mail_plugins sieve } # require SSL for all non-localhost connections ssl = required - mail_home = /var/mail/%n + # Only the mail user should be authorized to write mail + + mail_home = /var/mail/%d/%n mail_attachment_dir = /var/mail/attachments mail_attachment_min_size = 64k + # When copying perms from /var/mail, use this group + mail_access_groups = vmail + # require modern crypto - taken from Mozilla's SSL recommendations page ssl_min_protocol = TLSv1.2 ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 @@ -94,23 +132,9 @@ in { !include ${masterConfig} # Enable sieve scripts - # protocols = $protocols sieve - - # plugin { - # location of users' sieve directory and their "active" sieve script - # sieve = file:~/sieve;active=~/.dovecot.sieve - - # directory of global sieve scripts to run before and after processing ALL - # incoming mail - # sieve_before = /usr/local/etc/dovecot/sieve-before.d - # sieve_after = /usr/local/etc/dovecot/sieve-after.d - - # make sieve aware of user+tag@domain.tld aliases - # recipient_delimiter = + + protocols = $protocols sieve - # maximum size of all user's sieve scripts - # sieve_quota_max_storage = 10M - # } + !include ${sieveConfig} ''; }; } diff --git a/services/dovecot/master.nix b/services/dovecot/master.nix index 5b562ea1..06fd7f72 100644 --- a/services/dovecot/master.nix +++ b/services/dovecot/master.nix @@ -1,9 +1,5 @@ -{common, pkgs, ...}: +{pkgs, ...}: pkgs.writeText "dovecot-master-config" '' -# to improve performance, disable fsync globally - we will enable it for -# some specific services later on -mail_fsync = never - service imap-login { # plain-text IMAP should only be accessible from localhost inet_listener imap { @@ -29,22 +25,31 @@ service pop3-login { # enable semi-long-lived IMAP processes to improve performance service imap { - service_count = 256 - # set to the number of CPU cores on your server - process_min_avail = 3 + # Service count must be 1 to prevent imap uid leaking and setuid issues + # Seen as "imap(foo)<5288><JWXxCE2ks4ZZE0NM>: Fatal: setuid(foo from userdb lookup) failed with euid=bar: Operation not permitted" + # See https://doc.dovecot.org/configuration_manual/service_configuration/ + # Docs specify you should just do this if you have multiple UIDs + service_count = 1 + process_limit = 2000 } # Listen on LMTP port for postfix to deliver mail service lmtp { - inet_listener { - port = ${builtins.toString common.dovecotLmtpPort} + client_limit = 1 + unix_listener /var/run/dovecot2_lmtp.sock { + user = postfix + group = postfix + mode = 0600 } } # Listen on auth socket for postfix to authenticate users service auth { - inet_listener { - port = ${builtins.toString common.dovecotSaslPort} + client_limit = 2000 + unix_listener /var/run/dovecot2_sasl.sock { + user = postfix + group = postfix + mode = 0600 } } '' diff --git a/services/dovecot/sieve.nix b/services/dovecot/sieve.nix new file mode 100644 index 00000000..f21ac5e2 --- /dev/null +++ b/services/dovecot/sieve.nix @@ -0,0 +1,147 @@ +{pkgs}: +with pkgs.stdenv; +let + + # These scripts are used by the imapsieve plugin to learn spam and ham + learnSpamScript = pkgs.writeShellScript "learn-spam.sh" '' + ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/rspamd.sock learn_spam + ''; + + learnHamScript = pkgs.writeShellScript "learn-ham.sh" '' + ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/rspamd.sock learn_ham + ''; + + sievePipeBinaries = mkDerivation { + name = "sieve-pipe-binaries"; + + phases = [ "copyPhase" "fixupPhase" ]; + + copyPhase = '' + mkdir -p $out/bin + cp -a ${learnHamScript} $out/bin/learn-ham.sh + cp -a ${learnSpamScript} $out/bin/learn-spam.sh + ''; + }; + + # sieve-before script to put spam into Junk folder + spamFilter = pkgs.writeText "spam-filter.sieve" '' + require ["fileinto"]; + + if header :is "X-Spam" "Yes" { + fileinto "Junk"; + } + ''; + + # imapsieve script to detect when a user marks an email as spam + reportSpamFilter = pkgs.writeText "report-spam.sieve" '' + require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + + if environment :matches "imap.email" "*" { + set "email" "''${1}"; + } + + pipe :copy "learn-spam.sh" [ "''${email}" ]; + ''; + + # imapsieve script to detect when a user moves an email out of spam + reportHamFilter = pkgs.writeText "report-ham.sieve" '' + require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + + if environment :matches "imap.mailbox" "*" { + set "mailbox" "''${1}"; + } + + if string "''${mailbox}" "Trash" { + stop; + } + + if environment :matches "imap.email" "*" { + set "email" "''${1}"; + } + + pipe :copy "learn-ham.sh" [ "''${email}" ]; + ''; + + sieveSimpleConfig = '' + sieve_plugins = sieve_imapsieve sieve_extprograms + sieve_global_extensions = +vnd.dovecot.pipe + sieve_pipe_bin_dir = ${sievePipeBinaries}/bin + ''; + + sieveCompileConfig = pkgs.writeText "sieve-compile-config" '' + plugin { + ${sieveSimpleConfig} + } + ''; + + sieveScripts = mkDerivation { + name = "sieve-scripts"; + + buildInputs = [ pkgs.dovecot_pigeonhole ]; + + phases = [ "copyPhase" ]; + + copyPhase = '' + mkdir -p $out/{before,after,imap} + cd $out/before + cp ${spamFilter} spam-filter.sieve + sievec -c "${sieveCompileConfig}" spam-filter.sieve + cd $out/imap + cp ${reportSpamFilter} report-spam.sieve + cp ${reportHamFilter} report-ham.sieve + sievec -c "${sieveCompileConfig}" report-spam.sieve + sievec -c "${sieveCompileConfig}" report-ham.sieve + ''; + + meta = with lib; { + description = "Redbrick compiled sieve scripts for Dovecot"; + platforms = platforms.linux; + maintainers = [ maintainers.m1cr0man ]; + }; + }; + +in pkgs.writeText "dovecot-sieve-config" '' + service managesieve-login { + inet_listener sieve { + name = sieve + address = 127.0.0.1 + port = 4190 + ssl = yes + } + } + + service managesieve { + # Blank line required syntactically + } + + plugin { + ${sieveSimpleConfig} + + # location of users' sieve directory and their "active" sieve script + sieve = file:~/sieve;active=~/.dovecot.sieve + + # directory of global sieve scripts to run before and after processing ALL + # incoming mail + sieve_before = ${sieveScripts}/before + sieve_after = ${sieveScripts}/after + + # make sieve aware of user+tag@domain.tld aliases + recipient_delimiter = + + + # maximum size of all user's sieve scripts + sieve_quota_max_storage = 2M + + ## Spam and Ham learning ## + + # From elsewhere to Junk folder + imapsieve_mailbox1_name = Junk + imapsieve_mailbox1_causes = COPY + imapsieve_mailbox1_before = file:${sieveScripts}/imap/report-spam.sieve + + # From Junk folder to elsewhere + imapsieve_mailbox2_name = * + imapsieve_mailbox2_from = Junk + imapsieve_mailbox2_causes = COPY + imapsieve_mailbox2_before = file:${sieveScripts}/imap/report-ham.sieve + } +'' diff --git a/services/dovecot/variables.nix b/services/dovecot/variables.nix deleted file mode 100644 index b1d1dc80..00000000 --- a/services/dovecot/variables.nix +++ /dev/null @@ -1,3 +0,0 @@ -{ - configPath = "/etc/dovecot.d"; -} diff --git a/services/httpd/default.nix b/services/httpd/default.nix index ebc8ecc8..2c8167ab 100644 --- a/services/httpd/default.nix +++ b/services/httpd/default.nix @@ -1,10 +1,8 @@ { config, pkgs, lib, ... }: +with (import ./shared.nix { tld = config.redbrick.tld; }); let - tld = config.redbrick.tld; - common = import ../../common/variables.nix; vhosts = import ./vhosts.nix { inherit config; }; errorPages = import ../../packages/httpd-error-pages { inherit pkgs; }; - adminAddr = "webmaster@${tld}"; # Define a base vhost for all TLDs. This will serve only ACME on port 80 # Everything else is promoted to HTTPS @@ -67,6 +65,7 @@ in { ./php-fpm.nix ./mediawiki.nix ./privatebin.nix + ./mailman.nix ]; # Enable suexec support @@ -120,7 +119,6 @@ in { </Directory> AddHandler cgi-script .cgi - AddHandler cgi-script .py AddHandler cgi-script .sh AddHandler server-parsed .shtml AddHandler server-parsed .html diff --git a/services/httpd/mailman.nix b/services/httpd/mailman.nix new file mode 100644 index 00000000..cd4e86db --- /dev/null +++ b/services/httpd/mailman.nix @@ -0,0 +1,46 @@ +# Manual steps post-deploy: +# cd /var/lib/mailman-web && sudo -u wwwrun mailman-web createsuperuser +{ pkgs, lib, config, ... }: +with (import ./shared.nix { tld = config.redbrick.tld; }); +let + webRoot = config.services.mailman.webRoot; + generatedDataPath = "/var/lib/mailman-web"; + + # Build mod_wsgi with python3 + wsgiPkg = with pkgs; mod_wsgi.overrideAttrs (oldAttrs: { + buildInputs = [ apacheHttpd python3 ncurses ]; + }); + + vhostConfig = { + adminAddr = "webmaster@${tld}"; + serverAliases = [ "localmail.${tld}" ]; + servedDirs = [ { dir = "${generatedDataPath}/static"; urlPath = "/static"; } ]; + extraConfig = '' + <Location /accounts/signup> + Order allow,deny + Deny from all + </Location> + <Directory "${webRoot}"> + Options ExecCGI + <Files wsgi.py> + Require all granted + </Files> + WSGIProcessGroup mailman + </Directory> + WSGIScriptAlias / ${webRoot}/mailman_web/wsgi.py + ''; + }; +in { + services.httpd = { + extraModules = [ { name = "wsgi"; path = "${wsgiPkg}/modules/mod_wsgi.so"; } ]; + extraConfig = '' + WSGISocketPrefix /run/httpd/wsgi + WSGIDaemonProcess mailman threads=4 home=${generatedDataPath} python-path=/etc/mailman3:${webRoot}:${ + lib.makeSearchPath pkgs.python3.sitePackages + pkgs.python3Packages.mailman-web.requiredPythonModules + } + ''; + + virtualHosts."lists.${tld}" = vhostConfig // { onlySSL = true; } // (vhostCerts tld); + }; +} diff --git a/services/httpd/shared.nix b/services/httpd/shared.nix index 063f8696..2da08382 100644 --- a/services/httpd/shared.nix +++ b/services/httpd/shared.nix @@ -52,4 +52,9 @@ in { ProxyPassReverse / ${proxyAddress}/ ''; }; + + vhostCerts = domain: { + sslServerKey = "${common.certsDir}/${domain}/key.pem"; + sslServerCert = "${common.certsDir}/${domain}/fullchain.pem"; + }; } diff --git a/services/httpd/vhosts.nix b/services/httpd/vhosts.nix index 8baecbb7..a6d81786 100644 --- a/services/httpd/vhosts.nix +++ b/services/httpd/vhosts.nix @@ -9,6 +9,7 @@ let "paste" "wiki" "cmtwiki" + "lists" ]; # This is appended at the top @@ -21,13 +22,11 @@ let group = user.gid; }; }) (filter (user: !elem user.uid userBlacklist) users)); -in (if (config.redbrick.skipVhosts) then {} else userVhosts // { - "abovethefold.es" = vhost { - documentRoot = "${webtree}/r/receive/abovethefold"; - user = "receive"; - group = "staff"; - wwwRedirect = true; - serverAliases = [ "www.abovethefold.es" ]; +in (userVhosts // { + "bash.${tld}" = vhost { + documentRoot = "${webtree}/y/yosarian/bash"; + user = "yosarian"; + group = "associat"; }; "blog.${tld}" = vhost { documentRoot = "${webtree}/vhosts/blog.redbrick.dcu.ie"; @@ -64,13 +63,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "bugs"; group = "redbrick"; }; - "butterflyexplosion.com" = vhost { - documentRoot = "${webtree}/c/carr"; - user = "carr"; - group = "associat"; - wwwRedirect = true; - serverAliases = [ "www.butterflyexplosion.com" ]; - }; "ca2wiki.${tld}" = vhost { documentRoot = "${webtree}/s/sonic/wiki"; user = "sonic"; @@ -86,11 +78,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "sonic"; group = "member"; }; - "ciankehoe.ie" = vhost { - documentRoot = "${webtree}/c/cianky/ciankehoe.ie/public"; - user = "cianky"; - group = "member"; - }; "colors.${tld}" = vhost { documentRoot = "${webtree}/vhosts/colors.redbrick.dcu.ie"; user = "wwwrun"; @@ -101,18 +88,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "chair"; group = "committe"; }; - "dcudrama.ie" = vhost { - documentRoot = "${webtree}/d/drama"; - user = "drama"; - group = "society"; - wwwRedirect = true; - serverAliases = [ "www.dcudrama.ie" ]; - }; - "djbdns.now.ie" = vhost { - documentRoot = "${webtree}/l/lecter/djbdns"; - user = "lecter"; - group = "associat"; - }; "forbidden.${tld}" = vhost { documentRoot = "${webtree}/vhosts/forbidden.redbrick.dcu.ie"; user = "stolnart"; @@ -128,20 +103,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "bunbun"; group = "member"; }; - "grahambartley.com" = vhost { - documentRoot = "${webtree}/d/dedoctor"; - user = "dedoctor"; - group = "member"; - wwwRedirect = true; - serverAliases = [ "www.grahambartley.com" ]; - }; - "h8.work" = vhost { - documentRoot = "${webtree}/a/ainran/domains/h8.work"; - user = "ainran"; - group = "member"; - wwwRedirect = true; - serverAliases = [ "www.h8.work" ]; - }; "hack.${tld}" = vhost { documentRoot = "${webtree}/n/newbrick"; user = "newbrick"; @@ -152,18 +113,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "newbrick"; group = "redbrick"; }; - "halenger.com" = vhost { - documentRoot = "${home}/associat/h/halenger/domains/halenger.com"; - user = "halenger"; - group = "associat"; - wwwRedirect = true; - serverAliases = [ "www.halenger.com" ]; - }; - "interlan.dcu.ie" = vhost { - documentRoot = "${webtree}/vhosts/www.interlan.dcu.ie"; - user = "gamessoc"; - group = "society"; - }; "gamessoc.${tld}" = vhost { documentRoot = "${webtree}/g/games"; user = "gamessoc"; @@ -174,18 +123,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { RedirectMatch 301 "^gamesoc\.(.*)$" "https://www.games.$1" ''; }; - "lessthanthree.be" = vhost { - documentRoot = "${webtree}/o/ornat"; - user = "ornat"; - group = "member"; - wwwRedirect = true; - serverAliases = [ "www.lessthanthree.be" ]; - }; - "blog.lessthanthree.be" = vhost { - documentRoot = "${webtree}/o/ornat/blog"; - user = "ornat"; - group = "member"; - }; "mak.${tld}" = vhost { documentRoot = "${webtree}/m/mak/mak"; user = "mak"; @@ -196,20 +133,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "mcmahon"; group = "member"; }; - "mlane.org" = vhost { - documentRoot = "${webtree}/a/ainran/mlane"; - user = "ainran"; - group = "associat"; - wwwRedirect = true; - serverAliases = [ "www.mlane.org" ]; - }; - "obrienronan.com" = vhost { - documentRoot = "${webtree}/vhosts/www.obrienronan.com"; - user = "mellow"; - group = "associat"; - wwwRedirect = true; - serverAliases = [ "www.obrienronan.com" ]; - }; "packages.${tld}" = vhost { documentRoot = "${webtree}/r/rbpkg/apt"; user = "rbpkg"; @@ -235,6 +158,172 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "d_fens"; group = "associat"; }; + "room.${tld}" = vhost { + documentRoot = "${webtree}/e/edu/n109a"; + user = "edu"; + group = "redbrick"; + serverAliases = ["n109a.${tld}"]; + }; + "sadsoc.${tld}" = vhost { + documentRoot = "${webtree}/a/art_wolf/sadsoc"; + user = "art_wolf"; + group = "associat"; + }; + "security.${tld}" = vhost { + documentRoot = "${webtree}/d/d_fens/security"; + user = "d_fens"; + group = "associat"; + }; + "signup.${tld}" = vhost { + documentRoot = "${webtree}/e/events/csday"; + user = "events"; + group = "redbrick"; + }; + "surfnsail.${tld}" = vhost { + documentRoot = "${webtree}/s/sailing"; + user = "sailing"; + group = "club"; + }; + "wanderers.${tld}" = vhost { + documentRoot = "${webtree}/w/wander/"; + user = "wander"; + group = "projects"; + }; + "webmail.${tld}" = vhost { + documentRoot = "${webtree}/vhosts/rainloop"; + user = "wwwrun"; + group = "wwwrun"; + }; + "x-files.${tld}" = vhost { + documentRoot = "${webtree}/f/fox_chic"; + user = "fox_chic"; + group = "associat"; + }; + "yfg.${tld}" = vhost { + documentRoot = "${webtree}/f/finegael"; + user = "finegael"; + group = "associat"; + }; + "youth2000.${tld}" = vhost { + documentRoot = "${webtree}/y/youth2k"; + user = "gamessoc"; + group = "society"; + }; + "git.${tld}" = vhostProxy "http://localhost:3000/"; + "prometheus.${tld}" = vhostProxy "http://localhost:9090/"; + "graphs.${tld}" = vhostProxy "http://localhost:3001/"; + "dcufm.${tld}" = vhostProxy "http://localhost:8002/"; + "jakarta.${tld}" = vhostProxy "http://136.206.15.59:8080/"; + "macspayn.${tld}" = vhostProxy "http://136.206.15.25:3007/"; + "portaldev.${tld}" = vhostProxy "http://136.206.15.61:9080/"; + "radio.${tld}" = vhostProxy "http://radio.${tld}:8000/"; + "riainccc.${tld}" = vhostProxy "http://http://136.206.15.25:3000/"; + "tomcat.dregin.${tld}" = vhostProxy "http://136.206.15.14:20002/"; + "webchat.${tld}" = vhostProxy "http://127.0.0.1:16667/"; + "werdztomcat.${tld}" = vhostProxy "http://136.206.15.14:20001/"; + "www.${tld}" = vhostRedirect "https://${tld}/"; + "admin.${tld}" = vhostRedirect "https://blog.${tld}/"; + "admins.${tld}" = vhostRedirect "https://blog.${tld}/"; + "ajaxterm.${tld}" = vhostRedirect "https://term.${tld}/"; + "anyterm.${tld}" = vhostRedirect "https://term.${tld}/"; + "dconcannon.${tld}" = vhostRedirect "https://shimoda.${tld}/"; + "dermot.${tld}" = vhostRedirect "https://homer.${tld}/"; + "devnull.${tld}" = vhostRedirect "https://colmmacc.${tld}/"; + "devrandom.${tld}" = vhostRedirect "https://marvin.${tld}/"; + "events.${tld}" = vhostRedirect "https://${tld}/events/"; + "fosdem.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/ZwETj0"; + "github.${tld}" = vhostRedirect "https://github.com/redbrick/"; + "help.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk"; + "helpdesk.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk"; + "helpdeskexam.${tld}" = vhostRedirect "https://md.${tld}/s/SJzip7F9X#"; + "mail.${tld}" = vhostRedirect "https://webmail.${tld}/"; + "hoodies.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/Q4uIzR"; + "parlour.${tld}" = vhostRedirect "https://songsfromtheparlour.com/"; + "sistem.${tld}" = vhostRedirect "https://sistem.intersocs.ie/"; + "techweek.${tld}" = vhostRedirect "https://techweek.dcu.ie/"; + "tickets.${tld}" = vhostRedirect "https://dcusu.ticketsolve.com/shows/873599383/events/128190598"; + "ubuntu.${tld}" = vhostRedirect "https://wiki.${tld}/mw/RedBrick_Ubuntu"; + +} // (if (config.redbrick.skipCustomVhosts) then {} else { + + "abovethefold.es" = vhost { + documentRoot = "${webtree}/r/receive/abovethefold"; + user = "receive"; + group = "staff"; + wwwRedirect = true; + serverAliases = [ "www.abovethefold.es" ]; + }; + "butterflyexplosion.com" = vhost { + documentRoot = "${webtree}/c/carr"; + user = "carr"; + group = "associat"; + wwwRedirect = true; + serverAliases = [ "www.butterflyexplosion.com" ]; + }; + "ciankehoe.ie" = vhost { + documentRoot = "${webtree}/c/cianky/ciankehoe.ie/public"; + user = "cianky"; + group = "member"; + }; + "dcudrama.ie" = vhost { + documentRoot = "${webtree}/d/drama"; + user = "drama"; + group = "society"; + wwwRedirect = true; + serverAliases = [ "www.dcudrama.ie" ]; + }; + "djbdns.now.ie" = vhost { + documentRoot = "${webtree}/l/lecter/djbdns"; + user = "lecter"; + group = "associat"; + }; + "grahambartley.com" = vhost { + documentRoot = "${webtree}/d/dedoctor"; + user = "dedoctor"; + group = "member"; + wwwRedirect = true; + serverAliases = [ "www.grahambartley.com" ]; + }; + "h8.work" = vhost { + documentRoot = "${webtree}/a/ainran/domains/h8.work"; + user = "ainran"; + group = "member"; + wwwRedirect = true; + serverAliases = [ "www.h8.work" ]; + }; + "halenger.com" = vhost { + documentRoot = "${home}/associat/h/halenger/domains/halenger.com"; + user = "halenger"; + group = "associat"; + wwwRedirect = true; + serverAliases = [ "www.halenger.com" ]; + }; + "lessthanthree.be" = vhost { + documentRoot = "${webtree}/o/ornat"; + user = "ornat"; + group = "member"; + wwwRedirect = true; + serverAliases = [ "www.lessthanthree.be" ]; + }; + "blog.lessthanthree.be" = vhost { + documentRoot = "${webtree}/o/ornat/blog"; + user = "ornat"; + group = "member"; + }; + "mlane.org" = vhost { + documentRoot = "${webtree}/a/ainran/mlane"; + user = "ainran"; + group = "associat"; + wwwRedirect = true; + serverAliases = [ "www.mlane.org" ]; + }; + "obrienronan.com" = vhost { + documentRoot = "${webtree}/vhosts/www.obrienronan.com"; + user = "mellow"; + group = "associat"; + wwwRedirect = true; + serverAliases = [ "www.obrienronan.com" ]; + }; "richardwalsh.ie" = vhost { documentRoot = "${webtree}/k/koffee/"; user = "koffee"; @@ -252,12 +341,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { "honk.for.faggots-on-strike.com" ]; }; - "room.${tld}" = vhost { - documentRoot = "${webtree}/e/edu/n109a"; - user = "edu"; - group = "redbrick"; - serverAliases = ["n109a.${tld}"]; - }; "ryanmcdyer.com" = vhost { documentRoot = "${webtree}/r/ryanmcd"; user = "ryanmcd"; @@ -265,16 +348,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { wwwRedirect = true; serverAliases = [ "www.ryanmcdyer.com" ]; }; - "sadsoc.${tld}" = vhost { - documentRoot = "${webtree}/a/art_wolf/sadsoc"; - user = "art_wolf"; - group = "associat"; - }; - "security.${tld}" = vhost { - documentRoot = "${webtree}/d/d_fens/security"; - user = "d_fens"; - group = "associat"; - }; "shaunneary.com" = vhost { documentRoot = "${webtree}/s/shaun/koken"; user = "shaun"; @@ -284,11 +357,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { ''; # TODO shout at shaun, his www. NS points to a different server }; - "signup.${tld}" = vhost { - documentRoot = "${webtree}/e/events/csday"; - user = "events"; - group = "redbrick"; - }; "solarsystemscanlan.com" = vhost { documentRoot = "${webtree}/s/singer/solarsystemscanlan.com/"; user = "singer"; @@ -303,11 +371,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { wwwRedirect = true; serverAliases = [ "www.songsfromtheparlour.com" ]; }; - "surfnsail.${tld}" = vhost { - documentRoot = "${webtree}/s/sailing"; - user = "sailing"; - group = "club"; - }; "techweek.dcu.ie" = vhost { documentRoot = "${webtree}/t/techwk/dist"; user = "techwk"; @@ -354,11 +417,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "nettles"; group = "associat"; }; - "wanderers.${tld}" = vhost { - documentRoot = "${webtree}/w/wander/"; - user = "wander"; - group = "projects"; - }; "wiki.colmreilly.com" = vhost { documentRoot = "${webtree}/n/nettles/wiki/"; user = "nettles"; @@ -371,11 +429,6 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { wwwRedirect = true; serverAliases = [ "www.ejmitchell.com" ]; }; - "www.iahpc.ie" = vhost { - documentRoot = "${home}/guest/iahpc/public_html"; - user = "iahpc"; - group = "guest"; - }; "www.luxgaa.lu" = vhost { documentRoot = "${webtree}/s/shivo/LuxGAA"; user = "shivo"; @@ -386,63 +439,5 @@ in (if (config.redbrick.skipVhosts) then {} else userVhosts // { user = "shivo"; group = "associat"; }; - "x-files.${tld}" = vhost { - documentRoot = "${webtree}/f/fox_chic"; - user = "fox_chic"; - group = "associat"; - }; - "yfg.${tld}" = vhost { - documentRoot = "${webtree}/f/finegael"; - user = "finegael"; - group = "associat"; - }; - "youth2000.${tld}" = vhost { - documentRoot = "${webtree}/y/youth2k"; - user = "gamessoc"; - group = "society"; - }; - "webmail.${tld}" = vhost { - documentRoot = "${webtree}/vhosts/rainloop"; - user = "wwwrun"; - group = "wwwrun"; - }; - "bash.${tld}" = vhost { - documentRoot = "${webtree}/y/yosarian/bash"; - user = "yosarian"; - group = "associat"; - }; - "git.${tld}" = vhostProxy "http://localhost:3000"; - "prometheus.${tld}" = vhostProxy "http://localhost:9090"; - "graphs.${tld}" = vhostProxy "http://localhost:3001"; - "dcufm.${tld}" = vhostProxy "http://localhost:8002"; - "jakarta.${tld}" = vhostProxy "http://136.206.15.59:8080"; - "lists.${tld}" = vhostProxy "http://mail.internal:80"; - "macspayn.${tld}" = vhostProxy "http://136.206.15.25:3007"; - "portaldev.${tld}" = vhostProxy "http://136.206.15.61:9080"; - "radio.${tld}" = vhostProxy "http://radio.${tld}:8000"; - "riainccc.${tld}" = vhostProxy "http://http://136.206.15.25:3000"; - "tomcat.dregin.${tld}" = vhostProxy "http://136.206.15.14:20002"; - "webchat.${tld}" = vhostProxy "http://127.0.0.1:16667"; - "werdztomcat.${tld}" = vhostProxy "http://136.206.15.14:20001"; - "www.${tld}" = vhostRedirect "https://${tld}"; - "admin.${tld}" = vhostRedirect "https://blog.${tld}"; - "admins.${tld}" = vhostRedirect "https://blog.${tld}"; - "ajaxterm.${tld}" = vhostRedirect "https://term.${tld}"; - "anyterm.${tld}" = vhostRedirect "https://term.${tld}"; - "dconcannon.${tld}" = vhostRedirect "https://shimoda.${tld}"; - "dermot.${tld}" = vhostRedirect "https://homer.${tld}"; - "devnull.${tld}" = vhostRedirect "https://colmmacc.${tld}"; - "devrandom.${tld}" = vhostRedirect "https://marvin.${tld}"; - "events.${tld}" = vhostRedirect "https://${tld}/events"; - "fosdem.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/ZwETj0"; - "github.${tld}" = vhostRedirect "https://github.com/redbrick"; - "help.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk"; - "helpdesk.${tld}" = vhostRedirect "https://wiki.${tld}/mw/Helpdesk"; - "helpdeskexam.${tld}" = vhostRedirect "https://md.${tld}/s/SJzip7F9X#"; - "hoodies.${tld}" = vhostRedirect "https://redbrickdcu.typeform.com/to/Q4uIzR"; - "parlour.${tld}" = vhostRedirect "https://songsfromtheparlour.com"; "radio.theinternets.be" = vhostRedirect "https://radio.${tld}"; - "techweek.${tld}" = vhostRedirect "https://techweek.dcu.ie"; - "tickets.${tld}" = vhostRedirect "https://dcusu.ticketsolve.com/shows/873599383/events/128190598"; - "ubuntu.${tld}" = vhostRedirect "https://wiki.${tld}/mw/RedBrick_Ubuntu"; -}) +})) diff --git a/services/libvirt.nix b/services/libvirt.nix new file mode 100644 index 00000000..637db563 --- /dev/null +++ b/services/libvirt.nix @@ -0,0 +1,6 @@ +{ + virtualisation.libvirtd = { + enable = true; + onShutdown = "shutdown"; + }; +} diff --git a/services/postfix/aliases.nix b/services/postfix/aliases.nix new file mode 100644 index 00000000..ca74163b --- /dev/null +++ b/services/postfix/aliases.nix @@ -0,0 +1,460 @@ +{tld}: { + # System, committee and important email aliases. + # + # NOTES + # ===== + # - For committee position aliases during the changeover period, list the + # outgoing person(s) first followed by the incoming person(s). + + #-----------------# + # SYSTEM ACCOUNTS # + #-----------------# + + # Redirections for system and pseudo accounts. + "MAILER-DAEMON" = "postmaster"; + "postmaster" = "root"; + "bin" = "root"; + "daemon" = "root"; + "man" = "root"; + "news" = "root"; + "nobody" = "/dev/null"; + "operator" = "root"; + "pop" = "root"; + "system" = "root"; + "toor" = "root"; + "usenet" = "news"; + "uucp" = "root"; + "xten" = "root"; + "postfix" = "root"; + "abuse" = "rb-admins,chair,sec"; + "security" = "root"; + "ftp" = "root"; + "ftp-bugs" = "ftp"; + "hostmaster" = "root"; + "nagios" = "root"; + "_nagios" = "root"; + "ossec" = "root"; + + # Mailman + "mailman" = "root"; + "mailman-owner" = "mailman"; + "mailman-request" = "mailman"; + "mailman-bounces" = "mailman"; + "mailman-admin" = "mailman"; + + # Where root mail goes. VERY IMPORTANT! + "root" = "rb-admins"; + + #----------------# + # Administrators # + #----------------# + + # Who wants to get system reports, cron job output etc. + "system-reports" = "rb-admins"; + "audit_warn" = "system-reports"; + + # Offical way to contact admins for requests. + "admin-request" = "rb-admins, ticket"; + "elected-admin" = "elected-admins"; + + # Where mail addressed to generic 'admins' goes. + "admin" = "admin-request"; + "admins" = "admin-request"; + + # DCU admin list. + # + # <plop>: Thu May 28 11:07:05 BST 1998 + "dcu-admin-list" = "rb-admins, sysops@dcu.ie, mcgorman@compapp.dcu.ie"; + + # Admin Mailing lists + "rb-admins" = "rb-admins@lists.${tld}"; + "elected-admins" = "elected-admins@lists.${tld}"; + "admin-discuss" = "admin-discuss@lists.${tld}"; + "trainee-admins" = "trainee-admins@lists.${tld}"; + + #-------------# + # Web related # + #-------------# + + # webmaster is a mailman list + "httpd" = "webmaster"; + "www" = "webmaster"; + "webmaster" = "webmaster@lists.${tld}"; + + #---------------------# + # Committee & Society # + #---------------------# + + # "The Founders" (TM) + "founders" = "drjolt, wibble, sandman, fergus, swipe, hyper"; + + # Committee is a mailing list (handled by mailman) + "committee" = "committee@lists.${tld}"; + + # HELP! requests. + "help" = "helpdesk"; + "support" = "helpdesk"; + "help-request" = "helpdesk"; + "helpdesk-request" = "helpdesk"; + "helpdesk" = "helpdesk@lists.${tld}"; + + # Chairperson alias. + "chairperson" = "chair"; + + # Treasurer alias. + "treasurer" = "treasure"; + + # Secretary alias. + "secretary" = "sec"; + + # Events alias. + "ents" = "events"; + "birthday" = "events"; + + # Accounts alias: + "accounts" = "elected-admins, treasurer, chair"; + + # Redbrick encyclopedia sybmissions/queries. + # + # wishkah gave webgroup encylopedia, on conditions he + # be included in mails about it - <bubble> + # + "encyclopedia" = "wishkah"; + + # c-hey maintainters alias. + "c-hey" = "pooka, colmmacc, bobb"; + + # DNS. + "dns" = "committee"; + + # cancel-announce + # Cthulhu Thu Jan 27 01:00:00 GMT 2000 + "cancel-announce" = "bobb"; + + #---------------# + # Miscellaneous # + #---------------# + + # Mailing List aliases + "commonroom" = "\"commonroom@lists.${tld}\""; + "latvia" = "\"latvia@lists.${tld}\""; + "renting" = "\"rental@lists.${tld}\""; + # this one is for pooka's mailing list + "learning-journal" = "learning-journal@lists.${tld}"; + + # User aliases + "wimax" = "johan"; + # Alias for STOCS to go with Web Page URL. (Added cthulhu) + "sillicon" = "stocs"; + "spamtastic" = "bubble"; + "su-webgroup" = "phil, arioch, p, esoteric"; + "senseigoclub" = "pooka, belial, plop+go"; + "blog" = "atlas"; + "skyhawk" = "declan"; + "tom.doyle" = "greenday"; + "hairforceone" = "greenday"; + "david.craig" = "vexation"; + "dave.murphy" = "drjolt"; + "david.murphy" = "drjolt"; + "s.maher" = "snoopie"; + "Robert.Carew" = "rob"; + "diablo" = "fergus"; + "antarbh" = "supres"; + "colin.whittaker" = "grimnar"; + "meaigs" = "afrodite"; + "Margaret.McGaley" = "afrodite"; + "Kevin.Cannon" = "p"; + "raz" = "ivor"; + "fergusos" = "shocks"; + "zirconia" = "zircon"; + "eoin.mcgrath" = "bob"; + "acahill" = "ace"; + "Ian.Hollingsworth" = "lemming"; + "comet" = "helmet"; + "thomas.kelly" = "kudo"; + "debug" = "ubiquity"; + "webradio" = "kudo, singer, cain, celery, thayl"; + "red_giant" = "redgiant"; + "dong" = "tunney"; + "donal.mulligan" = "thor"; + "donal" = "thor"; + "Tanya.Reilly" = "toaster"; + "mark.dunne" = "pixies"; + "cathal.thorne" = "bodie"; + "lickylips" = "lickylip"; + "youknowwho" = "lickylip"; + "john.canavan" = "tibor"; + "john" = "tibor"; + "john.lyons" = "homerj"; + "brian.scanlan" = "singer"; + "caroline.sheedy" = "bootie"; + "jon.lundberg" = "spock"; + "jonathan.lundberg" = "spock"; + "damien.martin" = "otto"; + "orla.mcgann" = "orly"; + "nigel.parkes" = "elmer"; + "eileen.gavin" = "munchkin"; + "john.looney" = "valen"; + "cian.synnott" = "pooka"; + "mothlamp" = "pooka"; + "kachun.leung" = "plop"; + "dermot.hanley" = "wibble"; + "mike.mchugh" = "sandman"; + "james.raferty" = "lecter"; + "james.raftery" = "lecter"; + "aoife.mcgoveran" = "hms"; + "andrew.lawless" = "andy"; + "shane.ohuid" = "wishkah"; + "daire.mckenna" = "fatwa"; + "john.barker" = "barkerj"; + "sean.cullen" = "hyper"; + "paraic.oceallaigh" = "swipe"; + "micheal.mchugh" = "sandman"; + "fergus.donohue" = "fergus"; + "barry.oneill" = "bubble"; + "aoife.cahill" = "ace"; + "sheila.pollard" = "sheila"; + "robert.crosbie" = "bobb"; + "bobb-spam" = "bobb"; + "bobb-spam0" = "bobb"; + "bobb-spam1" = "bobb"; + "bobb-spam2" = "bobb"; + "bobb-spam3" = "bobb"; + "bobb-spam4" = "bobb"; + "bobb-spam5" = "bobb"; + "bobb-spam6" = "bobb"; + "bobb-spam7" = "bobb"; + "bobb-spam8" = "bobb"; + "bobb-spam9" = "bobb"; + "adam.kelly" = "cthulhu"; + "justin.moran" = "cain"; + "cecily.murray" = "celery"; + "karl.podesta" = "kpodesta"; + "ronan.ryan" = "ledge"; + "johnny" = "jonny"; + "andrew.phillips" = "jesus"; + "hoi.chau.wong" = "whc"; + "paddy.grant" = "floppy"; + "patrick.grant" = "floppy"; + "julie.kerin" = "julie"; + "iddqd" = "macbain"; + "michael.mcginness" = "mikka"; + "conor.okane" = "cokane"; + "donal.hunt" = "redgiant"; + "brian.bambrick" = "moridin"; + "declan.brennan" = "wilma"; + "grainne.walsh" = "grainy"; + "philip.reynolds" = "phil"; + "phil.reynolds" = "phil"; + "mglennon" = "magluby"; + "sigh" = "mort"; + "mort" = "arioch"; + "craygor" = "element"; + "ledg" = "ledge"; + "amanda" = "dipso"; + "djhooker" = "wilma"; + "dell" = "sandman"; + "patsy" = "heavenly"; + "raf" = "turiel"; + "littlemisscomedy" = "lmc"; + "samresh" = "doomgod"; + "conor.coyle" = "squaw"; + "kevinbrennan" = "zircon"; + "alanthecat" = "alantc"; + "colm.maccarthaigh" = "colmmacc"; + "pizzateam" = "sonic"; + "holyspambatman" = "sonic"; + "david.johnston" = "emperor"; + "dermot.duffy" = "dizer"; + "dizerspam" = "dizer"; + "eoin.campbell" = "cambo"; + "neil.walsh" = "marvin"; + "barry" = "bubble"; + "anthony.moyles" = "huey"; + "mclarke" = "prolix"; + "mclark" = "prolix"; + "mark_campbell" = "mark"; + "mark.campbell" = "mark"; + "mcampbell" = "mark"; + "campbellm" = "mark"; + "pronane" = "kaos"; + "shane_tallon" = "del_boy"; + "wesley.gorman" = "badboy"; + "ciaran.kenny" = "goratrix"; + "trevor.johnston" = "trevj"; + "sinead.mcgivney" = "neady"; + "enda_dowling" = "deano"; + "david.concannon" = "shimoda"; + "gary_ludgate" = "dice"; + "martin.clarke" = "prolix"; + "cambo1982" = "cambo"; + "jonathan.walsh" = "melmoth"; + "ann.byrne" = "halfpint"; + "martin.harte" = "tuama"; + "lee.cash" = "brodie"; + "declan.oneill" = "dec"; + "keith.mcdonnell" = "bkeeper"; + "john.ruddy" = "phase"; + "peter.sinnott" = "link"; + "michael.dowling" = "mickeyd"; + "mickeydspam" = "mickeyd"; + "eoghan" = "atlas"; + "dru" = "drusilla"; + "wavehunt" = "johan"; + "stephen.ryan37" = "ryaner"; + "natashamaher" = "7of9"; + "huskerdu" = "phl"; + "credak" = "creadak"; + "craig.gavagan" = "creadak"; + "joeh762" = "kuze"; + "macattac" = "mak"; + "failho" = "carri"; + "bunny" = "bunbun"; + # This burd wanted this alias. She's a friend of mine (singer). + # She used to be these addresses. Get rid of them and you're + # all dead. Yes, even you, son of drjolt in the year 2525. + # ok boomer - m1cr0man, 2020 + "zorro" = "\"Aisling.NiCheallachain@irishlife.ie\""; + "aisfc" = "\"Aisling.NiCheallachain@irishlife.ie\""; + "assassins" = "art_wolf"; + "sarunas" = "svan"; + "sarunas.v" = "svan"; + "sarunas.vancevicius" = "svan"; + "eoghan.gaffney" = "atlas"; + "carbonkid" = "gaara"; + "waf" = "dregin"; + "surfnsail" = "sailing"; + "andrew.martin" = "werdz"; + "mieows" = "angelkat"; + "cocowtf" = "cocao"; + "patrickswayze" = "goldfish"; + "wolfhead" = "drg"; + "david.lynam" = "coconut"; + "cian.brennan" = "lil_cain"; + "cian" = "lil_cain"; + "biggestpenis" = "lil_cain"; + "eoghan.cotter" = "johan"; + "austin.halpin" = "haus"; + "shane.stacey" = "isaac702"; + "fruitcake" = "fructus"; + "fruitcaek" = "fructus"; + "lotta.mikkonen" = "attol"; + "diarmaid.mcmanus" = "elephant"; + "caroline.fuery" = "carri"; + "damien.rhatigan" = "dano"; + "jennifer.flynn" = "jennyf"; + "michael.odowd" = "nanaki"; + "microman" = "m1cr0man"; + "cian.butler" = "butlerx"; + # guess which one is the correct one + "kat.farrell" = "angelkat"; + "kat.farrel" = "angelkat"; + "kat.farell" = "angelkat"; + "kat.farel" = "angelkat"; + #for sonic + "alan.walsh" = "sonic"; + "alwalsh" = "sonic"; + "alanwalsh" = "sonic"; + "sonicthehedgehog" = "sonic"; + # aliases for receive :) + "andrew.harford" = "receive"; + "andrew.j.harford" = "receive"; + "andy.harford" = "receive"; + "winchair" = "angelkat"; + "winkat" = "angelkat"; + "starbuck" = "receive"; + # people spell bad + "recieve" = "receive"; + "andrew.hartford" = "receive"; + "lotta" = "attol"; + "powertax" = "pwrtaxi"; + + "gamestocs" = "gamessoc"; + "games1" = "gamessoc"; + "games2" = "gamessoc"; + "gamessoc1" = "gamessoc"; + "gamessoc2" = "gamessoc"; + "gamessoc3" = "gamessoc"; + "gamessoc4" = "gamessoc"; + "gamessoc5" = "gamessoc"; + "gamessoc6" = "gamessoc"; + "gamessoc7" = "gamessoc"; + "gamessoc8" = "gamessoc"; + "gamessoc9" = "gamessoc"; + "gamessoc10" = "gamessoc"; + "gamessoc11" = "gamessoc"; + "gamessoc12" = "gamessoc"; + "gamessoc13" = "gamessoc"; + + "jennyf" = "ribbons"; + "niall.gaffney" = "gamma"; + "richard.walsh" = "koffee"; + "vadim" = "vadimck"; + "lorcan.boyle" = "zergless"; + "robert.devereux" = "kylar"; + "christopher.boyle" = "greyman"; + "cboyle" = "greyman"; + "cliodhna.harrison" = "thegirl"; + "safrole" = "saf"; + "jonny" = "banjo"; + "xmeabhx" = "timelady"; + "jamesreilly" = "fun"; + "snowda" = "fordy"; + "funtrain" = "admins"; + "ladmins" = "admins"; + "tron" = "jammy"; + "amadan" = "marvin"; + "mike_d" = "marvin"; + "loky" = "chikatee"; + "scunni" = "jericho"; + "the_rock" = "jericho"; + "iruane" = "stark"; + "aurora" = "smf"; + "smithers" = "manuel"; + "jonnyb" = "nedd"; + "wwallace" = "epic"; + "lizard" = "creech"; + "lep" = "phat"; + "crispy" = "chris"; + "account" = "afsoc"; + "odie" = "me"; + "hriding" = "equest"; + "equestrian" = "equest"; + + "economosoc" = "econosoc"; + + # now a spamtrap - colmmacc + "sarahb" = "/dev/null"; + "audi_58" = "AUDI_S8"; + "c_clay" = "fallen"; + "rcummi" = "4aces"; + "imelda" = "fruity"; + "mulinho" = "mullins"; + "sully1" = "sully"; + "robert" = "gandalf"; + "d_omall" = "domall"; + "dhunt" = "zoro"; + "seth" = "stranger"; + "jolt-9" = "chalk"; + "gee_sus" = "gee"; + "jruddy" = "phase"; + "lynchman" = "mloc"; + "brianh" = "alantc"; + "sutty" = "yosarian"; + "ryanks" = "rhino"; + "aliastom" = "ayatolah"; + "osprey" = "pariah"; + "jbolger" = "x"; + "joey_p" = "mrs_girl"; + "vigdis" = "tom"; + "dimitri" = "grover"; + "oasis" = "geezer"; + "games" = "gamessoc"; + "smeghead" = "jeebers"; + "piesoc" = "matsoc"; + "cherub" = "crumbs"; + "dme3" = "dme4"; + "garyod2" = "gary"; + "haus17" = "haus"; + "nadned" = "damnson"; +} diff --git a/services/postfix/default.nix b/services/postfix/default.nix index 5fa29413..677cfe40 100644 --- a/services/postfix/default.nix +++ b/services/postfix/default.nix @@ -1,40 +1,82 @@ # Requires rspamadm dkim_keygen -k /var/secrets/${tld}.$(hostname).dkim.key -b 2048 -s $(hostname) -d ${tld} # chown rspamd:root chmod 400 -{config, pkgs, ...}: +{config, pkgs, lib, ...}: let tld = config.redbrick.tld; common = import ../../common/variables.nix; + aliases = import ./aliases.nix { inherit tld; }; + # TLD needs to be appended to use aliases as sender address with smtpd_sender_login_maps + # Only supports 1:1 mappings but could be modified to support 1:Many + aliasesAbsolute = lib.mapAttrs (alias: owner: if (builtins.match "^[a-zA-Z0-9_\\-\\+\\.]+$" owner) != null then "${owner}@${tld}" else owner) aliases; + aliasesFile = pkgs.writeText "postfix-aliases" (builtins.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}: ${v}") aliasesAbsolute)); + ldapCommon = '' server_host = ldap://${common.ldapHost}/ version = 3 bind = no + search_base = ou=accounts,o=redbrick ''; - ldapAliasMap = pkgs.writeText "virt-mailbox-maps" (ldapCommon + '' - search_base = ou=accounts,o=redbrick + # Authenticated users we always accept mail from over port 587 + # Allows mailman to spoof addresses + sender_whitelist = pkgs.writeText "sender_whitelist" '' + mailmgr@${tld} OK + ''; + + ldapSenderMap = pkgs.writeText "postfix-sender-maps" (ldapCommon + '' query_filter = (&(objectClass=posixAccount)(uid=%u)) result_attribute = uid result_format = %s@${tld} ''); + # Addresses we reject mail from over port 25 + sender_blacklist = pkgs.writeText "sender_blacklist" '' + ${tld} REJECT + ''; + + # IPs we reject unauthenticated connections from + # Rspamd explicitly allows mail from local addresses which is dangerous for us + # List taken from rspamd's local_addrs option + unauth_ip_blacklist = pkgs.writeText "unauth_ip_blacklist" '' + 127.0.0.0/8 REJECT + 192.168.0.0/16 REJECT + 10.0.0.0/8 REJECT + 172.16.0.0/12 REJECT + fd00::/8 REJECT + 169.254.0.0/16 REJECT + fe80::/10 REJECT + ''; + commonRestrictions = [ - "permit_mynetworks" "permit_sasl_authenticated" + "permit_sasl_authenticated" "reject_unauth_pipelining" ]; in { imports = [ + ../redis.nix + ./mailman.nix + ./rspamd.nix ./postsrsd.nix ]; + # Add postfix to redis group + users.users.postfix.extraGroups = [ + "redis" + ]; + # Ensure postsrsd is started before postfix systemd.services.postfix = { - requires = [ "postsrsd.service" ]; - after = [ "postsrsd.service" ]; + requires = [ "postsrsd.service" "redis.service" "rspamd.service" ]; + after = [ "postsrsd.service" "redis.service" "rspamd.service" ]; }; networking.firewall.allowedTCPPorts = [ 25 587 ]; + # Since the TLD cert is a wildcard, this allows us to use TLS + # over localhost and authenticate correctly. Used in mailman + networking.hosts."127.0.0.1" = [ "localmail.${tld}" ]; + security.dhparams.enable = true; security.dhparams.params.smtpd_512.bits = 512; security.dhparams.params.smtpd_2048.bits = 2048; @@ -64,18 +106,27 @@ in { # smtp_inet found in https://github.com/NixOS/nixpkgs/blob/54361cde9226ae6346b53b34acea9b493f803509/nixos/modules/services/mail/postfix.nix#L768 masterConfig.smtp_inet.args = [ "-o" "smtpd_sasl_auth_enable=no" ]; + # Files that need postmap run on them + # Added to /var/lib/postfix/conf/<name> + mapFiles.sender_whitelist = sender_whitelist; + mapFiles.sender_blacklist = sender_blacklist; + mapFiles.unauth_ip_blacklist = unauth_ip_blacklist; + + # Aliases + aliasFiles.redbrick_aliases = aliasesFile; + config = { # IP address used by postfix to send outgoing mail. You only need this if # your machine has multiple IP addresses - set it to your MX address to # satisfy your SPF record. - smtp_bind_address = "192.168.0.135"; + smtp_bind_address = config.redbrick.smtpBindAddress; # http://www.postfix.org/BASIC_CONFIGURATION_README.html#proxy_interfaces - proxy_interfaces = "136.206.15.5"; + proxy_interfaces = config.redbrick.smtpExternalAddress; - #virtual_mailbox_domains = tld; - #virtual_mailbox_maps = "hash:/var/lib/postfix/aliases"; - #virtual_alias_maps = "ldap:" ++ ./ldap-virtual-alias-maps.cf; - # alias_maps = "hash:/etc/aliases, ldap:"; + # Some bad clients...like MAILMAN... forget to add some important headers + # In particular I saw mailman forget Message-ID. This setting permits postfix + # to fix them + local_header_rewrite_clients = "permit_sasl_authenticated"; # Generate own DHParams smtpd_tls_dh512_param_file = config.security.dhparams.params.smtpd_512.path; @@ -86,11 +137,23 @@ in { # https://wiki.dovecot.org/HowTo/PostfixAndDovecotSASL smtpd_sasl_auth_enable = true; smtpd_sasl_type = "dovecot"; - smtpd_sasl_path = "inet:${common.dovecotHost}:${builtins.toString common.dovecotSaslPort}"; + smtpd_sasl_path = "unix:/var/run/dovecot2_sasl.sock"; - # deliver mail for virtual users to Dovecot's TCP socket + # Deliver mail for all users to Dovecot's LMTP socket # http://www.postfix.org/lmtp.8.html - mailbox_transport = "lmtp:inet:${common.dovecotHost}:${builtins.toString common.dovecotLmtpPort}"; + mailbox_transport = "lmtp:unix:/var/run/dovecot2_lmtp.sock"; + + # For the sake of possible NixOS overrides, + # set the default local_recipient_maps explicitly + # The $alias_maps trick means that aliases will resolve correctly + # Lightly documented here: http://www.postfix.org/LOCAL_RECIPIENT_README.html#main_config + local_recipient_maps = [ "ldap:${ldapSenderMap}" "$alias_maps" ]; + + # Require that registered accounts are authenticated to send mail as them + smtpd_sender_login_maps = [ "ldap:${ldapSenderMap}" "$alias_maps" ]; + + # Written to /etc/postfix by the nix config + alias_maps = [ "hash:/etc/postfix/redbrick_aliases" ]; # Configure postsrsd so that forwarded mail is "remailed" with a safe from address sender_canonical_maps = "tcp:127.0.0.1:${builtins.toString config.services.postsrsd.forwardPort}"; @@ -171,14 +234,25 @@ in { smtpd_helo_required = true; smtpd_helo_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [ + # Allow hosts with any hostname internally to connect + "permit_mynetworks" "reject_invalid_helo_hostname" "reject_non_fqdn_helo_hostname" # This will reject all incoming mail without a HELO hostname that # properly resolves in DNS. This is a somewhat restrictive check and may # reject legitimate mail. "reject_unknown_helo_hostname" ]); - smtpd_sender_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [ + smtpd_sender_restrictions = builtins.concatStringsSep ", " ([ + # Check the user isn't mailmgr + "check_sasl_access hash:/var/lib/postfix/conf/sender_whitelist" + # Not even good users should break these rules "reject_non_fqdn_sender" "reject_unknown_sender_domain" + # Allow authenticated users to send their email as themselves + "reject_sender_login_mismatch" "permit_sasl_authenticated" + # Prevent anyone from @${tld} sending mail unauthenticated + "check_sender_access hash:/var/lib/postfix/conf/sender_blacklist" + "reject_unlisted_sender" "reject_unauth_pipelining" + "warn_if_reject" "reject_unverified_sender" ]); smtpd_recipient_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [ "reject_non_fqdn_recipient" "reject_unknown_recipient_domain" @@ -188,31 +262,29 @@ in { "reject_multi_recipient_bounce" ]); smtpd_relay_restrictions = builtins.concatStringsSep ", " [ - "permit_mynetworks" "permit_sasl_authenticated" + # Check the user isn't mailmgr + "check_sasl_access hash:/var/lib/postfix/conf/sender_whitelist" + "reject_unlisted_sender" + "reject_sender_login_mismatch" "permit_sasl_authenticated" + # TODO Consider explicit reject here # !!! THIS SETTING PREVENTS YOU FROM BEING AN OPEN RELAY !!! "reject_unauth_destination" # !!! DO NOT REMOVE IT UNDER ANY CIRCUMSTANCES !!! ]; + smtpd_client_restrictions = builtins.concatStringsSep ", " (commonRestrictions ++ [ + # Allow hosts with any hostname internally to connect + "permit_mynetworks" + # Reject failed reverse records + "reject_unknown_reverse_client_hostname" + # Reject unauthenticated connections from local addresses. This is due to + # a limitation in rspamd which prevents us checking for spoofing internally + # see rspamd.nix + "check_client_a_access cidr:/var/lib/postfix/conf/unauth_ip_blacklist" + # This will reject all incoming connections without a reverse DNS + # entry that resolves back to the client's IP address. This is a very + # restrictive check and may reject legitimate mail. + "warn_if_reject" "reject_unknown_client_hostname" + ]); }; - - # Sets smtpd_client_restrictions - dnsBlacklists = commonRestrictions ++ [ - "reject_unknown_reverse_client_hostname" - # This will reject all incoming connections without a reverse DNS - # entry that resolves back to the client's IP address. This is a very - # restrictive check and may reject legitimate mail. - "reject_unknown_client_hostname" - ]; - }; - - # Enable rspamd and connect to postfix. - services.rspamd = { - enable = true; - postfix.enable = true; - locals."dkim_signing.conf".text = '' - path = "/var/secrets/$domain.$selector.dkim.key"; - selector = "${config.networking.hostName}"; - allow_username_mismatch = true; - ''; }; } diff --git a/services/postfix/exim2nix.py b/services/postfix/exim2nix.py new file mode 100755 index 00000000..c459c0be --- /dev/null +++ b/services/postfix/exim2nix.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Python 3.6+ +# Converts exim aliases text file to Nix +# Usage: cat exim_aliases.txt | ./exim2nix.py > aliases.nix +import sys + +print('{') +for l in sys.stdin: + if '#' not in l and ':' in l: + try: + alias, to = l.strip().split(':') + except: + print("Broken line", l.strip()) + continue + alias = alias.strip() + to = to.strip().replace('"', r'\"') + print(f" \"{alias}\" = \"{to}\";") + else: + print(l.strip()) +print('}') diff --git a/services/postfix/mailman.nix b/services/postfix/mailman.nix new file mode 100644 index 00000000..f96c9281 --- /dev/null +++ b/services/postfix/mailman.nix @@ -0,0 +1,160 @@ +# Manual steps post-deploy: +# cd /var/lib/mailman && sudo -u mailman mailman aliases && sysemctl restart postfix +{pkgs, config, lib, ...}: +let + common = import ../../common/variables.nix; + + tld = config.redbrick.tld; + secretsFile = "/var/secrets/mailman.json"; + secrets = import /var/secrets/mailman.nix; + postgresHost = "127.0.0.1"; + + mailServer = "localmail.${tld}"; + + # Mailman needs access to hyperkitty, which is on the same host + hyperkittyLocal = mailServer; +in { + services.mailman = { + enable = true; + siteOwner = "admins+mailman@${tld}"; + webHosts = [ "lists.${tld}" ]; + hyperkitty = { + enable = true; + baseUrl = "https://${hyperkittyLocal}/hyperkitty/"; + }; + }; + + # Mailman core has no real NixOS Options + # Extend the etc file.. + environment.etc."mailman.cfg" = { + mode = "0400"; + user = "mailman"; + text = '' + [mta] + smtp_host: ${mailServer} + smtp_port: 587 + smtp_user: ${secrets.emailUser} + smtp_pass: ${secrets.emailPassword} + smtp_secure_mode: starttls + ''; + }; + + # Our ldap has combined first name + last name (cn), and no email field + # If there's ever a Django dev looking at this and sees a better way to do it, + # PLEASE DO IT + environment.etc."mailman3/rbapp/__init__.py".text = '' + __version__ = '1.0.0' + default_app_config = 'rbapp.apps.RBAppConfig' + ''; + environment.etc."mailman3/rbapp/signals.py".text = '' + from django_auth_ldap.backend import populate_user + from django.dispatch import receiver + + @receiver(populate_user) + def on_populate_user(sender, **kwargs): + """Process population of a user.""" + user = kwargs.get('user', None) + ldap_user = kwargs.get('ldap_user', None) + + if not (user and ldap_user): + return + + user.email = user.username + "@${tld}" + + name_split = user.first_name.split() + if len(name_split) != 2: + return + + first_name, last_name = name_split + user.first_name = first_name + user.last_name = last_name + ''; + environment.etc."mailman3/rbapp/apps.py".text = '' + from django.apps import AppConfig + + class RBAppConfig(AppConfig): + name = 'rbapp' + verbose_name = 'RB App' + + def ready(self): + import rbapp.signals + ''; + environment.etc."mailman3/settings.py".text = '' + # Add ldap to search path + # Package must be updated if main python3 version changes + import site + site.addsitedir('${pkgs.python37Packages.ldap}/lib/python3.7/site-packages') + site.addsitedir('${pkgs.python37Packages.pyasn1-modules}/lib/python3.7/site-packages') + site.addsitedir('${pkgs.python37Packages.django-auth-ldap}/lib/python3.7/site-packages') + + import ldap + import json + from django_auth_ldap.config import LDAPSearch, PosixGroupType + + TIME_ZONE = 'Europe/Dublin' + ALLOWED_HOSTS = [ 'lists.${tld}', 'localmail.${tld}' ] + + # When initialising Mailman, comment this line out until you go to /admin and add a site + # Otherwise you might get "Site matching query does not exist" + SITE_ID = 2 + + INSTALLED_APPS = INSTALLED_APPS + ['rbapp'] + + AUTHENTICATION_BACKENDS = ( + 'django_auth_ldap.backend.LDAPBackend', + 'django.contrib.auth.backends.ModelBackend', + ) + + AUTH_LDAP_SERVER_URI = "ldap://${common.ldapHost}" + + # Use the user's own credentials to bind to LDAP. Allows for reading of + # special fields, and no need for a mailman LDAP user + AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True + AUTH_LDAP_USER_ATTRLIST = ["*", "+"] + AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=accounts,o=redbrick" + AUTH_LDAP_USER_ATTR_MAP = { + "username": "uid", + "first_name": "cn", + } + + AUTH_LDAP_GROUP_TYPE = PosixGroupType() + AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=accounts,o=redbrick", ldap.SCOPE_SUBTREE, "(uid=%(user)s)") + AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,o=redbrick", ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)") + + with open('${secretsFile}', 'r') as db_pass_file: + secrets = json.load(db_pass_file) + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'mailman', + 'USER': secrets['db_user'], + 'PASSWORD': secrets['db_password'], + 'HOST': '${postgresHost}', + 'PORT': '5432', + } + } + + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = '${mailServer}' + EMAIL_PORT = 587 + EMAIL_USE_TLS = True + EMAIL_HOST_USER = secrets['email_user'] + EMAIL_HOST_PASSWORD = secrets['email_password'] + DEFAULT_FROM_EMAIL = 'mailmgr@${tld}' + ACCOUNT_EMAIL_VERIFICATION = 'none' + + AUTH_LDAP_MIRROR_GROUPS = True + AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_superuser": "cn=mailadm,ou=groups,o=redbrick" + } + ''; + + services.postfix = { + relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ]; + config.transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; + config.local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; +} diff --git a/services/postfix/rspamd.nix b/services/postfix/rspamd.nix new file mode 100644 index 00000000..fc1756f0 --- /dev/null +++ b/services/postfix/rspamd.nix @@ -0,0 +1,116 @@ +{ config, ... }: +let + milterPort = "/run/rspamd/milter.sock"; +in { + # Not necessary since it's in the postfix import, but just in case + imports = [ + ../redis.nix + ]; + + # Add rspamd to redis group + users.users.rspamd.extraGroups = [ + "redis" + ]; + + # Ensure redis is started before rspamd + systemd.services.postfix = { + requires = [ "redis.service" ]; + after = [ "redis.service" ]; + }; + + services.rspamd = { + enable = true; + postfix = { + enable = true; + config = { + smtpd_milters = [ "unix:${milterPort}" ]; + non_smtpd_milters = [ "unix:${milterPort}" ]; + }; + }; + overrides."options.inc".text = '' + # Don't whitelist any IP addresses for SPF/DKIM checks + # This unfortunately doesn't work, at least when implemented in 2020 + # and thus we have to blacklist local IPs sending unauthed mail in postfix + local_addrs = []; + ''; + locals."redis.conf".text = '' + # Redis is needed for a number of modules + servers = "/run/redis/redis.sock"; + ''; + locals."dkim_signing.conf".text = '' + path = "/var/secrets/$domain.$selector.dkim.key"; + selector = "${config.networking.hostName}"; + allow_username_mismatch = true; + sign_local = false; + sign_authenticated = true; + use_esld = false; + ''; + locals."worker-controller.inc".text = '' + # generate a password hash using the `rspamadm pw` command and put it here + # This is git safe - it's a hash, for god sake + password = "$2$6znhwcxm4f3aja5d4cwaj1pheayfddms$13x4n1kn8frfnnx6mwkpchi3twd9napyqpf4pyom5rrqktxdgobb"; + enable_password = "$2$6znhwcxm4f3aja5d4cwaj1pheayfddms$13x4n1kn8frfnnx6mwkpchi3twd9napyqpf4pyom5rrqktxdgobb"; + + # dovecot will use this socket to communicate with rspamd + # RuntimeDirectory created by systemd in nixpkgs module for rspamd + bind_socket = "/run/rspamd/rspamd.sock mode=0660 owner=rspamd group=dovecot2"; + + # you can comment this out if you don't need the web interface + bind_socket = "127.0.0.1:11334"; + ''; + locals."worker-normal.inc".text = '' + # we're not running rspamd in a distributed setup, so this can be disabled + # the proxy worker will handle all the spam filtering + enabled = false; + ''; + locals."worker-proxy.inc".text = '' + # this worker will be used as postfix milter + milter = yes; + + # RuntimeDirectory created by systemd in nixpkgs module for rspamd + bind_socket = "${milterPort} mode=0660 owner=rspamd group=postfix"; + + # the following specifies self-scan mode, for when rspamd is on the same + # machine as postfix + timeout = 120s; + upstream "local" { + default = yes; + self_scan = yes; + } + ''; + locals."classifier-bayes.conf".text = '' + autolearn = true; + backend = "redis"; + ''; + locals."maillist.conf".text = ""; + locals."mx_check.conf".text = '' + enabled = true; + ''; + locals."phishing.conf".text = '' + openphis_enabled = true; + phishtank_enabled = true; + ''; + locals."replies.conf".text = '' + action = "no action"; + ''; + locals."spf.conf".text = '' + spf_cache_size = 2k; + spf_cache_expire = 1d; + max_dns_nesting = 8; + max_dns_requests = 20; + min_cache_ttl = 5m; + disable_ipv6 = false; + ''; + locals."dkim.conf".text = '' + dkim_cache_size = 2k; + ''; + locals."url_reputation.conf".text = '' + # Scan URLs + enabled = true; + ''; + locals."url_tags.conf".text = '' + # Redis caching of URL tags + enabled = true; + ''; + }; +} diff --git a/services/redis.nix b/services/redis.nix new file mode 100644 index 00000000..420a1b96 --- /dev/null +++ b/services/redis.nix @@ -0,0 +1,24 @@ +{ + users.groups.redis = {}; + users.users.redis.group = "redis"; + + # Create a log directory with systemd <3 + systemd.services.redis.serviceConfig.LogsDirectory = "redis"; + + services.redis = { + enable = true; + port = 0; + unixSocket = "/run/redis/redis.sock"; + + # Journal + logfile = "/var/log/redis/redis.log"; + syslog = false; + + extraConfig = '' + rdbcompression yes + maxmemory 2G + maxmemory-policy allkeys-lru + unixsocketperm 660 + ''; + }; +} diff --git a/services/ssh.nix b/services/ssh.nix index e7292afb..b1506679 100644 --- a/services/ssh.nix +++ b/services/ssh.nix @@ -1,15 +1,50 @@ -{ - services.openssh.enable = true; +{ pkgs, ... }: +let + whodir = "/run/whoroot"; + + # A wrapper over w which replaces root with the username from the key + # of the logged in rooter. The files in whodir are written by the + # loginShellInit + # The first regex removes the slash from the tty + whoroot = pkgs.writeShellScriptBin "wroot" '' + sedscript="s!pts/!pts!g;" + for f in ${whodir}/*; do + bf=$(basename $f) + sedscript="''${sedscript}s!root +$bf!$(cat $f)\t$bf!g;" + done + ${pkgs.procps}/bin/w | ${pkgs.gnused}/bin/sed -E "$sedscript" + ''; +in { + services.openssh = { + enable = true; + extraConfig = '' + PermitUserEnvironment yes + ''; + }; + users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnVLSh0OStxZTkXE6oGgwfFvsbvN6bFPlVfDYOwtnzn lucas@oatfield" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPR+OTAIYr02f/WKQSXo7zYy9tkuAHYpy0ajqY6aJ7Nk m1cr0man@redbrick.dcu.ie" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYKnYP4Mmyk4wQE7J6Tyr27XToKtxAhXBZr5HkEXiFq root@gelandewagen" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHvabMrrJILDua2sedVqBStb6YKBHpgCO5HOM98l7uwf greenday@DESKTOP-NJVR3G4" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjro8OS7cWf6xBcrs4erZqjN5JdztoGqpMXFQwzd9pV mctastic@azazel.redbrick.dcu.ie" - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLSLIF2IOo/OzbmbMGp4kt6VP2z8zNCuuVNyuxyBU0A8cOeUhkAbVibVmFqPlcHDJ4+zhkNN0GDnEJEUAmBNi+yc9EJG7StxdguEAKPlA9gQ/Z73cMrfMHtTPOHj/uCKUqi9vzb3tlOltJmuS3SwF0B5dk58j/cwr3nEEzikMmQIykxI+F+rxMnxaQXtNBGz3ednAaJ4Lvv9JSxWcExEtU0lM0X1MgZgkYFr48uQwsDUE+j23+wifMrOA+zhC0uRcuIapnxsyoW/wDOYrQZFlw6acrVX+zNtxcCQoqIX4oAobCXn7tYz9peKrV8TmJwQOspsmyY75xIAbyz0AiD3oh kyle@HP-Kyle" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/8Xf5/DtcOPjZKfag4ATBe5a3I1HvhYqi8fV7si4OU butlerx@notthe.cloud" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhA5mm1sBzz6tcrUF2FzW6wrckW1IsQAyS8Bfu4yJRJ d_fens@redbrick.dcu.ie" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGybjW48+tQaykqDIuSeuH/3GLQRHZDa1toJOIB/FrD4 fraz@azazel.redbrick.dcu.ie" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVc8zqnZkzsOHzfycpJ3QbB9SJ2FxmRRifYbBuuixk2 galvinio@azazel.redbrick.dcu.ie" + "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnVLSh0OStxZTkXE6oGgwfFvsbvN6bFPlVfDYOwtnzn m1cr0man@redbrick.dcu.ie" + "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPR+OTAIYr02f/WKQSXo7zYy9tkuAHYpy0ajqY6aJ7Nk m1cr0man@redbrick.dcu.ie" + "environment=\"REMOTEUSER=m1cr0man\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYKnYP4Mmyk4wQE7J6Tyr27XToKtxAhXBZr5HkEXiFq m1cr0man@redbrick.dcu.ie" + "environment=\"REMOTEUSER=greenday\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHvabMrrJILDua2sedVqBStb6YKBHpgCO5HOM98l7uwf greenday@redbrick.dcu.ie" + "environment=\"REMOTEUSER=mctastic\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjro8OS7cWf6xBcrs4erZqjN5JdztoGqpMXFQwzd9pV mctastic@azazel.redbrick.dcu.ie" + "environment=\"REMOTEUSER=ylmcc\" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLSLIF2IOo/OzbmbMGp4kt6VP2z8zNCuuVNyuxyBU0A8cOeUhkAbVibVmFqPlcHDJ4+zhkNN0GDnEJEUAmBNi+yc9EJG7StxdguEAKPlA9gQ/Z73cMrfMHtTPOHj/uCKUqi9vzb3tlOltJmuS3SwF0B5dk58j/cwr3nEEzikMmQIykxI+F+rxMnxaQXtNBGz3ednAaJ4Lvv9JSxWcExEtU0lM0X1MgZgkYFr48uQwsDUE+j23+wifMrOA+zhC0uRcuIapnxsyoW/wDOYrQZFlw6acrVX+zNtxcCQoqIX4oAobCXn7tYz9peKrV8TmJwQOspsmyY75xIAbyz0AiD3oh ylmcc@redbrick.dcu.ie" + "environment=\"REMOTEUSER=butlerx\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/8Xf5/DtcOPjZKfag4ATBe5a3I1HvhYqi8fV7si4OU butlerx@redbrick.dcu.ie" + "environment=\"REMOTEUSER=d_fens\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhA5mm1sBzz6tcrUF2FzW6wrckW1IsQAyS8Bfu4yJRJ d_fens@redbrick.dcu.ie" + "environment=\"REMOTEUSER=fraz\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGybjW48+tQaykqDIuSeuH/3GLQRHZDa1toJOIB/FrD4 fraz@redbrick.dcu.ie" + "environment=\"REMOTEUSER=galvinio\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVc8zqnZkzsOHzfycpJ3QbB9SJ2FxmRRifYbBuuixk2 galvinio@redbrick.dcu.ie" + "environment=\"REMOTEUSER=mcmahon\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBPzJW/r9XddCBa8Y1wLCt1FGNvGB3OD/fFzo19AE6/B mcmahon@redbrick.dcu.ie" + ]; + + environment.loginShellInit = '' + if [ -n "$REMOTEUSER" ]; then + mkdir -p ${whodir} + # The regex removes the slashes and /dev from the tty + echo $REMOTEUSER > ${whodir}/$(tty | sed -E 's!(/dev)?/!!g') + fi + ''; + + environment.systemPackages = [ + whoroot ]; }