diff --git a/config/fudo/acme-for-hostname.nix b/config/fudo/acme-for-hostname.nix
new file mode 100644
index 0000000..4dd5f2e
--- /dev/null
+++ b/config/fudo/acme-for-hostname.nix
@@ -0,0 +1,61 @@
+# Starts an Nginx server on $HOSTNAME just to get a cert for this host
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.fudo.acme;
+
+ wwwRoot = hostname:
+ pkgs.writeTextFile {
+ name = "index.html";
+
+ text = ''
+
+
+ ${hostname}
+
+
+ ${hostname}
+
+
+ '';
+ destination = "/www";
+ };
+
+in {
+
+ options.fudo.acme = {
+ hostnames = mkOption {
+ type = with types; listOf str;
+ description = "A list of hostnames mapping to this host, for which to acquire SSL certificates.";
+ default = [];
+ example = [
+ "my.hostname.com"
+ "alt.hostname.com"
+ ];
+ };
+ };
+
+ config = {
+
+ services.nginx = {
+ enable = true;
+
+ virtualHosts = listToAttrs
+ (map
+ (hostname:
+ nameValuePair hostname
+ {
+ enableACME = true;
+ forceSSL = true;
+ root = (wwwRoot hostname) + ("/" + "www");
+ })
+ cfg.hostnames);
+ };
+
+ security.acme.certs = listToAttrs
+ (map (hostname: nameValuePair hostname { email = "admin@fudo.org"; })
+ cfg.hostnames);
+ };
+}
diff --git a/config/fudo/authentication.nix b/config/fudo/authentication.nix
new file mode 100644
index 0000000..c88fa7c
--- /dev/null
+++ b/config/fudo/authentication.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.fudo.authentication;
+in {
+ options.fudo.authentication = {
+ enable = mkEnableOption "Use Fudo users & groups from LDAP.";
+
+ ssl-ca-certificate = mkOption {
+ type = types.str;
+ description = "Path to the CA certificate to use to bind to the server.";
+ };
+
+ bind-passwd-file = mkOption {
+ type = types.str;
+ description = "Path to a file containing the password used to bind to the server.";
+ };
+
+ ldap-url = mkOption {
+ type = types.str;
+ description = "URL of the LDAP server.";
+ example = "ldaps://auth.fudo.org";
+ };
+
+ base = mkOption {
+ type = types.str;
+ description = "The LDAP base in which to look for users.";
+ default = "dc=fudo,dc=org";
+ };
+
+ bind-dn = mkOption {
+ type = types.str;
+ description = "The DN with which to bind the LDAP server.";
+ default = "cn=auth_reader,dc=fudo,dc=org";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.ldap = {
+ enable = true;
+ base = cfg.base;
+ bind = {
+ distinguishedName = cfg.bind-dn;
+ passwordFile = cfg.bind-passwd-file;
+ timeLimit = 5;
+ };
+ loginPam = true;
+ nsswitch = true;
+ server = cfg.ldap-url;
+ timeLimit = 5;
+ useTLS = true;
+ extraConfig = ''
+ TLS_CACERT ${cfg.ssl-ca-certificate}
+ TSL_REQCERT allow
+ '';
+
+ daemon = {
+ enable = true;
+ extraConfig = ''
+ tls_cacertfile ${cfg.ssl-ca-certificate}
+ tls_reqcert allow
+ '';
+ };
+ };
+ };
+}
diff --git a/config/fudo/common.nix b/config/fudo/common.nix
new file mode 100644
index 0000000..3679ff9
--- /dev/null
+++ b/config/fudo/common.nix
@@ -0,0 +1,16 @@
+# General Fudo config, shared across packages
+{ config, lib, pkgs, ... }:
+
+with lib;
+{
+ options.fudo.common = {
+ local-networks = mkOption {
+ type = with types; listOf str;
+ description = ''
+ A list of networks to consider 'local'. Used by various services to
+ limit access to the external world.
+ '';
+ default = [];
+ };
+ };
+}
diff --git a/config/fudo/grafana.nix b/config/fudo/grafana.nix
new file mode 100644
index 0000000..10ee540
--- /dev/null
+++ b/config/fudo/grafana.nix
@@ -0,0 +1,127 @@
+# NOTE: this assumes that postgres is running locally.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.fudo.grafana;
+
+ database-name = "grafana";
+ database-user = "grafana";
+
+in {
+
+ options.fudo.grafana = {
+ enable = mkEnableOption "Fudo Metrics Display Service";
+
+ hostname = mkOption {
+ type = types.str;
+ description = "Grafana site hostname.";
+ example = "fancy-graphs.fudo.org";
+ };
+
+ smtp-username = mkOption {
+ type = types.str;
+ description = "Username with which to send email.";
+ };
+
+ smtp-password-file = mkOption {
+ type = types.path;
+ description = "Path to a file containing the email user's password.";
+ };
+
+ database-password-file = mkOption {
+ type = types.path;
+ description = "Path to a file containing the database user's password.";
+ };
+
+ admin-password-file = mkOption {
+ type = types.path;
+ description = "Path to a file containing the admin user's password.";
+ };
+
+ secret-key-file = mkOption {
+ type = types.path;
+ description = "Path to a file containing the server's secret key, used for signatures.";
+ };
+
+ prometheus-host = mkOption {
+ type = types.str;
+ description = "The URL of the prometheus data source.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ fudo.postgresql = {
+ databases.${database-name} = {};
+
+ local-users.${database-user} = {
+ password = (fileContents cfg.database-password-file);
+ databases = ["${database-name}"];
+ };
+ };
+
+ services.nginx = {
+ enable = true;
+
+ virtualHosts = {
+ "${cfg.hostname}" = {
+ enableACME = true;
+ forceSSL = true;
+
+ locations."/" = {
+ proxyPass = "http://127.0.0.1:3000";
+
+ 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;
+ '';
+ };
+ };
+ };
+ };
+
+ services.grafana = {
+ enable = true;
+
+ addr = "127.0.0.1";
+ protocol = "http";
+ domain = "${cfg.hostname}";
+ rootUrl = "https://${cfg.hostname}/";
+
+ security = {
+ adminPasswordFile = cfg.admin-password-file;
+ secretKeyFile = cfg.secret-key-file;
+ };
+
+ smtp = {
+ enable = true;
+ fromAddress = "metrics@fudo.org";
+ host = "mail.fudo.org:25";
+ user = cfg.smtp-username;
+ passwordFile = cfg.smtp-password-file;
+ };
+
+ database = {
+ host = "localhost";
+ name = database-name;
+ user = database-user;
+ passwordFile = cfg.database-password-file;
+ type = "postgres";
+ };
+
+ provision.datasources = [
+ {
+ editable = false;
+ isDefault = true;
+ name = cfg.prometheus-host;
+ type = "prometheus";
+ url = "https://${cfg.prometheus-host}/";
+ }
+ ];
+ };
+ };
+}
diff --git a/config/fudo/kdc.nix b/config/fudo/kdc.nix
new file mode 100644
index 0000000..71954f4
--- /dev/null
+++ b/config/fudo/kdc.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+ cfg = config.fudo.auth.kdc;
+
+ stringJoin = joiner: attrList:
+ if (length attrList) == 0 then
+ ""
+ else
+ foldr(lAttr: rAttr: "${lAttr}${joiner}${rAttr}") (last attrList) (init attrList);
+
+in {
+
+ options.fudo.auth.kdc = {
+ enable = mkEnableOption "Fudo KDC";
+
+ database-path = mkOption {
+ type = types.str;
+ description = ''
+ The path at which to store the database files.
+ '';
+ default = "/var/heimdal/heimdal";
+ };
+
+ realm = mkOption {
+ type = types.str;
+ description = ''
+ The realm for which we are the acting KDC.
+ '';
+ };
+
+ mkey-file = mkOption {
+ type = types.str;
+ description = ''
+ The path to the master key file.
+ '';
+ };
+
+ acl-file = mkOption {
+ type = types.str;
+ description = ''
+ The path to the Access Control file.
+ '';
+ };
+
+ bind-addresses = mkOption {
+ type = with types; listOf str;
+ description = ''
+ A list of IP addresses on which to bind.
+ '';
+ default = [];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment = {
+ systemPackages = [
+ pkgs.heimdalFull
+ ];
+
+ etc."krb5.conf" = {
+ text = mkAfter ''
+ [kdc]
+ database = {
+ realm = ${cfg.realm}
+ mkey_file = ${cfg.mkey-file}
+ acl_file = ${cfg.acl-file}
+ }
+ addresses = ${stringJoin " " cfg.bind-addresses}
+ enable-http = true
+ '';
+ };
+ };
+
+ systemd.services = {
+ heimdal-kdc = {
+ enable = true;
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ description = "Heimdal Kerberos Key Distribution Center (ticket server)";
+ serviceConfig = {
+ ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kdc'';
+ };
+ };
+
+ heimdal-admin-server = {
+ enable = true;
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ description = "Heimdal Kerberos Remote Administration Server";
+ serviceConfig = {
+ ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kadmind'';
+ };
+ };
+ };
+ };
+}
diff --git a/config/fudo/ldap.nix b/config/fudo/ldap.nix
index 20ac8d8..d7a5578 100644
--- a/config/fudo/ldap.nix
+++ b/config/fudo/ldap.nix
@@ -206,10 +206,11 @@ in {
};
sslCACert = mkOption {
- type = types.str;
+ type = with types; nullOr str;
description = ''
The path to the SSL CA cert used to sign the certificate.
'';
+ default = null;
};
organization = mkOption {
@@ -329,7 +330,7 @@ in {
TLSCertificateFile ${cfg.sslCert}
TLSCertificateKeyFile ${cfg.sslKey}
- TLSCACertificateFile ${cfg.sslCACert}
+ ${optionalString (cfg.sslCACert != null) "TLSCACertificateFile ${cfg.sslCACert}"}
authz-regexp "^uid=auth/([^.]+)\.fudo\.org,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=$1,ou=hosts,dc=fudo,dc=org"
authz-regexp "^uid=[^,/]+/root,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=admin,dc=fudo,dc=org"
@@ -363,8 +364,9 @@ access to dn.subtree="ou=groups,${cfg.base}" attrs=memberUid
by users read
by * none
-access to dn.subtree="ou=members,${cfg.base}" attrs=cn,sn,homeDirectory,loginShell,gecos,description
+access to dn.subtree="ou=members,${cfg.base}" attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber
by group.exact="cn=admin,ou=groups,${cfg.base}" write
+ by dn.exact="cn=user_db_reader,${cfg.base}" read
by users read
by * none
@@ -384,6 +386,9 @@ access to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by users read
by * none
+
+
+index objectClass,uid eq
'';
declarativeContents = ''
diff --git a/config/fudo.nix b/config/fudo/local-network.nix
similarity index 91%
rename from config/fudo.nix
rename to config/fudo/local-network.nix
index 631a1e1..29967c2 100644
--- a/config/fudo.nix
+++ b/config/fudo/local-network.nix
@@ -1,3 +1,7 @@
+# UNFINISHED!
+#
+# The plan is to bootstrap a local network config: DNS, DHCP, etc.
+
{ lib, config, pkgs, ... }:
with lib;
@@ -33,14 +37,14 @@ let
ipv6Address = mkOption {
type = types.str;
description = ''
- The V6 IP of a given host, if any.
+ The V6 IP of this nameserver, if any.
'';
};
ipv4Address = mkOption {
type = types.str;
description = ''
- The V4 IP of a given host, if any.
+ The V4 IP of this nameserver, if any.
'';
};
@@ -56,9 +60,6 @@ let
};
in {
- imports = [
- ./fudo/ldap.nix
- ];
options = {
diff --git a/config/fudo/mail-container.nix b/config/fudo/mail-container.nix
new file mode 100644
index 0000000..948172b
--- /dev/null
+++ b/config/fudo/mail-container.nix
@@ -0,0 +1,256 @@
+{ config, lib, ... }:
+
+with lib;
+let
+ cfg = config.fudo.mail-server;
+ container-maildir = "/var/lib/mail";
+ container-statedir = "/var/lib/mail-state";
+ container-shared = "container/mail-server";
+ container-postfix-cert = "${container-shared}/postfix/cert.pem";
+ container-postfix-key = "${container-shared}/postfix/key.pem";
+ container-dovecot-cert = "${container-shared}/dovecot/cert.pem";
+ container-dovecot-key = "${container-shared}/dovecot/key.pem";
+ container-fudo-ca-cert = "${container-shared}/fudo-ca.pem";
+
+ # Don't bother with group-id, nixos doesn't seem to use it anyway
+ container-mail-user = "mailer";
+ container-mail-user-id = 542;
+ container-mail-group = "mailer";
+ trusted-networks = config.fudo.prometheus.trusted-networks;
+
+in rec {
+ options.fudo.mail-server.container = {
+ ldap-url = mkOption {
+ type = types.str;
+ description = "URL of the LDAP server to use for authentication.";
+ example = "ldaps://auth.fudo.org/";
+ };
+
+ # host-ip = mkOption {
+ # type = types.str;
+ # description = "The IP to assign to this server, for communication with the mail server container.";
+ # default = "10.110.0.1";
+ # };
+
+ # container-ip = mkOption {
+ # type = types.str;
+ # description = "The IP to assign to the mail server container.";
+ # default = "10.110.0.2";
+ # };
+ };
+
+ config = mkIf (cfg.enableContainer && !cfg.enable) {
+
+ # Disable postfix on thi host--it'll be run in the container instead
+ services.postfix.enable = false;
+
+ # Copy data intended for the container to a path in /etc which can be
+ # bind-mounted.
+ environment.etc = {
+ "${container-postfix-cert}" = {
+ mode = "0444";
+ source = cfg.postfix.ssl-certificate;
+ };
+
+ "${container-postfix-key}" = {
+ mode = "0400";
+ source = cfg.postfix.ssl-private-key;
+ };
+
+ "${container-dovecot-cert}" = {
+ mode = "0444";
+ source = cfg.dovecot.ssl-certificate;
+ };
+
+ "${container-dovecot-key}" = {
+ mode = "0400";
+ source = cfg.dovecot.ssl-private-key;
+ };
+
+ "${container-fudo-ca-cert}" = {
+ mode = "0444";
+ source = "/etc/nixos/static/fudo_ca.pem";
+ };
+ };
+
+ services.nginx = mkIf cfg.monitoring {
+ enable = true;
+
+ virtualHosts = let
+ proxy-headers = ''
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ ''
+ ;
+ trusted-network-string = optionalString ((length trusted-networks) > 0)
+ (concatStringsSep "\n"
+ (map (network: "allow ${network};") trusted-networks)) + "\ndeny all;";
+ in {
+ "${cfg.hostname}" = {
+ enableACME = true;
+ forceSSL = true;
+
+ locations."/metrics/postfix" = {
+ proxyPass = "http://127.0.0.1:9154/metrics";
+
+ extraConfig = ''
+ ${proxy-headers}
+
+ ${trusted-network-string}
+ '';
+ };
+
+ locations."/metrics/dovecot" = {
+ proxyPass = "http://127.0.0.1:9166/metrics";
+
+ extraConfig = ''
+ ${proxy-headers}
+
+ ${trusted-network-string}
+ '';
+ };
+
+ locations."/metrics/rspamd" = {
+ proxyPass = "http://127.0.0.1:7980/metrics";
+
+ extraConfig = ''
+ ${proxy-headers}
+
+ ${trusted-network-string}
+ '';
+ };
+ };
+ };
+ };
+
+ # services.xinetd = let
+ # xinetd-entry = name: port: {
+ # name = name;
+ # port = port;
+ # protocol = "tcp";
+ # server = "";
+ # extraConfig = ''
+ # socket_type = stream
+ # wait = no
+ # redirect = ${cfg.container.container-ip} ${toString port}
+ # '';
+ # };
+ # in {
+ # enable = true;
+ # services = [
+ # (xinetd-entry "smtp" 25)
+ # (xinetd-entry "pop3" 110)
+ # (xinetd-entry "pop3s" 995)
+ # (xinetd-entry "imap" 143)
+ # (xinetd-entry "imaps" 993)
+ # (xinetd-entry "submission" 587)
+ # ];
+ # };
+
+ containers.mail-server = {
+
+ autoStart = true;
+
+ bindMounts = {
+ "${container-maildir}" = {
+ hostPath = cfg.mail-directory;
+ isReadOnly = false;
+ };
+
+ "${container-statedir}" = {
+ hostPath = cfg.state-directory;
+ isReadOnly = false;
+ };
+
+ "/etc/${container-shared}" = {
+ hostPath = "/etc/${container-shared}";
+ isReadOnly = true;
+ };
+ };
+
+ config = { config, pkgs, ... }: {
+
+ environment.systemPackages = with pkgs; [
+ nmap
+ ];
+
+ imports = [
+ ../local.nix
+ ];
+
+ environment = {
+ etc = {
+ "postfix-certs/key.pem" = {
+ source = "/etc/${container-postfix-key}";
+ user = config.services.postfix.user;
+ mode = "0400";
+ };
+
+ "dovecot-certs/key.pem" = {
+ source = "/etc/${container-dovecot-key}";
+ user = config.services.dovecot2.user;
+ mode = "0400";
+ };
+ };
+ };
+
+ users = {
+ users = {
+ ${container-mail-user} = {
+ isSystemUser = true;
+ uid = container-mail-user-id;
+ group = "mailer";
+ };
+ };
+
+ groups = {
+ ${container-mail-group} = {
+ members = ["mailer"];
+ };
+ };
+ };
+
+ fudo.mail-server =
+ {
+ enable = true;
+ hostname = cfg.hostname;
+ domain = cfg.domain;
+
+ debug = cfg.debug;
+ monitoring = cfg.monitoring;
+
+ state-directory = container-statedir;
+ mail-directory = container-maildir;
+
+ postfix.ssl-certificate = "/etc/${container-postfix-cert}";
+ postfix.ssl-private-key = "/etc/postfix-certs/key.pem";
+
+ dovecot = {
+ ssl-certificate = "/etc/${container-dovecot-cert}";
+ ssl-private-key = "/etc/dovecot-certs/key.pem";
+ ldap-ca = "/etc/${container-fudo-ca-cert}";
+ ldap-urls = cfg.dovecot.ldap-urls;
+ ldap-reader-dn = cfg.dovecot.ldap-reader-dn;
+ ldap-reader-passwd = cfg.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;
+ };
+ };
+ };
+ };
+}
diff --git a/config/fudo/mail.nix b/config/fudo/mail.nix
new file mode 100644
index 0000000..7b664a8
--- /dev/null
+++ b/config/fudo/mail.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, environment, ... }:
+
+with lib;
+let
+ inherit (lib.strings) concatStringsSep;
+ cfg = config.fudo.mail-server;
+
+in {
+
+ options.fudo.mail-server = {
+ enable = mkEnableOption "Fudo Email Server";
+
+ enableContainer = mkEnableOption ''
+ Run the mail server in a container.
+
+ Mutually exclusive with mail-server.enable.
+ '';
+
+ domain = mkOption {
+ type = types.str;
+ description = "The main and default domain name for this email server.";
+ };
+
+ hostname = mkOption {
+ type = types.str;
+ description = "The domain name to use for the mail server.";
+ };
+
+ monitoring = mkEnableOption "Enable monitoring for the mail server.";
+
+ mail-user = mkOption {
+ type = types.str;
+ description = "User to use for mail delivery.";
+ };
+
+ # No group id, because NixOS doesn't seem to use it
+ mail-group = mkOption {
+ type = types.str;
+ description = "Group to use for mail delivery.";
+ };
+
+ mail-user-id = mkOption {
+ type = types.int;
+ description = "UID of mail-user.";
+ };
+
+ local-domains = mkOption {
+ type = with types; listOf str;
+ description = "A list of domains for which we accept mail.";
+ default = ["localhost" "localhost.localdomain"];
+ example = [
+ "localhost"
+ "localhost.localdomain"
+ "somedomain.com"
+ "otherdomain.org"
+ ];
+ };
+
+ mail-directory = mkOption {
+ type = types.str;
+ description = "Path to use for mail storage.";
+ };
+
+ state-directory = mkOption {
+ type = types.str;
+ description = "Path to use for state data.";
+ };
+
+ trusted-networks = mkOption {
+ type = with types; listOf str;
+ description = "A list of trusted networks, for which we will happily relay without auth.";
+ example = [
+ "10.0.0.0/16"
+ "192.168.0.0/24"
+ ];
+ };
+
+ sender-blacklist = mkOption {
+ type = with types; listOf str;
+ description = "A list of email addresses for whom we will not send email.";
+ default = [];
+ example = [
+ "baduser@test.com"
+ "change-pw@test.com"
+ ];
+ };
+
+ recipient-blacklist = mkOption {
+ type = with types; listOf str;
+ description = "A list of email addresses for whom we will not accept email.";
+ default = [];
+ example = [
+ "baduser@test.com"
+ "change-pw@test.com"
+ ];
+ };
+
+ message-size-limit = mkOption {
+ type = types.int;
+ description = "Size of max email in megabytes.";
+ default = 30;
+ };
+
+ user-aliases = mkOption {
+ type = with types; loaOf(listOf str);
+ description = "A map of real user to list of aliases.";
+ example = {
+ someuser = ["alias0" "alias1"];
+ };
+ };
+
+ alias-users = mkOption {
+ type = with types; loaOf(listOf str);
+ description = "A map of email alias to a list of users.";
+ example = {
+ alias = ["realuser0" "realuser1"];
+ };
+ };
+
+ mailboxes = mkOption {
+ description = ''
+ The mailboxes for dovecot.
+
+ Depending on the mail client used it might be necessary to change some mailbox's name.
+ '';
+ default = [
+ {
+ name = "Trash";
+ auto = "no";
+ specialUse = "Trash";
+ }
+ {
+ name = "Junk";
+ auto = "subscribe";
+ specialUse = "Junk";
+ }
+ {
+ name = "Drafts";
+ auto = "subscribe";
+ specialUse = "Drafts";
+ }
+ {
+ name = "Sent";
+ auto = "subscribe";
+ specialUse = "Sent";
+ }
+ ];
+ };
+
+ debug = mkOption {
+ description = "Enable debugging on mailservers.";
+ type = types.bool;
+ default = false;
+ };
+
+ max-user-connections = mkOption {
+ description = "Max simultaneous connections per user.";
+ type = types.int;
+ default = 20;
+ };
+ };
+
+ imports = [
+ ./mail/dkim.nix
+ ./mail/dovecot.nix
+ ./mail/postfix.nix
+ ./mail/rspamd.nix
+ ./mail/clamav.nix
+ ];
+}
diff --git a/config/fudo/mail/clamav.nix b/config/fudo/mail/clamav.nix
new file mode 100644
index 0000000..4dd2a74
--- /dev/null
+++ b/config/fudo/mail/clamav.nix
@@ -0,0 +1,28 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.fudo.mail-server;
+
+in {
+ options.fudo.mail-server.clamav = {
+ enable = mkOption {
+ description = "Enable virus scanning with ClamAV.";
+ type = types.bool;
+ default = true;
+ };
+ };
+
+ config = mkIf (cfg.enable && cfg.clamav.enable) {
+
+ services.clamav = {
+ daemon = {
+ enable = true;
+ extraConfig = ''
+ PhishingScanURLs no
+ '';
+ };
+ updater.enable = true;
+ };
+ };
+}
diff --git a/config/fudo/mail/dkim.nix b/config/fudo/mail/dkim.nix
new file mode 100644
index 0000000..3e0cb48
--- /dev/null
+++ b/config/fudo/mail/dkim.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.fudo.mail-server;
+
+ createDomainDkimCert = dom:
+ let
+ dkim_key = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key";
+ dkim_txt = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.txt";
+ in
+ ''
+ if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]
+ then
+ ${cfg.dkim.package}/bin/opendkim-genkey -s "${cfg.dkim.selector}" \
+ -d "${dom}" \
+ --bits="${toString cfg.dkim.key-bits}" \
+ --directory="${cfg.dkim.key-directory}"
+ mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.private" "${dkim_key}"
+ mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.txt" "${dkim_txt}"
+ echo "Generated key for domain ${dom} selector ${cfg.dkim.selector}"
+ fi
+ '';
+
+ createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.local-domains);
+
+ keyTable = pkgs.writeText "opendkim-KeyTable"
+ (lib.concatStringsSep "\n" (lib.flip map cfg.local-domains
+ (dom: "${dom} ${dom}:${cfg.dkim.selector}:${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key")));
+ signingTable = pkgs.writeText "opendkim-SigningTable"
+ (lib.concatStringsSep "\n" (lib.flip map cfg.local-domains (dom: "${dom} ${dom}")));
+
+ dkim = config.services.opendkim;
+ args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
+in
+{
+
+ options.fudo.mail-server.dkim = {
+ signing = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable dkim signatures for mail.";
+ };
+
+ key-directory = mkOption {
+ type = types.str;
+ default = "/var/dkim";
+ description = "Path to use to store DKIM keys.";
+ };
+
+ selector = mkOption {
+ type = types.str;
+ default = "mail";
+ description = "Name to use for mail-signing keys.";
+ };
+
+ key-bits = mkOption {
+ type = types.int;
+ default = 2048;
+ description = ''
+ How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
+
+ If you have already deployed a key with a different number of bits than specified
+ here, then you should use a different selector (dkimSelector). In order to get
+ this package to generate a key with the new number of bits, you will either have to
+ change the selector or delete the old key file.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.opendkim;
+ description = "OpenDKIM package to use.";
+ };
+ };
+
+ config = mkIf (cfg.dkim.signing && cfg.enable) {
+ services.opendkim = {
+ enable = true;
+ selector = cfg.dkim.selector;
+ domains = "csl:${builtins.concatStringsSep "," cfg.local-domains}";
+ configFile = pkgs.writeText "opendkim.conf" (''
+ Canonicalization relaxed/simple
+ UMask 0002
+ Socket ${dkim.socket}
+ KeyTable file:${keyTable}
+ SigningTable file:${signingTable}
+ '' + (lib.optionalString cfg.debug ''
+ Syslog yes
+ SyslogSuccess yes
+ LogWhy yes
+ ''));
+ };
+
+ users.users = {
+ "${config.services.postfix.user}" = {
+ extraGroups = [ "${config.services.opendkim.group}" ];
+ };
+ };
+
+ systemd.services.opendkim = {
+ preStart = lib.mkForce createAllCerts;
+ serviceConfig = {
+ ExecStart = lib.mkForce "${cfg.dkim.package}/bin/opendkim ${escapeShellArgs args}";
+ PermissionsStartOnly = lib.mkForce false;
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dkim.key-directory}' - ${config.services.opendkim.user} ${config.services.opendkim.group} - -"
+ ];
+ };
+}
diff --git a/config/fudo/mail/dovecot.nix b/config/fudo/mail/dovecot.nix
new file mode 100644
index 0000000..6c5d4fd
--- /dev/null
+++ b/config/fudo/mail/dovecot.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, environment, ... }:
+
+with lib;
+let
+ cfg = config.fudo.mail-server;
+
+ state-directory = "${cfg.state-directory}/dovecot";
+
+ pipe-bin = pkgs.stdenv.mkDerivation {
+ name = "pipe_bin";
+ src = ./dovecot/pipe_bin;
+ buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
+ buildCommand = ''
+ mkdir -p $out/pipe/bin
+ cp $src/* $out/pipe/bin/
+ chmod a+x $out/pipe/bin/*
+ patchShebangs $out/pipe/bin
+
+ for file in $out/pipe/bin/*; do
+ wrapProgram $file \
+ --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
+ done
+ '';
+ };
+
+ ldap-conf = filename: uris:
+ pkgs.writeText filename ''
+ uris = ${concatStringsSep " " uris}
+ ldap_version = 3
+ dn = ${cfg.dovecot.ldap-reader-dn}
+ dnpass = ${cfg.dovecot.ldap-reader-passwd}
+ auth_bind = yes
+ auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
+ base = dc=fudo,dc=org
+ # tls_ca_cert_file = ${cfg.dovecot.ldap-ca}
+ # FIXME: turn back on when certs work
+ tls = no
+ tls_require_cert = try
+ '';
+
+ dovecot-user = config.services.dovecot2.user;
+
+in {
+ options.fudo.mail-server.dovecot = {
+ ssl-private-key = mkOption {
+ type = types.str;
+ description = "Location of the server SSL private key.";
+ };
+
+ ssl-certificate = mkOption {
+ type = types.str;
+ description = "Location of the server SSL certificate.";
+ };
+
+ ldap-ca = mkOption {
+ type = types.str;
+ description = "The path to the CA cert used to sign the LDAP server certificate.";
+ };
+
+ ldap-urls = mkOption {
+ type = with types; listOf str;
+ description = "The urls of LDAP servers.";
+ };
+
+ ldap-reader-dn = mkOption {
+ type = types.str;
+ description = ''
+ DN to use for reading user information. Needs access to homeDirectory,
+ uidNumber, gidNumber, and uid, but not password attributes.
+ '';
+ };
+
+ ldap-reader-passwd = mkOption {
+ type = types.str;
+ description = ''
+ Password for the user specified in ldap-reader-dn.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.prometheus.exporters.dovecot = mkIf cfg.monitoring {
+ enable = true;
+ scopes = ["user" "global"];
+ listenAddress = "127.0.0.1";
+ port = 9166;
+ socketPath = "/var/run/dovecot2/old-stats";
+ };
+
+ services.dovecot2 = {
+ enable = true;
+ enableImap = true;
+ enableLmtp = true;
+ enablePop3 = true;
+ enablePAM = false;
+
+
+ createMailUser = true;
+
+ mailUser = cfg.mail-user;
+ mailGroup = cfg.mail-group;
+ mailLocation = "maildir:${cfg.mail-directory}/%u/";
+
+ sslServerCert = cfg.dovecot.ssl-certificate;
+ sslServerKey = cfg.dovecot.ssl-private-key;
+
+ modules = [ pkgs.dovecot_pigeonhole ];
+ protocols = [ "sieve" ];
+
+ sieveScripts = {
+ after = builtins.toFile "spam.sieve" ''
+ require "fileinto";
+
+ if header :is "X-Spam" "Yes" {
+ fileinto "Junk";
+ stop;
+ }
+ '';
+ };
+
+ mailboxes = cfg.mailboxes;
+
+ extraConfig = ''
+ #Extra Config
+
+ # The prometheus exporter still expects an older style of metrics
+ mail_plugins = $mail_plugins old_stats
+ service old-stats {
+ unix_listener old-stats {
+ user = dovecot-exporter
+ group = dovecot-exporter
+ }
+ }
+
+ ${lib.optionalString cfg.debug ''
+ mail_debug = yes
+ auth_debug = yes
+ verbose_ssl = yes
+ ''}
+
+ protocol imap {
+ mail_max_userip_connections = ${toString cfg.max-user-connections}
+ mail_plugins = $mail_plugins imap_sieve
+ }
+
+ protocol pop3 {
+ mail_max_userip_connections = ${toString cfg.max-user-connections}
+ }
+
+ protocol lmtp {
+ mail_plugins = $mail_plugins sieve
+ }
+
+ mail_access_groups = ${cfg.mail-group}
+ ssl = required
+
+ # When looking up usernames, just use the name, not the full address
+ auth_username_format = %n
+
+ service lmtp {
+ # Enable logging in debug mode
+ ${optionalString cfg.debug "executable = lmtp -L"}
+
+ # Unix socket for postfix to deliver messages via lmtp
+ unix_listener dovecot-lmtp {
+ user = "postfix"
+ group = ${cfg.mail-group}
+ mode = 0600
+ }
+
+ # Drop privs, since all mail is owned by one user
+ user = ${cfg.mail-user}
+ group = ${cfg.mail-group}
+ }
+
+ auth_mechanisms = login plain
+ passdb {
+ driver = ldap
+ args = ${ldap-conf "ldap-passdb.conf" cfg.dovecot.ldap-urls}
+ }
+ userdb {
+ driver = static
+ args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u
+ }
+
+ # Used by postfix to authorize users
+ service auth {
+ unix_listener auth {
+ mode = 0660
+ user = "${config.services.postfix.user}"
+ group = ${config.services.postfix.group}
+ }
+ }
+
+ namespace inbox {
+ separator = "/"
+ inbox = yes
+ }
+
+ plugin {
+ sieve_plugins = sieve_imapsieve sieve_extprograms
+ sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve
+ sieve_default = file:/var/sieve/%u/default.sieve
+ sieve_default_name = default
+ # From elsewhere to Spam folder
+ imapsieve_mailbox1_name = Junk
+ imapsieve_mailbox1_causes = COPY
+ imapsieve_mailbox1_before = file:${state-directory}/imap_sieve/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
+ sieve_pipe_bin_dir = ${pipe-bin}/pipe/bin
+ sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
+ }
+
+ recipient_delimiter = +
+
+ lmtp_save_to_detail_mailbox = yes
+
+ lda_mailbox_autosubscribe = yes
+ lda_mailbox_autocreate = yes
+ '';
+ };
+
+ 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'
+
+ chown ${cfg.mail-user}:${cfg.mail-group} ${cfg.mail-directory}
+ '';
+ };
+}
diff --git a/config/fudo/mail/dovecot/imap_sieve/report-ham.sieve b/config/fudo/mail/dovecot/imap_sieve/report-ham.sieve
new file mode 100644
index 0000000..a9d30cf
--- /dev/null
+++ b/config/fudo/mail/dovecot/imap_sieve/report-ham.sieve
@@ -0,0 +1,15 @@
+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.user" "*" {
+ set "username" "${1}";
+}
+
+pipe :copy "sa-learn-ham.sh" [ "${username}" ];
diff --git a/config/fudo/mail/dovecot/imap_sieve/report-spam.sieve b/config/fudo/mail/dovecot/imap_sieve/report-spam.sieve
new file mode 100644
index 0000000..4024b7a
--- /dev/null
+++ b/config/fudo/mail/dovecot/imap_sieve/report-spam.sieve
@@ -0,0 +1,7 @@
+require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
+
+if environment :matches "imap.user" "*" {
+ set "username" "${1}";
+}
+
+pipe :copy "sa-learn-spam.sh" [ "${username}" ];
\ No newline at end of file
diff --git a/config/fudo/mail/dovecot/pipe_bin/sa-learn-ham.sh b/config/fudo/mail/dovecot/pipe_bin/sa-learn-ham.sh
new file mode 100755
index 0000000..76fc4ed
--- /dev/null
+++ b/config/fudo/mail/dovecot/pipe_bin/sa-learn-ham.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -o errexit
+exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
\ No newline at end of file
diff --git a/config/fudo/mail/dovecot/pipe_bin/sa-learn-spam.sh b/config/fudo/mail/dovecot/pipe_bin/sa-learn-spam.sh
new file mode 100755
index 0000000..2a2f766
--- /dev/null
+++ b/config/fudo/mail/dovecot/pipe_bin/sa-learn-spam.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -o errexit
+exec rspamc -h /run/rspamd/worker-controller.sock learn_spam
\ No newline at end of file
diff --git a/config/fudo/mail/postfix.nix b/config/fudo/mail/postfix.nix
new file mode 100644
index 0000000..927f8c5
--- /dev/null
+++ b/config/fudo/mail/postfix.nix
@@ -0,0 +1,297 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ inherit (lib.strings) concatStringsSep;
+
+ cfg = config.fudo.mail-server;
+
+ make-user-aliases = entries:
+ concatStringsSep "\n"
+ (mapAttrsToList (user: aliases:
+ concatStringsSep "\n"
+ (map (alias: "${alias} ${user}") aliases))
+ entries);
+
+ make-alias-users = domains: entries:
+ concatStringsSep "\n"
+ (flatten
+ (mapAttrsToList (alias: users:
+ (map (domain:
+ "${alias}@${domain} ${concatStringsSep "," users}")
+ domains))
+ entries));
+
+ policyd-spf = pkgs.writeText "policyd-spf.conf" (
+ cfg.postfix.policy-spf-extra-config
+ + (lib.optionalString cfg.debug ''
+ debugLevel = 4
+ ''));
+
+ submission-header-cleanup-rules = pkgs.writeText "submission_header_cleanup_rules" (''
+ # Removes sensitive headers from mails handed in via the submission port.
+ # See https://thomas-leister.de/mailserver-debian-stretch/
+ # Uses "pcre" style regex.
+
+ /^Received:/ IGNORE
+ /^X-Originating-IP:/ IGNORE
+ /^X-Mailer:/ IGNORE
+ /^User-Agent:/ IGNORE
+ /^X-Enigmail:/ IGNORE
+ '');
+
+ blacklist-postfix-entry = sender: "${sender} REJECT";
+ blacklist-postfix-file = entries:
+ concatStringsSep "\n" (map blacklist-postfix-entry entries);
+ sender-blacklist-file = builtins.toFile "reject_senders"
+ (blacklist-postfix-file cfg.sender-blacklist);
+ recipient-blacklist-file = builtins.toFile "reject_recipients"
+ (blacklist-postfix-file cfg.recipient-blacklist);
+
+ # A list of domains for which we accept mail
+ virtual-mailbox-map-file = builtins.toFile "virtual_mailbox_map"
+ (concatStringsSep "\n"
+ (map (domain: "@${domain} OK") cfg.local-domains));
+
+ sender-login-map-file = let
+ escapeDot = (str: replaceStrings ["."] ["\\."] str);
+ in builtins.toFile "sender_login_maps"
+ (concatStringsSep "\n"
+ (map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") cfg.local-domains));
+
+ mapped-file = name: "hash:/var/lib/postfix/conf/${name}";
+
+ pcre-file = name: "pcre:/var/lib/postfix/conf/${name}";
+
+in {
+
+ options.fudo.mail-server.postfix = {
+
+ ssl-private-key = mkOption {
+ type = types.str;
+ description = "Location of the server SSL private key.";
+ };
+
+ ssl-certificate = mkOption {
+ type = types.str;
+ description = "Location of the server SSL certificate.";
+ };
+
+ policy-spf-extra-config = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
+ '';
+ description = ''
+ Extra configuration options for policyd-spf. This can be use to among
+ other things skip spf checking for some IP addresses.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.prometheus.exporters.postfix = mkIf cfg.monitoring {
+ enable = true;
+ systemd.enable = true;
+ };
+
+ services.postfix = {
+ enable = true;
+ domain = cfg.domain;
+ origin = cfg.domain;
+ hostname = cfg.hostname;
+ destination = ["localhost" "localhost.localdomain"] ++
+ (map (domain: "localhost.${domain}") cfg.local-domains);
+
+ enableHeaderChecks = true;
+ enableSmtp = true;
+ enableSubmission = true;
+
+ mapFiles."reject_senders" = sender-blacklist-file;
+ mapFiles."reject_recipients" = recipient-blacklist-file;
+ mapFiles."virtual_mailbox_map" = virtual-mailbox-map-file;
+ mapFiles."sender_login_map" = sender-login-map-file;
+
+ # TODO: enable!
+ # headerChecks = [ { action = "REDIRECT spam@example.com"; pattern = "/^X-Spam-Flag:/"; } ];
+ networks = cfg.trusted-networks;
+
+ virtual = ''
+ ${make-user-aliases cfg.user-aliases}
+
+ ${make-alias-users cfg.local-domains cfg.alias-users}
+ '';
+
+ sslCert = cfg.postfix.ssl-certificate;
+ sslKey = cfg.postfix.ssl-private-key;
+
+ config = {
+ virtual_mailbox_domains = builtins.toFile "domain-list" (concatStringsSep "\n" cfg.local-domains);
+ # virtual_mailbox_base = "${cfg.mail-directory}/";
+ virtual_mailbox_maps = mapped-file "virtual_mailbox_map";
+
+ virtual_uid_maps = "static:${toString cfg.mail-user-id}";
+ virtual_gid_maps = "static:${toString config.users.groups."${cfg.mail-group}".gid}";
+
+ virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
+
+ # NOTE: it's important that this ends with /, to indicate Maildir format!
+ # mail_spool_directory = "${cfg.mail-directory}/";
+ message_size_limit = toString(cfg.message-size-limit * 1024 * 1024);
+
+ smtpd_banner = "${cfg.hostname} ESMTP NO UCE";
+
+ tls_eecdh_strong_curve = "prime256v1";
+ tls_eecdh_ultra_curve = "secp384r1";
+
+ policy-spf_time_limit = "3600s";
+
+ smtp_host_lookup = "dns, native";
+
+ smtpd_sasl_type = "dovecot";
+ smtpd_sasl_path = "/run/dovecot2/auth";
+ smtpd_sasl_auth_enable = "yes";
+
+ smtpd_sender_login_maps = (pcre-file "sender_login_map");
+
+ disable_vrfy_command = "yes";
+
+ recipient_delimiter = "+";
+
+ milter_protocol = "6";
+ milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
+
+ smtpd_milters = [
+ "unix:/run/rspamd/rspamd-milter.sock"
+ "unix:/var/run/opendkim/opendkim.sock"
+ ];
+
+ non_smtpd_milters = [
+ "unix:/run/rspamd/rspamd-milter.sock"
+ "unix:/var/run/opendkim/opendkim.sock"
+ ];
+
+ smtpd_relay_restrictions = [
+ "permit_mynetworks"
+ "permit_sasl_authenticated"
+ "reject_unauth_destination"
+ "reject_unauth_pipelining"
+ "reject_unauth_destination"
+ "reject_unknown_sender_domain"
+ ];
+
+ smtpd_sender_restrictions = [
+ "check_sender_access ${mapped-file "reject_senders"}"
+ ];
+
+ smtpd_recipient_restrictions = [
+ "check_sender_access ${mapped-file "reject_recipients"}"
+ "permit_mynetworks"
+ "permit_sasl_authenticated"
+ "check_policy_service unix:private/policy-spf"
+ "reject_unknown_recipient_domain"
+ "reject_unauth_pipelining"
+ "reject_unauth_destination"
+ "reject_invalid_hostname"
+ "reject_non_fqdn_hostname"
+ "reject_non_fqdn_sender"
+ "reject_non_fqdn_recipient"
+ ];
+
+ # Handled by submission
+ smtpd_tls_security_level = "may";
+
+ smtpd_tls_eecdh_grade = "ultra";
+
+ # Disable obselete protocols
+ smtpd_tls_protocols = [
+ "TLSv1.2"
+ "TLSv1.1"
+ "!TLSv1"
+ "!SSLv2"
+ "!SSLv3"
+ ];
+ smtp_tls_protocols = [
+ "TLSv1.2"
+ "TLSv1.1"
+ "!TLSv1"
+ "!SSLv2"
+ "!SSLv3"
+ ];
+ smtpd_tls_mandatory_protocols = [
+ "TLSv1.2"
+ "TLSv1.1"
+ "!TLSv1"
+ "!SSLv2"
+ "!SSLv3"
+ ];
+ smtp_tls_mandatory_protocols = [
+ "TLSv1.2"
+ "TLSv1.1"
+ "!TLSv1"
+ "!SSLv2"
+ "!SSLv3"
+ ];
+
+ smtp_tls_ciphers = "high";
+ smtpd_tls_ciphers = "high";
+ smtp_tls_mandatory_ciphers = "high";
+ smtpd_tls_mandatory_ciphers = "high";
+
+ smtpd_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
+ smtpd_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
+ smtp_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
+ smtp_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
+
+ tls_preempt_cipherlist = "yes";
+
+ smtpd_tls_auth_only = "yes";
+
+ smtpd_tls_loglevel = "1";
+
+ tls_random_source = "dev:/dev/urandom";
+ };
+
+ submissionOptions = {
+ smtpd_tls_security_level = "encrypt";
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_sasl_type = "dovecot";
+ smtpd_sasl_path = "/run/dovecot2/auth";
+ smtpd_sasl_security_options = "noanonymous";
+ smtpd_sasl_local_domain = cfg.domain;
+ smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+ smtpd_sender_restrictions = "reject_sender_login_mismatch,reject_unknown_sender_domain";
+ smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
+ cleanup_service_name = "submission-header-cleanup";
+ };
+
+ masterConfig = {
+ "policy-spf" = {
+ type = "unix";
+ privileged = true;
+ chroot = false;
+ command = "spawn";
+ args = [ "user=nobody" "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" "${policyd-spf}"];
+ };
+ "submission-header-cleanup" = {
+ type = "unix";
+ private = false;
+ chroot = false;
+ maxproc = 0;
+ command = "cleanup";
+ args = ["-o" "header_checks=pcre:${submission-header-cleanup-rules}"];
+ };
+ };
+ };
+
+ # Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
+ systemd.services.postfix = {
+ after = [ "dovecot2.service" ]
+ ++ (lib.optional cfg.dkim.signing "opendkim.service");
+ requires = [ "dovecot2.service" ]
+ ++ (lib.optional cfg.dkim.signing "opendkim.service");
+ };
+ };
+}
diff --git a/config/fudo/mail/rspamd.nix b/config/fudo/mail/rspamd.nix
new file mode 100644
index 0000000..4bc9324
--- /dev/null
+++ b/config/fudo/mail/rspamd.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.fudo.mail-server;
+
+in {
+ config = mkIf cfg.enable {
+ services.prometheus.exporters.rspamd.enable = true;
+
+ services.rspamd = {
+
+ enable = true;
+
+ locals = {
+ "milter_headers.conf" = {
+ text = ''
+ extended_spam_headers = yes;
+ '';
+ };
+
+ "antivirus.conf" = {
+ text = ''
+ clamav {
+ action = "reject";
+ symbol = "CLAM_VIRUS";
+ type = "clamav";
+ log_clean = true;
+ servers = "/run/clamav/clamd.ctl";
+ scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
+ }
+ '';
+ };
+ };
+
+ overrides = {
+ "milter_headers.conf" = {
+ text = ''
+ extended_spam_headers = true;
+ '';
+ };
+ };
+
+ workers.rspamd_proxy = {
+ type = "rspamd_proxy";
+ bindSockets = [{
+ socket = "/run/rspamd/rspamd-milter.sock";
+ mode = "0664";
+ }];
+ count = 1; # Do not spawn too many processes of this type
+ extraConfig = ''
+ milter = yes; # Enable milter mode
+ timeout = 120s; # Needed for Milter usually
+
+ upstream "local" {
+ default = yes; # Self-scan upstreams are always default
+ self_scan = yes; # Enable self-scan
+ }
+ '';
+ };
+
+ workers.controller = {
+ type = "controller";
+ count = 1;
+ bindSockets = [
+ "localhost:11334"
+ {
+ socket = "/run/rspamd/worker-controller.sock";
+ mode = "0666";
+ }
+ ];
+ includes = [];
+ };
+ };
+
+ systemd.services.rspamd = {
+ requires = (optional cfg.clamav.enable "clamav-daemon.service");
+ after = (optional cfg.clamav.enable "clamav-daemon.service");
+ };
+
+ systemd.services.postfix = {
+ after = [ "rspamd.service" ];
+ requires = [ "rspamd.service" ];
+ };
+
+ users.extraUsers.${config.services.postfix.user}.extraGroups = [ config.services.rspamd.group ];
+ };
+}
diff --git a/config/fudo/minecraft-server.nix b/config/fudo/minecraft-server.nix
new file mode 100644
index 0000000..06f9c67
--- /dev/null
+++ b/config/fudo/minecraft-server.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.fudo.minecraft-server;
+
+in {
+ options.fudo.minecraft-server = {
+ enable = mkEnableOption "Start a minecraft server.";
+
+ package = mkOption {
+ type = types.package;
+ description = "Minecraft package to use.";
+ default = pkgs.minecraft-server_1_15_1;
+ };
+
+ data-dir = mkOption {
+ type = types.path;
+ description = "Path at which to store minecraft data.";
+ };
+
+ world-name = mkOption {
+ type = types.str;
+ description = "Name of the server world (used in saves etc).";
+ };
+
+ motd = mkOption {
+ type = types.str;
+ description = "Welcome message for newcomers.";
+ };
+
+ game-mode = mkOption {
+ type = types.enum ["survival" "creative" "adventure" "spectator"];
+ description = "Game mode of the server.";
+ default = "survival";
+ };
+
+ difficulty = mkOption {
+ type = types.int;
+ description = "Difficulty level, where 0 is peaceful and 3 is hard.";
+ default = 2;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.minecraft-server = {
+ enable = true;
+ package = cfg.package;
+ dataDir = cfg.data-dir;
+ eula = true;
+ declarative = true;
+ serverProperties = {
+ level-name = cfg.world-name;
+ motd = cfg.motd;
+ difficulty = cfg.difficulty;
+ gamemode = cfg.game-mode;
+ };
+ };
+ };
+}
diff --git a/config/fudo/node-exporter.nix b/config/fudo/node-exporter.nix
new file mode 100644
index 0000000..82236cb
--- /dev/null
+++ b/config/fudo/node-exporter.nix
@@ -0,0 +1,58 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+let
+ inherit (lib.strings) concatStringsSep;
+
+ cfg = config.fudo.node-exporter;
+ fudo-cfg = config.fudo.common;
+
+ allow-network = network: "allow ${network};";
+
+in {
+ options.fudo.node-exporter = {
+ enable = mkEnableOption "Enable a Prometheus node exporter with some reasonable settings.";
+
+ hostname = mkOption {
+ type = types.str;
+ description = "Hostname from which to export statistics.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services = {
+ # This'll run an exporter at localhost:9100
+ prometheus.exporters.node = {
+ enable = true;
+ enabledCollectors = [ "systemd" ];
+ listenAddress = "127.0.0.1";
+ port = 9100;
+ user = "node";
+ };
+
+ # ...And this'll expose the above to the outside world, or at least the
+ # list of trusted networks, with SSL protection.
+ nginx = {
+ enable = true;
+
+ virtualHosts."${cfg.hostname}" = {
+ enableACME = true;
+ forceSSL = true;
+
+ location."/metrics/node" = {
+ extraConfig = ''
+ ${concatStringsSep "\n" (map allow-network fudo-cfg.local-networks)}
+ allow 127.0.0.0/16;
+ deny all;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ '';
+
+ proxyPass = "http://127.0.0.1:9100/metrics";
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/config/fudo/postgres.nix b/config/fudo/postgres.nix
new file mode 100644
index 0000000..d24cc80
--- /dev/null
+++ b/config/fudo/postgres.nix
@@ -0,0 +1,197 @@
+{ config, lib, pkgs, environment, ... }:
+
+with lib;
+let
+
+ cfg = config.fudo.postgresql;
+
+ userOpts = { username, ... }: {
+ options = {
+ password = mkOption {
+ type = types.str;
+ description = "The user's (plaintext) password.";
+ };
+
+ databases = mkOption {
+ type = with types; listOf str;
+ description = "Databases to which this user has access.";
+ default = [];
+ };
+ };
+ };
+
+ databaseOpts = { dbname, ... }: {
+ options = {
+ users = mkOption {
+ type = with types; listOf str;
+ description = "A list of users who should have access to this database.";
+ default = [];
+ };
+ };
+ };
+
+ userDatabaseAccess = user: databases:
+ listToAttrs (map (database-name:
+ {
+ name = "DATABASE ${database-name}";
+ value = "ALL PRIVILEGES";
+ })
+ databases);
+
+ stringJoin = joiner: els:
+ if (length els) == 0 then
+ ""
+ else
+ foldr(lel: rel: "${lel}${joiner}${rel}") (last els) (init els);
+
+ makeEntry = nw:
+ "host all all ${nw} gss include_realm=0 krb_realm=FUDO.ORG";
+
+ makeNetworksEntry = networks:
+ stringJoin "\n" (map makeEntry networks);
+
+ setPasswordSql = username: attrs:
+ "ALTER USER ${username} ENCRYPTED PASSWORD '${attrs.password}';";
+
+ setPasswordsSql = users:
+ stringJoin "\n"
+ (mapAttrsToList (username: attrs: setPasswordSql username attrs)
+ users);
+
+ makeLocalUserPasswordEntries = users:
+ stringJoin "\n"
+ (mapAttrsToList
+ (username: attrs:
+ stringJoin "\n"
+ (map (db: ''
+ host ${username} ${db} 127.0.0.1/16 md5
+ host ${username} ${db} ::1/128 md5
+ '') attrs.databases))
+ users);
+
+
+in {
+
+ options.fudo.postgresql = {
+ enable = mkEnableOption "Fudo PostgreSQL Server";
+
+ ssl-private-key = mkOption {
+ type = types.str;
+ description = "Location of the server SSL private key.";
+ };
+
+ ssl-certificate = mkOption {
+ type = types.str;
+ description = "Location of the server SSL certificate.";
+ };
+
+ keytab = mkOption {
+ type = types.str;
+ description = "Location of the server Kerberos keytab.";
+ };
+
+ local-networks = mkOption {
+ type = with types; listOf str;
+ description = "A list of networks from which to accept connections.";
+ example = [
+ "10.0.0.1/16"
+ ];
+ default = [];
+ };
+
+ local-users = mkOption {
+ type = with types; loaOf (submodule userOpts);
+ description = "A map of users to user attributes.";
+ example = {
+ sampleUser = {
+ password = "some-password";
+ databases = [ "sample_user_db" ];
+ };
+ };
+ default = {};
+ };
+
+ databases = mkOption {
+ type = with types; loaOf (submodule databaseOpts);
+ description = "A map of databases to database options.";
+ default = {};
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ environment = {
+ systemPackages = with pkgs; [
+ postgresql_11_gssapi
+ ];
+
+ etc = {
+ "postgresql/private/privkey.pem" = {
+ mode = "0400";
+ user = "postgres";
+ group = "postgres";
+ source = cfg.ssl-private-key;
+ };
+
+ "postgresql/cert.pem" = {
+ mode = "0444";
+ user = "postgres";
+ group = "postgres";
+ source = cfg.ssl-certificate;
+ };
+
+ "postgresql/private/postgres.keytab" = {
+ mode = "0400";
+ user = "postgres";
+ group = "postgres";
+ source = cfg.keytab;
+ };
+ };
+ };
+
+ services.postgresql = {
+ enable = true;
+ package = pkgs.postgresql_11_gssapi;
+ enableTCPIP = true;
+ ensureDatabases = mapAttrsToList (name: value: name) cfg.databases;
+ ensureUsers = mapAttrsToList
+ (username: attrs:
+ {
+ name = username;
+ ensurePermissions =
+ #{ "DATABASE ${username}" = "ALL PRIVILEGES"; };
+ (userDatabaseAccess username attrs.databases);
+ })
+ cfg.local-users;
+
+ extraConfig =
+ ''
+ krb_server_keyfile = '/etc/postgresql/private/postgres.keytab'
+
+ ssl = true
+ ssl_cert_file = '/etc/postgresql/cert.pem'
+ ssl_key_file = '/etc/postgresql/private/privkey.pem'
+
+ unix_socket_directories = '/var/run/postgresql'
+ '';
+
+ authentication =
+ ''
+ local all all ident
+
+ ${makeLocalUserPasswordEntries cfg.local-users}
+
+ # 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
+
+ # local networks
+ ${makeNetworksEntry cfg.local-networks}
+ '';
+
+ initialScript = pkgs.writeText "database-init.sql" ''
+ ${setPasswordsSql cfg.local-users}
+ '';
+ };
+ };
+}
diff --git a/config/fudo/profiles.nix b/config/fudo/profiles.nix
new file mode 100644
index 0000000..0a1d78d
--- /dev/null
+++ b/config/fudo/profiles.nix
@@ -0,0 +1,32 @@
+# Switch between different basic profiles
+{ lib, config, pkgs, ... }:
+
+with lib;
+let
+ profile = config.fudo.profile;
+
+ profiles = {
+ desktop = ./profiles/desktop.nix {
+ pkgs = pkgs;
+ config = config;
+ };
+ server = ./profiles/server.nix {
+ pkgs = pkgs;
+ config = config;
+ };
+ };
+
+in {
+ options.fudo.profile = {
+ type = types.enum (attrNames profiles);
+ example = "desktop";
+ description = ''
+ The profile to use for this host. This will do some profile-dependent
+ configuration, for example removing X-libs from servers and adding UI
+ packages to desktops.
+ '';
+ default = "server";
+ };
+
+ config = optionalAttrs (profiles ? profile) profiles.${profile};
+}
diff --git a/config/fudo/profiles/desktop.nix b/config/fudo/profiles/desktop.nix
new file mode 100644
index 0000000..1aa0d53
--- /dev/null
+++ b/config/fudo/profiles/desktop.nix
@@ -0,0 +1,151 @@
+{ config, pkgs, ... }:
+
+{
+ environment.systemPackages = with pkgs; [
+ cool-retro-term
+ chrome-gnome-shell
+ chromium
+ ffmpeg-full
+ firefox
+ gimp
+ glxinfo
+ gnome3.gnome-shell
+ gnome3.gnome-session
+ google-chrome
+ gtk2
+ gtk2-x11
+ gtk3
+ gtkimageview
+ i3lock
+ libfixposix
+ minecraft
+ mplayer
+ nomacs
+ openssl_1_1
+ redshift
+ rhythmbox
+ shotwell
+ spotify
+ sqlite
+ steam
+ system-config-printer
+ virtmanager
+ xorg.xev
+ xzgv
+ virtmanager-qt
+ ];
+
+ # Splash screen
+ boot.plymouth.enable = true;
+
+ services.avahi = {
+ enable = true;
+ browseDomains = [config.fudo.domain;];
+ domainName = config.fudo.domain;
+ };
+
+ boot.tmpOnTmpfs = true;
+
+ services.xserver = {
+ enable = true;
+
+ layout = "us";
+ xkbVariant = "dvp";
+ xkbOptions = "ctrl:nocaps";
+
+ desktopManager.gnome3.enable = true;
+ desktopManager.default = "gnome3";
+
+ displayManager.gdm.enable = true;
+
+ windowManager.session = pkgs.lib.singleton {
+ name = "stumpwm";
+ start = ''
+ ${pkgs.lispPackages.stumpwm}/bin/stumpwm &
+ waidPID=$!
+ '';
+ };
+ };
+
+ services.printing = {
+ enable = true;
+ };
+
+ services.gnome3 = {
+ evolution-data-server.enable = pkgs.lib.mkForce false;
+ gnome-user-share.enable = pkgs.lib.mkForce false;
+ };
+
+ services.dbus.socketActivated = true;
+
+ services.openssh.forwardX11 = true;
+
+ programs.ssh.forwardX11 = true;
+
+ sound.enable = true;
+
+ hardware.pulseaudio.enable = true;
+
+ fonts = {
+ enableCoreFonts = true;
+ enableFontDir = true;
+ enableGhostscriptFonts = false;
+ fontconfig.ultimate.enable = true;
+
+ fonts = with pkgs; [
+ cantarell_fonts
+ dejavu_fonts
+ dina-font
+ dosemu_fonts
+ fira-code
+ fira-code-symbols
+ freefont_ttf
+ liberation_ttf
+ mplus-outline-fonts
+ nerdfonts
+ noto-fonts
+ noto-fonts-cjk
+ noto-fonts-emoji
+ proggyfonts
+ terminus_font
+ ubuntu_font_family
+ ucsFonts
+ unifont
+ vistafonts
+ xlibs.fontadobe100dpi
+ xlibs.fontadobe75dpi
+ xlibs.fontadobeutopia100dpi
+ xlibs.fontadobeutopia75dpi
+ xlibs.fontadobeutopiatype1
+ xlibs.fontarabicmisc
+ xlibs.fontbh100dpi
+ xlibs.fontbh75dpi
+ xlibs.fontbhlucidatypewriter100dpi
+ xlibs.fontbhlucidatypewriter75dpi
+ xlibs.fontbhttf
+ xlibs.fontbhtype1
+ xlibs.fontbitstream100dpi
+ xlibs.fontbitstream75dpi
+ xlibs.fontbitstreamtype1
+ xlibs.fontcronyxcyrillic
+ xlibs.fontcursormisc
+ xlibs.fontdaewoomisc
+ xlibs.fontdecmisc
+ xlibs.fontibmtype1
+ xlibs.fontisasmisc
+ xlibs.fontjismisc
+ xlibs.fontmicromisc
+ xlibs.fontmisccyrillic
+ xlibs.fontmiscethiopic
+ xlibs.fontmiscmeltho
+ xlibs.fontmiscmisc
+ xlibs.fontmuttmisc
+ xlibs.fontschumachermisc
+ xlibs.fontscreencyrillic
+ xlibs.fontsonymisc
+ xlibs.fontsunmisc
+ xlibs.fontwinitzkicyrillic
+ xlibs.fontxfree86type1
+ ];
+ };
+}
diff --git a/config/fudo/profiles/server.nix b/config/fudo/profiles/server.nix
new file mode 100644
index 0000000..70c9271
--- /dev/null
+++ b/config/fudo/profiles/server.nix
@@ -0,0 +1,27 @@
+{ pkgs, ... }:
+
+{
+ environment = {
+ systemPackages = with pkgs; [
+ ];
+
+ noXlibs = true;
+ };
+
+ security = {
+ hideProcessInformation = true;
+ };
+
+ boot.tmpOnTmpfs = true;
+
+ services.xserver.enable = false;
+
+ programs = {
+ gnupg.agent = {
+ enable = true;
+ enableSSHSupport = true;
+ };
+
+ ssh.startAgent = true;
+ };
+}
diff --git a/config/fudo/prometheus.nix b/config/fudo/prometheus.nix
new file mode 100644
index 0000000..df8c928
--- /dev/null
+++ b/config/fudo/prometheus.nix
@@ -0,0 +1,204 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ inherit (lib.strings) concatStringsSep;
+ cfg = config.fudo.prometheus;
+ fudo-cfg = config.fudo.common;
+
+in {
+
+ options.fudo.prometheus = {
+ enable = mkEnableOption "Fudo Prometheus Data-Gathering Server";
+
+ service-discovery-dns = mkOption {
+ type = with types; loaOf (listOf str);
+ description = ''
+ A map of exporter type to a list of domains to use for service discovery.
+ '';
+ example = {
+ node = [ "node._metrics._tcp.my-domain.com" ];
+ postfix = [ "postfix._metrics._tcp.my-domain.com" ];
+ };
+ default = {
+ dovecot = [];
+ node = [];
+ postfix = [];
+ rspamd = [];
+ };
+ };
+
+ static-targets = mkOption {
+ type = with types; loaOf (listOf str);
+ description = ''
+ A map of exporter type to a list of host:ports from which to collect metrics.
+ '';
+ example = {
+ node = [ "my-host.my-domain:1111" ];
+ };
+ default = {
+ dovecot = [];
+ node = [];
+ postfix = [];
+ rspamd = [];
+ };
+ };
+
+ docker-hosts = mkOption {
+ type = with types; listOf str;
+ description = ''
+ A list of explicit docker targets from which to gather node data.
+ '';
+ default = [];
+ };
+
+ push-url = mkOption {
+ type = with types; nullOr str;
+ description = ''
+ The that services can use to manually push data.
+ '';
+ default = null;
+ };
+
+ push-address = mkOption {
+ type = with types; nullOr str;
+ description = ''
+ The address on which to listen for incoming data.
+ '';
+ default = null;
+ };
+
+ hostname = mkOption {
+ type = with types; str;
+ description = "The hostname upon which Prometheus will serve.";
+ example = "my-metrics-server.fudo.org";
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.nginx = {
+ enable = true;
+
+ virtualHosts = {
+ "${cfg.hostname}" = {
+ enableACME = true;
+ forceSSL = true;
+
+ locations."/" = {
+ proxyPass = "http://127.0.0.1:9090";
+
+ 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;
+
+ ${optionalString ((length fudo-cfg.local-networks) > 0)
+ (concatStringsSep "\n" (map (network: "allow ${network};") fudo-cfg.local-networks)) + "\ndeny all;"}
+ '';
+ };
+ };
+ };
+ };
+
+ services.prometheus = {
+
+ enable = true;
+
+ webExternalUrl = "https://${cfg.hostname}";
+
+ scrapeConfigs = [
+ {
+ job_name = "docker";
+ honor_labels = false;
+ static_configs = [
+ {
+ targets = cfg.docker-hosts;
+ }
+ ];
+ }
+
+ {
+ job_name = "node";
+ scheme = "https";
+ metrics_path = "/metrics/node";
+ honor_labels = false;
+ dns_sd_configs = [
+ {
+ names = cfg.service-discovery-dns.node;
+ }
+ ];
+ static_configs = [
+ {
+ targets = cfg.static-targets.node;
+ }
+ ];
+ }
+
+ {
+ job_name = "dovecot";
+ scheme = "https";
+ metrics_path = "/metrics/dovecot";
+ honor_labels = false;
+ dns_sd_configs = [
+ {
+ names = cfg.service-discovery-dns.dovecot;
+ }
+ ];
+ static_configs = [
+ {
+ targets = cfg.static-targets.dovecot;
+ }
+ ];
+ }
+
+ {
+ job_name = "postfix";
+ scheme = "https";
+ metrics_path = "/metrics/postfix";
+ honor_labels = false;
+ dns_sd_configs = [
+ {
+ names = cfg.service-discovery-dns.postfix;
+ }
+ ];
+ static_configs = [
+ {
+ targets = cfg.static-targets.postfix;
+ }
+ ];
+ }
+
+ {
+ job_name = "rspamd";
+ scheme = "https";
+ metrics_path = "/metrics/rspamd";
+ honor_labels = false;
+ dns_sd_configs = [
+ {
+ names = cfg.service-discovery-dns.rspamd;
+ }
+ ];
+ static_configs = [
+ {
+ targets = cfg.static-targets.rspamd;
+ }
+ ];
+ }
+ ];
+
+ pushgateway = {
+ enable = if (cfg.push-url != null) then true else false;
+ web = {
+ external-url = if cfg.push-url == null then
+ cfg.push-address
+ else
+ cfg.push-url;
+ listen-address = cfg.push-address;
+ };
+ };
+ };
+ };
+}
diff --git a/config/fudo/sites.nix b/config/fudo/sites.nix
new file mode 100644
index 0000000..d7e242e
--- /dev/null
+++ b/config/fudo/sites.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ site = config.fudo.site;
+
+ hostname = config.networking.hostName;
+
+ winnipeg-networks = [
+ "208.81.1.128/28"
+ "208.81.3.112/28"
+ "192.168.11.1/24"
+ ];
+
+ site-configs = {
+ global-config = {
+ };
+
+ winnipeg = global-config // {
+ time.timeZone = "America/Winnipeg";
+
+ fudo.common.local-networks = winnipeg-networks;
+
+ services.cron = {
+ mailto = "admin@fudo.org";
+ };
+
+ networking = {
+ domain = "fudo.org";
+ search = ["fudo.org"];
+ firewall.enable = false;
+ networkmanager.enable = pkgs.lib.mkForce false;
+ nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ];
+ };
+
+ security.acme.certs."${hostname}" = {
+ email = "admin@fudo.org";
+
+ plugins = [
+ "fullchain.pem"
+ "full.pem"
+ "key.pem"
+ "chain.pem"
+ "cert.pem"
+ ];
+ };
+
+ fudo.node-exporter = {
+ enable = true;
+ hostname = hostname;
+ };
+
+ nginx = {
+ enable = true;
+
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedTlsSettings = true;
+ };
+ };
+
+ nutty-club = winnipeg // {
+ defaultGateway = "208.81.3.113";
+ };
+ };
+
+in {
+ options.fudo.site = mkOption {
+ type = types.enum (attrNames site-configs);
+ example = "nutty-club";
+ description = ''
+ The site at which this host is located. This will do some site-dependent
+ configuration.
+ '';
+ default = "";
+ };
+
+ config = optionalAttrs (site-configs ? site) site-configs.${site};
+}
diff --git a/config/local.nix b/config/local.nix
new file mode 100644
index 0000000..14a1f85
--- /dev/null
+++ b/config/local.nix
@@ -0,0 +1,21 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+{
+ imports = [
+ ./fudo/acme-for-hostname.nix
+ ./fudo/authentication.nix
+ ./fudo/common.nix
+ ./fudo/grafana.nix
+ ./fudo/kdc.nix
+ ./fudo/ldap.nix
+ ./fudo/mail.nix
+ ./fudo/mail-container.nix
+ ./fudo/minecraft-server.nix
+ ./fudo/node-exporter.nix
+ ./fudo/postgres.nix
+ ./fudo/profiles.nix
+ ./fudo/prometheus.nix
+ ./fudo/sites.nix
+ ];
+}
diff --git a/config/postgresql_11.nix b/config/postgresql_11.nix
index ff75b52..e2238cb 100644
--- a/config/postgresql_11.nix
+++ b/config/postgresql_11.nix
@@ -72,67 +72,4 @@ in {
};
};
};
-
- # config = mkIf config.fudo.postgresql.enable
-
- # environment = {
-
- # systemPackages = with pkgs; [
- # postgresql_11_gssapi
- # ];
-
- # etc = {
- # "postgresql/private/privkey.pem" = {
- # mode = "0400";
- # user = "postgres";
- # group = "postgres";
- # source = dataPath + "/certs/private/privkey.pem";
- # };
-
- # "postgresql/cert.pem" = {
- # mode = "0444";
- # user = "postgres";
- # group = "postgres";
- # source = dataPath + "/certs/cert.pem";
- # };
-
- # "postgresql/private/postgres.keytab" = {
- # mode = "0400";
- # user = "postgres";
- # group = "postgres";
- # source = dataPath + "/keytabs/postgres.keytab";
- # };
- # };
- # };
-
- # services.postgresql = {
- # enable = true;
- # package = pkgs.postgresql_11_gssapi;
- # enableTCPIP = true;
-
- # extraConfig = ''
- # krb_server_keyfile = '/etc/postgresql/private/postgres.keytab'
-
- # ssl = true
- # ssl_cert_file = '/etc/postgresql/cert.pem'
- # ssl_key_file = '/etc/postgresql/private/privkey.pem'
- # '';
-
- # authentication = ''
- # 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
-
- # # local network
- # host all all 10.0.0.1/24 gss include_realm=0 krb_realm=FUDO.ORG
- # host all all 2601:600:997f:fc00::/60 gss include_realm=0 krb_realm=FUDO.ORG
- # '';
-
- # initialScript = pkgs.writeText "backend-initscript" ''
- # ${catLines (map createUserSql fudo.postgresql.users)}
- # ${catLines (map createDatabaseSql fudo.postgresql.databases)}
- # '';
- # };
}
diff --git a/defaults.nix b/defaults.nix
index 772758e..121c83b 100644
--- a/defaults.nix
+++ b/defaults.nix
@@ -5,10 +5,8 @@
{
imports = [
./hardware-configuration.nix
- ./packages/postgresql_11_gssapi.nix
- ./packages/minecraft-server_1_15_1.nix
- ./config/fudo.nix
- ./config/postgresql_11.nix
+ ./packages/local.nix
+ ./config/local.nix
];
nixpkgs.config.allowUnfree = true;
@@ -97,7 +95,6 @@
mosh.enable = true;
ssh = {
- forwardX11 = true;
extraConfig = ''
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
@@ -126,7 +123,6 @@
openssh = {
enable = true;
startWhenNeeded = true;
- forwardX11 = true;
extraConfig = ''
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
@@ -146,31 +142,16 @@
};
};
- users.groups = {
- fudosys = {
- gid = 888;
+ users.extraUsers = {
+ node = {
+ isSystemUser = true;
+ group = "nogroup";
};
};
- users.ldap = {
- enable = true;
- base = "dc=fudo,dc=org";
- bind.distinguishedName = "cn=auth_reader,dc=fudo,dc=org";
- bind.passwordFile = "/srv/nslcd/bind.passwd";
- bind.timeLimit = 5;
- loginPam = false;
- server = "ldap://france.fudo.org";
- timeLimit = 5;
- useTLS = true;
- extraConfig = ''
- TLS_CACERT /etc/nixos/static/fudo_ca.pem
- '';
-
- daemon = {
- enable = true;
- extraConfig = ''
- tls_cacertfile /etc/nixos/static/fudo_ca.pem
- '';
+ users.groups = {
+ fudosys = {
+ gid = 888;
};
};
diff --git a/fudo/alias-users.nix b/fudo/alias-users.nix
new file mode 100644
index 0000000..a0bd333
--- /dev/null
+++ b/fudo/alias-users.nix
@@ -0,0 +1,18 @@
+# A map of email aliases to a list of users (useful for system and bulk aliases)
+
+let
+ admin-users = ["reaper@fudo.org" "niten@fudo.org"];
+in {
+ root = admin-users;
+ postmaster = admin-users;
+
+ www-data = admin-users;
+ hostmaster = admin-users;
+ webmaster = admin-users;
+ ftp = admin-users;
+ irc = admin-users;
+ admin = admin-users;
+ system = admin-users;
+
+ asdf = ["mswaffer@gmail.com" "bouncetest@fudo.org"];
+}
diff --git a/fudo/email.nix b/fudo/email.nix
new file mode 100644
index 0000000..bea47a5
--- /dev/null
+++ b/fudo/email.nix
@@ -0,0 +1,39 @@
+# Fudo email settings
+{ config }:
+
+let
+ mail-hostname = "france.fudo.org";
+
+in {
+
+ domain = "fudo.org";
+
+ local-domains = [
+ "fudo.org"
+ "mail.fudo.org"
+ "${config.networking.hostName}"
+ "selby.ca"
+ "mail.selby.ca"
+ "fudo.im"
+ "mail.fudo.im"
+ "fudo.ca"
+ "mail.fudo.ca"
+ "fudo.link"
+ "mail.fudo.link"
+ "selbyhomecentre.com"
+ "stewartsoundservices.ca"
+ "rogerwongphoto.com"
+ ];
+
+ alias-users = import ./alias-users.nix;
+ user-aliases = import ./user-aliases.nix;
+ sender-blacklist = import ./sender-blacklist.nix;
+ recipient-blacklist = import ./recipient-blacklist.nix;
+
+ trusted-networks = [
+ "208.81.1.128/28"
+ "208.81.3.112/28"
+ "192.168.11.0/24"
+ "127.0.0.0/8"
+ ];
+}
diff --git a/fudo/recipient-blacklist.nix b/fudo/recipient-blacklist.nix
new file mode 100644
index 0000000..002dda1
--- /dev/null
+++ b/fudo/recipient-blacklist.nix
@@ -0,0 +1,3 @@
+# Emails for which we won't accept any email.
+
+[]
diff --git a/fudo/sender-blacklist.nix b/fudo/sender-blacklist.nix
new file mode 100644
index 0000000..8ca71e0
--- /dev/null
+++ b/fudo/sender-blacklist.nix
@@ -0,0 +1,7 @@
+# We won't forward email from these addresses, because they were used for
+# spamming. Learn2passward!
+
+[
+ "ark@fudo.org"
+ "theblacksun@fudo.org"
+]
diff --git a/fudo/system-users.nix b/fudo/system-users.nix
index ec57dc4..21830ef 100644
--- a/fudo/system-users.nix
+++ b/fudo/system-users.nix
@@ -8,4 +8,9 @@
description = "System Authenticator";
hashed-password = "{MD5}N36/kQ64mev1HARddvVk7Q==";
};
+
+ user_db_reader = {
+ description = "User Database Reader";
+ hashed-password = "{SSHA}IVKhrB+wMOCI/CCzbJW8sNDbH67ZTMBv";
+ };
}
diff --git a/fudo/user-aliases.nix b/fudo/user-aliases.nix
new file mode 100644
index 0000000..05cfb9e
--- /dev/null
+++ b/fudo/user-aliases.nix
@@ -0,0 +1,36 @@
+# A map of user to a list of email aliases (better for users with multiple nicknames)
+
+{
+ "niten@fudo.link" = [
+ "ertian@fudo.org"
+ "peter@fudo.org"
+ "peter@fudo.link"
+ "pselby@fudo.org"
+ "yiliu@fudo.org"
+
+ "peter@selby.ca"
+ ];
+
+ "xiaoxuan@fudo.org" = [
+ "xixi@fudo.org"
+ "claire@fudo.org"
+
+ "xixi@selby.ca"
+ "claire@selby.ca"
+ ];
+
+ "reaper@fudo.org" = [
+ "cricket@fudo.org"
+ "jstewart@fudo.org"
+ "jonathan@fudo.org"
+ "reaper@fudo.link"
+ ];
+
+ "swaff@fudo.org" = [
+ "mark@fudo.org"
+ ];
+
+ "ken@selby.ca" = [
+ "kselby@selby.ca"
+ ];
+}
diff --git a/fudo/users.nix b/fudo/users.nix
index 75b7e76..9089b58 100644
--- a/fudo/users.nix
+++ b/fudo/users.nix
@@ -393,6 +393,14 @@
hashed-password = "{SSHA}DKnhrycmXSu4HKWFPeBXA9xvZ0ytgXIpZA10tg==";
};
+ # Used to send alerts from grafana
+ metrics = {
+ uid = 10109;
+ group = "fudo";
+ common-name = "Fudo Metrics";
+ hashed-password = "{SSHA}FveEVy6kljQZey0xp0nF62SMlO5nATJ1";
+ };
+
testuser = {
uid = 10110;
group = "fudo";
diff --git a/hosts/france.nix b/hosts/france.nix
index b0e770b..b9657d7 100644
--- a/hosts/france.nix
+++ b/hosts/france.nix
@@ -1,59 +1,194 @@
-{ config, pkgs, ... }:
+{ config, pkgs, lib, ... }:
+with lib;
let
hostname = "france.fudo.org";
+ mail-hostname = "france.fudo.org";
+ host_ipv4 = "208.81.3.117";
+ all-hostnames = [];
+
+ acme-private-key = hostname: "/var/lib/acme/${hostname}/key.pem";
+ acme-certificate = hostname: "/var/lib/acme/${hostname}/fullchain.pem";
+ acme-ca = "/etc/nixos/static/letsencryptauthorityx3.pem";
+
+ fudo-ca = "/etc/nixos/static/fudo_ca.pem";
+
+ minecraft-data-dir = "/srv/minecraft/data";
+
+ system-mail-directory = "/srv/mail";
in {
- boot.loader.grub.enable = true;
- boot.loader.grub.version = 2;
- boot.loader.grub.device = "/dev/sda";
-
- security.hideProcessInformation = true;
+ boot.loader.grub = {
+ enable = true;
+ version = 2;
+ device = "/dev/sda";
+ };
imports = [
- ../defaults.nix
- ../networks/fudo.org.nix
- ../profiles/server.nix
- ../config/fudo.nix
- ../profiles/services/basic_acme.nix
- ../profiles/services/heimdal_kdc.nix
- ../profiles/services/minecraft.nix
../hardware-configuration.nix
- ../packages/local-packages.nix
+
+ ../defaults.nix
+
+ # These should really both be settings...
+ # ../networks/fudo.org.nix
+ # ../profiles/server.nix
+ ];
+
+ fudo.profile = "server";
+ fudo.site = "nutty-club";
+ fudo.local-networks = [
+ "208.81.1.128/28"
+ "208.81.3.112/28"
+ "172.17.0.0/16"
+ "127.0.0.0/8"
];
environment.systemPackages = with pkgs; [
- acme-ca
+ docker
lxd
multipath-tools
+ nix-prefetch-docker
];
- fudo.auth.server = {
+ fudo.prometheus = {
enable = true;
- base = "dc=fudo,dc=org";
- organization = "Fudo";
- rootpw-file = "/srv/ldap/secure/root.pw";
- kerberos-host = "france.fudo.org";
- kerberos-keytab = "/srv/ldap/secure/ldap.keytab";
-
- sslCert = "/srv/ldap/france.fudo.org.pem";
- sslKey = "/srv/ldap/secure/france.fudo.org-key.pem";
- sslCACert = "/etc/nixos/static/fudo_ca.pem";
-
- listen-uris = [
- "ldap://${hostname}/"
- "ldaps://${hostname}/"
- "ldap://localhost/"
- "ldaps://localhost/"
- "ldapi:///"
+ hostname = "metrics.fudo.org";
+ service-discovery-dns = {
+ node = [ "node._metrics._tcp.fudo.org" ];
+ postfix = [ "postfix._metrics._tcp.fudo.org" ];
+ dovecot = [ "dovecot._metrics._tcp.fudo.org" ];
+ rspamd = [ "rspamd._metrics._tcp.fudo.org" ];
+ };
+ # Connections will be allowed from these networks. No auth is performed--the
+ # data is read-only anyway.
+ trusted-networks = [
+ "208.81.1.128/28"
+ "208.81.3.112/28"
+ "172.17.0.0/16"
+ "127.0.0.0/8"
];
+ };
- users = import ../fudo/users.nix;
+ fudo.grafana = {
+ enable = true;
+ hostname = "monitor.fudo.org";
+ smtp-username = "metrics";
+ smtp-password-file = "/srv/grafana/secure/smtp.passwd";
+ database-password-file = "/srv/grafana/secure/db.passwd";
+ admin-password-file = "/srv/grafana/secure/admin.passwd";
+ secret-key-file = "/srv/grafana/secure/secret.key";
+ prometheus-host = "metrics.fudo.org";
+ };
- groups = import ../fudo/groups.nix;
+ # So that grafana waits for postgresql
+ systemd.services.grafana.requires = [
+ "postgresql"
+ ];
- system-users = import ../fudo/system-users.nix;
+ fudo.postgresql = {
+ enable = true;
+ ssl-private-key = (acme-private-key hostname);
+ ssl-certificate = (acme-certificate hostname);
+ keytab = "/srv/postgres/secure/postgres.keytab";
+
+ # We allow connections from local networks. Auth is still required. Outside
+ # of these networks, no access is allowed.
+ #
+ # TODO: that's probably to strict, allow kerberos connections from anywhere.
+ local-networks = [
+ "208.81.1.128/28"
+ "208.81.3.112/28"
+ "192.168.11.1/24"
+ "127.0.0.1/8"
+ "172.17.0.0/16"
+ ];
+ };
+
+ # Not all users need access to france; don't allow LDAP-user access.
+ fudo.authentication.enable = false;
+
+ # But we DO run an LDAP auth server. Should be better-named.
+ fudo.auth = {
+ server = {
+ enable = true;
+ base = "dc=fudo,dc=org";
+ organization = "Fudo";
+ rootpw-file = "/srv/ldap/secure/root.pw";
+ kerberos-host = "france.fudo.org";
+ kerberos-keytab = "/srv/ldap/secure/ldap.keytab";
+
+ sslCert = "/srv/ldap/france.fudo.org.pem";
+ sslKey = "/srv/ldap/secure/france.fudo.org-key.pem";
+ sslCACert = fudo-ca;
+
+ # We're using fudo-generated certs for now, but we should move to ACME
+ # once I can figure out how to correctly produce the ca.pem file. Until
+ # then, the server will fail to start using these certs. See:
+ # https://serverfault.com/a/834565
+
+ # sslCert = (acme-bare-cert hostname);
+ # sslKey = (acme-private-key hostname);
+ # sslCACert = acme-ca;
+
+ # TODO: loop over v4 and v6 IPs.
+ listen-uris = [
+ "ldap://${host_ipv4}/"
+ "ldaps://${host_ipv4}/"
+ "ldap://localhost/"
+ "ldaps://localhost/"
+ "ldapi:///"
+ ];
+
+ users = import ../fudo/users.nix;
+
+ groups = import ../fudo/groups.nix;
+
+ system-users = import ../fudo/system-users.nix;
+ };
+
+ # Heimdal Kerberos server
+ kdc = {
+ enable = true;
+ database-path = "/var/heimdal/heimdal";
+ realm = "FUDO.ORG";
+ mkey-file = "/var/heimdal/m-key";
+ acl-file = "/etc/heimdal/kdc.acl";
+ bind-addresses = [
+ host_ipv4
+ "127.0.0.1"
+ "127.0.1.1"
+ ];
+ };
+ };
+
+ # TODO: not used yet
+ fudo.acme.hostnames = all-hostnames;
+
+ fudo.mail-server = import ../fudo/email.nix { inherit config; } // {
+ enableContainer = true;
+ debug = true;
+ monitoring = true;
+
+ hostname = mail-hostname;
+
+ postfix.ssl-certificate = (acme-certificate mail-hostname);
+ postfix.ssl-private-key = (acme-private-key mail-hostname);
+ dovecot.ssl-certificate = (acme-certificate mail-hostname);
+ dovecot.ssl-private-key = (acme-private-key mail-hostname);
+
+ state-directory = "${system-mail-directory}/var";
+ mail-directory = "${system-mail-directory}/mailboxes";
+
+ dovecot.ldap-reader-dn = "cn=user_db_reader,dc=fudo,dc=org";
+ dovecot.ldap-reader-passwd = removeSuffix "\n" (readFile /srv/ldap/secure/user_db.passwd);
+
+ # FIXME: use SSL once I can figure out Acme SSL cert CA for LDAP.
+ dovecot.ldap-urls = [ "ldap://france.fudo.org" ];
+
+ clamav.enable = true;
+
+ dkim.signing = true;
};
networking = {
@@ -64,7 +199,8 @@ in {
interfaces.enp4s0f0.useDHCP = true;
interfaces.enp4s0f1.useDHCP = true;
- enableIPv6 = true;
+ # TODO: fix IPv6
+ enableIPv6 = false;
# Create a bridge for VMs to use
macvlans = {
@@ -84,7 +220,7 @@ in {
macAddress = "02:d4:e8:3b:10:2f";
ipv4.addresses = [
{
- address = "208.81.3.117";
+ address = host_ipv4;
prefixLength = 28;
}
];
@@ -104,7 +240,82 @@ in {
hardware.bluetooth.enable = false;
- virtualisation.lxd = {
+ virtualisation = {
+ lxd = {
+ enable = true;
+ };
+
+ docker = {
+ enable = true;
+ enableOnBoot = true;
+
+ autoPrune = {
+ enable = true;
+ };
+ };
+ };
+
+ fileSystems = {
+ "/srv/archiva" = {
+ fsType = "btrfs";
+ options = ["subvol=archiva"];
+ label = "pool0";
+ };
+ "/srv/grafana" = {
+ fsType = "btrfs";
+ options = ["subvol=grafana"];
+ label = "pool0";
+ };
+ "${system-mail-directory}" = {
+ fsType = "btrfs";
+ options = ["subvol=mail"];
+ label = "pool0";
+ };
+ "/srv/gitlab" = {
+ fsType = "btrfs";
+ options = ["subvol=gitlab"];
+ label = "pool0";
+ };
+ };
+
+ ##
+ # Archiva
+ ##
+
+ users.extraUsers = {
+ archiva = {
+ isNormalUser = false;
+ group = "nogroup";
+ uid = 1000;
+ };
+ };
+
+ docker-containers = {
+ archiva = {
+ image = "xetusoss/archiva";
+ ports = ["127.0.0.1:8091:8080"];
+ volumes = [
+ "/srv/archiva:/archiva-data"
+ ];
+ environment = {
+ # Not directly connected to the world anyway
+ SSL_ENABLED = "false";
+ };
+ # Ugly as shit: name-to-uid lookup fails.
+ #user = "1000";
+ user = toString config.users.users.archiva.uid;
+ };
+ };
+
+ ###
+ # Minecraft
+ ###
+
+ fudo.minecraft-server = {
enable = true;
+ package = pkgs.minecraft-server_1_15_1;
+ data-dir = minecraft-data-dir;
+ world-name = "selbyland";
+ motd = "Welcome to the Selby Minecraft server.";
};
}
diff --git a/hosts/nostromo.nix b/hosts/nostromo.nix
index 24e5b64..578b30e 100644
--- a/hosts/nostromo.nix
+++ b/hosts/nostromo.nix
@@ -1,7 +1,7 @@
{ config, pkgs, ... }:
let
- hostname = "nostromo";
+ hostname = "nostromo.sea.fudo.org";
in {
@@ -16,11 +16,20 @@ in {
../networks/sea.fudo.org.nix
../profiles/server.nix
../hardware-configuration.nix
-
- ../profiles/services/postgres.nix
# ../profiles/services/local_nameserver.nix
];
+ fudo.postgresql = {
+ enable = true;
+ ssl-private-key = "/srv/nostromo.sea.fudo.org/certs/private/privkey.pem";
+ ssl-certificate = "/srv/nostromo.sea.fudo.org/certs/cert.pem";
+ keytab = "/srv/nostromo.sea.fudo.org/keytabs/postgres.keytab";
+
+ local-networks = [
+ "10.0.0.1/24"
+ ];
+ };
+
networking = {
hostName = hostname;
diff --git a/networks/fudo.org.nix b/networks/fudo.org.nix
index 96f0666..9b4ba6e 100644
--- a/networks/fudo.org.nix
+++ b/networks/fudo.org.nix
@@ -1,27 +1,112 @@
-{ config, pkgs, ... }:
+{ lib, config, pkgs, ... }:
-{
- config.time.timeZone = "America/Winnipeg";
+let
+ hostname = config.networking.hostName;
- config.services.cron = {
- mailto = "admin@fudo.org";
+ www-root = "/var/www";
+
+ index = pkgs.writeTextFile {
+ name = "index.html";
+
+ text = ''
+
+
+ ${hostname}
+
+
+ ${hostname}
+
+
+ '';
+ destination = www-root + ("/" + hostname);
};
- config.networking = {
- domain = "fudo.org";
+in {
- search = ["fudo.org"];
+ config = {
+ time.timeZone = "America/Winnipeg";
- firewall.enable = false;
+ services.cron = {
+ mailto = "admin@fudo.org";
+ };
- networkmanager.enable = pkgs.lib.mkForce false;
+ networking = {
+ domain = "fudo.org";
- defaultGateway = "208.81.3.113";
+ search = ["fudo.org"];
- nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ];
- };
+ firewall.enable = false;
- config.services.prometheus.exporters = {
- node.enable = true;
+ networkmanager.enable = pkgs.lib.mkForce false;
+
+ defaultGateway = "208.81.3.113";
+
+ nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ];
+ };
+
+ security.acme.certs."${hostname}" = {
+ email = "admin@fudo.org";
+
+ plugins = [
+ "fullchain.pem"
+ "full.pem"
+ "key.pem"
+ "chain.pem"
+ "cert.pem"
+ ];
+ };
+
+ services = {
+ prometheus.exporters.node = {
+ enable = true;
+ enabledCollectors = [ "systemd" ];
+ user = "node";
+ };
+
+ nginx = {
+ enable = true;
+
+ recommendedGzipSettings = true;
+ recommendedOptimisation = true;
+ recommendedTlsSettings = true;
+
+ virtualHosts = {
+
+ "${hostname}" = {
+ enableACME = true;
+ forceSSL = true;
+ root = www-root + ("/" + hostname);
+
+ listen = [
+ {
+ addr = hostname;
+ port = 80;
+ ssl = false;
+ }
+ {
+ addr = hostname;
+ port = 443;
+ ssl = true;
+ }
+ ];
+
+ locations."/metrics/node" = {
+ extraConfig = ''
+ allow 208.81.1.128/28;
+ allow 208.81.3.112/28;
+ allow 127.0.0.0/16;
+ deny all;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ '';
+ # proxy_set_header Host $http_host;
+
+ proxyPass = "http://127.0.0.1:9100/metrics";
+ };
+ };
+ };
+ };
+ };
};
}
diff --git a/packages/acme-ca.nix b/packages/letsencrypt-ca.nix
similarity index 80%
rename from packages/acme-ca.nix
rename to packages/letsencrypt-ca.nix
index f4aaca0..c0957e5 100644
--- a/packages/acme-ca.nix
+++ b/packages/letsencrypt-ca.nix
@@ -1,8 +1,6 @@
{ stdenv, fetchurl }:
let
- # url = "https://letsencrypt.org/certs/isrgrootx1.pem.txt";
- # sha256 = "4c99356c265ee06c0ae0502e74d38231263513726d001cfe28ea25e70af2cc7f";
url = "https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt";
sha256 = "b6dd03f7fb8508e4f7ffe82ca8a3f98dde163e0bd44897e112a0850a5b606acf";
diff --git a/packages/local-packages.nix b/packages/local-packages.nix
deleted file mode 100644
index e93431f..0000000
--- a/packages/local-packages.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ pkgs, ... }:
-
-{
- nixpkgs.config.packageOverrides = pkgs: rec {
- acme-ca = import ./acme-ca.nix {
- stdenv = pkgs.stdenv;
- fetchurl = builtins.fetchurl;
- };
- };
-}
diff --git a/packages/minecraft-server_1_15_1.nix b/packages/local.nix
similarity index 55%
rename from packages/minecraft-server_1_15_1.nix
rename to packages/local.nix
index a923bf4..5318f41 100644
--- a/packages/minecraft-server_1_15_1.nix
+++ b/packages/local.nix
@@ -2,6 +2,11 @@
{
nixpkgs.config.packageOverrides = pkgs: rec {
+ letsencrypt-ca = import ./letsencrypt-ca.nix {
+ stdenv = pkgs.stdenv;
+ fetchurl = builtins.fetchurl;
+ };
+
minecraft-server_1_15_1 = pkgs.minecraft-server.overrideAttrs (oldAttrs: rec {
version = "1.15.1";
src = builtins.fetchurl {
@@ -9,5 +14,10 @@
sha256 = "a0c062686bee5a92d60802ca74d198548481802193a70dda6d5fe7ecb7207993";
};
});
+
+ postgresql_11_gssapi = pkgs.postgresql_11.overrideAttrs (oldAttrs: rec {
+ configureFlags = oldAttrs.configureFlags ++ [ "--with-gssapi" ];
+ buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ];
+ });
};
}
diff --git a/packages/options/postgresql_11.nix b/packages/options/postgresql_11.nix
deleted file mode 100644
index 51d14c7..0000000
--- a/packages/options/postgresql_11.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
- userOpts = { name, config, ... }: {
- options = {
- passwd = mkOption {
- type = types.str;
- description = ''
- The password of a given user.
- '';
- };
-
- databases = mkOption {
- type = types.listOf types.str;
- default = [];
- description = ''
- A list of databases to which this user should have access.
- '';
- };
- };
- };
-
-in {
-
- options = {
- fudo.postgresql = {
- databases = mkOption {
- type = types.attrsOf types.lines;
- default = {};
- description = ''
- A map of database_name => database_defn.
- '';
- };
- users = mkOption {
- type = with types; attrsOf (submodule userOpts);
- default = {};
- description = ''
- A map of user_name => { user_attributes }.
- '';
- };
- };
- };
-}
diff --git a/packages/postgresql_11_gssapi.nix b/packages/postgresql_11_gssapi.nix
deleted file mode 100644
index 12700ea..0000000
--- a/packages/postgresql_11_gssapi.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ pkgs, ... }:
-
-{
- nixpkgs.config.packageOverrides = pkgs: rec {
- postgresql_11_gssapi = pkgs.postgresql_11.overrideAttrs (oldAttrs: rec {
- configureFlags = oldAttrs.configureFlags ++ [ "--with-gssapi" ];
- buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ];
- });
- };
-}
diff --git a/profiles/desktop.nix b/profiles/desktop.nix
index 3b28702..445d47d 100644
--- a/profiles/desktop.nix
+++ b/profiles/desktop.nix
@@ -77,6 +77,10 @@
services.dbus.socketActivated = true;
+ services.openssh.forwardX11 = true;
+
+ programs.ssh.forwardX11 = true;
+
sound.enable = true;
hardware.pulseaudio.enable = true;
diff --git a/profiles/server.nix b/profiles/server.nix
index 8dfc03c..cb3ff2c 100644
--- a/profiles/server.nix
+++ b/profiles/server.nix
@@ -1,8 +1,14 @@
{ config, pkgs, ... }:
{
- environment.systemPackages = with pkgs; [
- ];
+ environment = {
+ systemPackages = with pkgs; [
+ ];
+
+ noXlibs = true;
+ };
+
+ security.hideProcessInformation = true;
boot.tmpOnTmpfs = true;
diff --git a/profiles/services/heimdal_kdc.nix b/profiles/services/heimdal_kdc.nix
deleted file mode 100644
index 4f9d46b..0000000
--- a/profiles/services/heimdal_kdc.nix
+++ /dev/null
@@ -1,34 +0,0 @@
-{ config, pkgs, environment, ... }:
-
-let
- databasePath = /var/heimdal/heimdal;
-
-in {
- environment = {
- systemPackages = with pkgs; [
- heimdalFull
- ];
- };
-
- systemd.services = {
- heimdal-kdc = {
- enable = true;
- wantedBy = [ "multi-user.target" ];
- after = [ "network.target" ];
- description = "Heimdal Kerberos Key Distribution Center (ticket server)";
- serviceConfig = {
- ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kdc'';
- };
- };
-
- heimdal-admin-server = {
- enable = true;
- wantedBy = [ "multi-user.target" ];
- after = [ "network.target" ];
- description = "Heimdal Kerberos Remote Administration Server";
- serviceConfig = {
- ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kadmind'';
- };
- };
- };
-}
diff --git a/profiles/services/minecraft.nix b/profiles/services/minecraft.nix
deleted file mode 100644
index 47ba6c1..0000000
--- a/profiles/services/minecraft.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-{ pkgs, ... }:
-
-let
- dataDir = /srv/minecraft/data;
-in {
- services.minecraft-server = {
- enable = true;
- package = pkgs.minecraft-server_1_15_1;
- dataDir = dataDir;
- eula = true;
- declarative = true;
- serverProperties = {
- level-name = "selbyland";
- motd = "Welcome to the Selby Minecraft Server";
- difficulty = 2;
- gamemode = "survival";
- };
- };
-}
diff --git a/profiles/services/postgres.nix b/profiles/services/postgres.nix
deleted file mode 100644
index d097882..0000000
--- a/profiles/services/postgres.nix
+++ /dev/null
@@ -1,65 +0,0 @@
-{ config, pkgs, environment, ... }:
-
-let
- dataPath = /srv + ("/" + config.networking.hostName);
-
-in {
-
- environment = {
-
- systemPackages = with pkgs; [
- postgresql_11_gssapi
- ];
-
- etc = {
- "postgresql/private/privkey.pem" = {
- mode = "0400";
- user = "postgres";
- group = "postgres";
- source = dataPath + "/certs/private/privkey.pem";
- };
-
- "postgresql/cert.pem" = {
- mode = "0444";
- user = "postgres";
- group = "postgres";
- source = dataPath + "/certs/cert.pem";
- };
-
- "postgresql/private/postgres.keytab" = {
- mode = "0400";
- user = "postgres";
- group = "postgres";
- source = dataPath + "/keytabs/postgres.keytab";
- };
- };
- };
-<
- services.postgresql = {
- enable = true;
- package = pkgs.postgresql_11_gssapi;
- enableTCPIP = true;
-
- extraConfig =
- ''
- krb_server_keyfile = '/etc/postgresql/private/postgres.keytab'
-
- ssl = true
- ssl_cert_file = '/etc/postgresql/cert.pem'
- ssl_key_file = '/etc/postgresql/private/privkey.pem'
- '';
-
- authentication =
- ''
- 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
-
- # local network
- host all all 10.0.0.1/24 gss include_realm=0 krb_realm=FUDO.ORG
- host all all 2601:600:997f:fc00::/60 gss include_realm=0 krb_realm=FUDO.ORG
- '';
- };
-}
diff --git a/static/fudo_ca.pem b/static/fudo_ca.pem
index cddc528..60637eb 100644
--- a/static/fudo_ca.pem
+++ b/static/fudo_ca.pem
@@ -1,23 +1,17 @@
-----BEGIN CERTIFICATE-----
-MIIDvzCCAyigAwIBAgIJAIO7c/KlNXiJMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD
-VQQGEwJDQTERMA8GA1UECBMITWFuaXRvYmExETAPBgNVBAcTCFdpbm5pcGVnMREw
-DwYDVQQKEwhGdWRvLm9yZzERMA8GA1UECxMIU2VjdXJpdHkxIjAgBgNVBAMTGUZ1
-ZG8ub3JnIFJvb3QgQ2VydGlmaWNhdGUxHTAbBgkqhkiG9w0BCQEWDmFkbWluQGZ1
-ZG8ub3JnMB4XDTA2MTIyMjIyMTYxMVoXDTE2MTIxOTIyMTYxMVowgZwxCzAJBgNV
-BAYTAkNBMREwDwYDVQQIEwhNYW5pdG9iYTERMA8GA1UEBxMIV2lubmlwZWcxETAP
-BgNVBAoTCEZ1ZG8ub3JnMREwDwYDVQQLEwhTZWN1cml0eTEiMCAGA1UEAxMZRnVk
-by5vcmcgUm9vdCBDZXJ0aWZpY2F0ZTEdMBsGCSqGSIb3DQEJARYOYWRtaW5AZnVk
-by5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANZpJiFZjgs1M744PTLH
-nAQVMC2VzH76+qNbLClNK3n6dknrx+FMFq35naXnJLnkmEhHW5DFMeQBudCAD1tv
-DTj6KxgBbBoMFIXfukQjMOjFIXcPE0MsbfJowjJxGDA3KFE5pLs5u5suGPLXPpog
-6ASSTg1n75crFSU/d9hN+drVAgMBAAGjggEFMIIBATAdBgNVHQ4EFgQUQS8uOVCa
-rLmMGYU6T0pIkDAnQr8wgdEGA1UdIwSByTCBxoAUQS8uOVCarLmMGYU6T0pIkDAn
-Qr+hgaKkgZ8wgZwxCzAJBgNVBAYTAkNBMREwDwYDVQQIEwhNYW5pdG9iYTERMA8G
-A1UEBxMIV2lubmlwZWcxETAPBgNVBAoTCEZ1ZG8ub3JnMREwDwYDVQQLEwhTZWN1
-cml0eTEiMCAGA1UEAxMZRnVkby5vcmcgUm9vdCBDZXJ0aWZpY2F0ZTEdMBsGCSqG
-SIb3DQEJARYOYWRtaW5AZnVkby5vcmeCCQCDu3PypTV4iTAMBgNVHRMEBTADAQH/
-MA0GCSqGSIb3DQEBBQUAA4GBAH2ZUJoSeNcslGlQUs7xPWwTSKVZ0OGpfhdI/pmA
-WQGC6Kj5MzlEunqaBEKaLSJ9yx/t0l5c5aFT77ERFacH0lhWme+AACEDAKuCbMeL
-fRnsQYoPZ0jEygnxvdG4IHl9dmKWr9SR361OWOP0uYpvWtiuF5w0GvFLJ0L5x7jy
-xZuP
+MIICqTCCAhICCQD6IEI142ZdAjANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMC
+Q0ExETAPBgNVBAgMCE1hbml0b2JhMREwDwYDVQQHDAhXaW5uaXBlZzERMA8GA1UE
+CgwIRnVkby5vcmcxHjAcBgNVBAsMFVNlY3VyaXR5ICYgRW5jcnlwdGlvbjERMA8G
+A1UEAwwIZnVkby5vcmcxHTAbBgkqhkiG9w0BCQEWDmFkbWluQGZ1ZG8ub3JnMB4X
+DTIwMDEwNzIzMzA0NFoXDTQ5MTIzMDIzMzA0NFowgZgxCzAJBgNVBAYTAkNBMREw
+DwYDVQQIDAhNYW5pdG9iYTERMA8GA1UEBwwIV2lubmlwZWcxETAPBgNVBAoMCEZ1
+ZG8ub3JnMR4wHAYDVQQLDBVTZWN1cml0eSAmIEVuY3J5cHRpb24xETAPBgNVBAMM
+CGZ1ZG8ub3JnMR0wGwYJKoZIhvcNAQkBFg5hZG1pbkBmdWRvLm9yZzCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA1mkmIVmOCzUzvjg9MsecBBUwLZXMfvr6o1ss
+KU0refp2SevH4UwWrfmdpeckueSYSEdbkMUx5AG50IAPW28NOPorGAFsGgwUhd+6
+RCMw6MUhdw8TQyxt8mjCMnEYMDcoUTmkuzm7my4Y8tc+miDoBJJODWfvlysVJT93
+2E352tUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQA4SLrx3nJfHrq/AJmemzw+IZoc
+WWERdSIMzCwM7iYSlp0dV0muAlm+QasOif9ZIDQF7Sl41DP6kznQO1VLVH4MBL0w
+67qJzmuXDO2nj959YxsblDHrP4vTR/Am2aBejTFTEgYHqVgiZA+mHzd6YuJgDc/8
+x61QIkPH0uK79dit/w==
-----END CERTIFICATE-----
diff --git a/static/letsencryptauthorityx3.pem b/static/letsencryptauthorityx3.pem
new file mode 100644
index 0000000..5decaec
--- /dev/null
+++ b/static/letsencryptauthorityx3.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1
+WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX
+NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf
+89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl
+Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc
+Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz
+uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB
+AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU
+BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB
+FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo
+SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js
+LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF
+BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG
+AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD
+VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB
+ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx
+A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM
+UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2
+DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1
+eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu
+OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw
+p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY
+2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0
+ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR
+PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b
+rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt
+-----END CERTIFICATE-----