Currently broken config...

This commit is contained in:
root 2020-01-15 11:24:11 -06:00
parent 46c45f4440
commit 00a97b1d94
51 changed files with 2991 additions and 364 deletions

View File

@ -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 = ''
<html>
<head>
<title>${hostname}</title>
</head>
<body>
<h1>${hostname}</title>
</body>
</html>
'';
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);
};
}

View File

@ -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
'';
};
};
};
}

16
config/fudo/common.nix Normal file
View File

@ -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 = [];
};
};
}

127
config/fudo/grafana.nix Normal file
View File

@ -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}/";
}
];
};
};
}

99
config/fudo/kdc.nix Normal file
View File

@ -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'';
};
};
};
};
}

View File

@ -206,10 +206,11 @@ in {
}; };
sslCACert = mkOption { sslCACert = mkOption {
type = types.str; type = with types; nullOr str;
description = '' description = ''
The path to the SSL CA cert used to sign the certificate. The path to the SSL CA cert used to sign the certificate.
''; '';
default = null;
}; };
organization = mkOption { organization = mkOption {
@ -329,7 +330,7 @@ in {
TLSCertificateFile ${cfg.sslCert} TLSCertificateFile ${cfg.sslCert}
TLSCertificateKeyFile ${cfg.sslKey} 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=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" 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 users read
by * none 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 group.exact="cn=admin,ou=groups,${cfg.base}" write
by dn.exact="cn=user_db_reader,${cfg.base}" read
by users read by users read
by * none by * none
@ -384,6 +386,9 @@ access to *
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
by users read by users read
by * none by * none
index objectClass,uid eq
''; '';
declarativeContents = '' declarativeContents = ''

View File

@ -1,3 +1,7 @@
# UNFINISHED!
#
# The plan is to bootstrap a local network config: DNS, DHCP, etc.
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
with lib; with lib;
@ -33,14 +37,14 @@ let
ipv6Address = mkOption { ipv6Address = mkOption {
type = types.str; type = types.str;
description = '' description = ''
The V6 IP of a given host, if any. The V6 IP of this nameserver, if any.
''; '';
}; };
ipv4Address = mkOption { ipv4Address = mkOption {
type = types.str; type = types.str;
description = '' description = ''
The V4 IP of a given host, if any. The V4 IP of this nameserver, if any.
''; '';
}; };
@ -56,9 +60,6 @@ let
}; };
in { in {
imports = [
./fudo/ldap.nix
];
options = { options = {

View File

@ -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;
};
};
};
};
}

170
config/fudo/mail.nix Normal file
View File

@ -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
];
}

View File

@ -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;
};
};
}

114
config/fudo/mail/dkim.nix Normal file
View File

@ -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} - -"
];
};
}

View File

@ -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}
'';
};
}

View File

@ -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}" ];

View File

@ -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}" ];

View File

@ -0,0 +1,3 @@
#!/bin/bash
set -o errexit
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham

View File

@ -0,0 +1,3 @@
#!/bin/bash
set -o errexit
exec rspamc -h /run/rspamd/worker-controller.sock learn_spam

View File

@ -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");
};
};
}

View File

@ -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 ];
};
}

View File

@ -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;
};
};
};
}

View File

@ -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";
};
};
};
};
};
}

197
config/fudo/postgres.nix Normal file
View File

@ -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}
'';
};
};
}

32
config/fudo/profiles.nix Normal file
View File

@ -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};
}

View File

@ -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
];
};
}

View File

@ -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;
};
}

204
config/fudo/prometheus.nix Normal file
View File

@ -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 <host:port> docker targets from which to gather node data.
'';
default = [];
};
push-url = mkOption {
type = with types; nullOr str;
description = ''
The <host:port> that services can use to manually push data.
'';
default = null;
};
push-address = mkOption {
type = with types; nullOr str;
description = ''
The <host:port> 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;
};
};
};
};
}

79
config/fudo/sites.nix Normal file
View File

@ -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};
}

21
config/local.nix Normal file
View File

@ -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
];
}

View File

@ -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)}
# '';
# };
} }

View File

@ -5,10 +5,8 @@
{ {
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
./packages/postgresql_11_gssapi.nix ./packages/local.nix
./packages/minecraft-server_1_15_1.nix ./config/local.nix
./config/fudo.nix
./config/postgresql_11.nix
]; ];
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
@ -97,7 +95,6 @@
mosh.enable = true; mosh.enable = true;
ssh = { ssh = {
forwardX11 = true;
extraConfig = '' extraConfig = ''
GSSAPIAuthentication yes GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes GSSAPIDelegateCredentials yes
@ -126,7 +123,6 @@
openssh = { openssh = {
enable = true; enable = true;
startWhenNeeded = true; startWhenNeeded = true;
forwardX11 = true;
extraConfig = '' extraConfig = ''
GSSAPIAuthentication yes GSSAPIAuthentication yes
GSSAPICleanupCredentials yes GSSAPICleanupCredentials yes
@ -146,34 +142,19 @@
}; };
}; };
users.extraUsers = {
node = {
isSystemUser = true;
group = "nogroup";
};
};
users.groups = { users.groups = {
fudosys = { fudosys = {
gid = 888; gid = 888;
}; };
}; };
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.extraUsers = { users.extraUsers = {
niten = { niten = {
isNormalUser = true; isNormalUser = true;

18
fudo/alias-users.nix Normal file
View File

@ -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"];
}

39
fudo/email.nix Normal file
View File

@ -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"
];
}

View File

@ -0,0 +1,3 @@
# Emails for which we won't accept any email.
[]

View File

@ -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"
]

View File

@ -8,4 +8,9 @@
description = "System Authenticator"; description = "System Authenticator";
hashed-password = "{MD5}N36/kQ64mev1HARddvVk7Q=="; hashed-password = "{MD5}N36/kQ64mev1HARddvVk7Q==";
}; };
user_db_reader = {
description = "User Database Reader";
hashed-password = "{SSHA}IVKhrB+wMOCI/CCzbJW8sNDbH67ZTMBv";
};
} }

36
fudo/user-aliases.nix Normal file
View File

@ -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"
];
}

View File

@ -393,6 +393,14 @@
hashed-password = "{SSHA}DKnhrycmXSu4HKWFPeBXA9xvZ0ytgXIpZA10tg=="; hashed-password = "{SSHA}DKnhrycmXSu4HKWFPeBXA9xvZ0ytgXIpZA10tg==";
}; };
# Used to send alerts from grafana
metrics = {
uid = 10109;
group = "fudo";
common-name = "Fudo Metrics";
hashed-password = "{SSHA}FveEVy6kljQZey0xp0nF62SMlO5nATJ1";
};
testuser = { testuser = {
uid = 10110; uid = 10110;
group = "fudo"; group = "fudo";

View File

@ -1,35 +1,116 @@
{ config, pkgs, ... }: { config, pkgs, lib, ... }:
with lib;
let let
hostname = "france.fudo.org"; 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 { in {
boot.loader.grub.enable = true; boot.loader.grub = {
boot.loader.grub.version = 2; enable = true;
boot.loader.grub.device = "/dev/sda"; version = 2;
device = "/dev/sda";
security.hideProcessInformation = true; };
imports = [ 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 ../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; [ environment.systemPackages = with pkgs; [
acme-ca docker
lxd lxd
multipath-tools multipath-tools
nix-prefetch-docker
]; ];
fudo.auth.server = { fudo.prometheus = {
enable = true;
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"
];
};
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";
};
# So that grafana waits for postgresql
systemd.services.grafana.requires = [
"postgresql"
];
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; enable = true;
base = "dc=fudo,dc=org"; base = "dc=fudo,dc=org";
organization = "Fudo"; organization = "Fudo";
@ -39,11 +120,21 @@ in {
sslCert = "/srv/ldap/france.fudo.org.pem"; sslCert = "/srv/ldap/france.fudo.org.pem";
sslKey = "/srv/ldap/secure/france.fudo.org-key.pem"; sslKey = "/srv/ldap/secure/france.fudo.org-key.pem";
sslCACert = "/etc/nixos/static/fudo_ca.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 = [ listen-uris = [
"ldap://${hostname}/" "ldap://${host_ipv4}/"
"ldaps://${hostname}/" "ldaps://${host_ipv4}/"
"ldap://localhost/" "ldap://localhost/"
"ldaps://localhost/" "ldaps://localhost/"
"ldapi:///" "ldapi:///"
@ -56,6 +147,50 @@ in {
system-users = import ../fudo/system-users.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 = { networking = {
hostName = hostname; hostName = hostname;
@ -64,7 +199,8 @@ in {
interfaces.enp4s0f0.useDHCP = true; interfaces.enp4s0f0.useDHCP = true;
interfaces.enp4s0f1.useDHCP = true; interfaces.enp4s0f1.useDHCP = true;
enableIPv6 = true; # TODO: fix IPv6
enableIPv6 = false;
# Create a bridge for VMs to use # Create a bridge for VMs to use
macvlans = { macvlans = {
@ -84,7 +220,7 @@ in {
macAddress = "02:d4:e8:3b:10:2f"; macAddress = "02:d4:e8:3b:10:2f";
ipv4.addresses = [ ipv4.addresses = [
{ {
address = "208.81.3.117"; address = host_ipv4;
prefixLength = 28; prefixLength = 28;
} }
]; ];
@ -104,7 +240,82 @@ in {
hardware.bluetooth.enable = false; hardware.bluetooth.enable = false;
virtualisation.lxd = { virtualisation = {
lxd = {
enable = true; 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.";
};
} }

View File

@ -1,7 +1,7 @@
{ config, pkgs, ... }: { config, pkgs, ... }:
let let
hostname = "nostromo"; hostname = "nostromo.sea.fudo.org";
in { in {
@ -16,11 +16,20 @@ in {
../networks/sea.fudo.org.nix ../networks/sea.fudo.org.nix
../profiles/server.nix ../profiles/server.nix
../hardware-configuration.nix ../hardware-configuration.nix
../profiles/services/postgres.nix
# ../profiles/services/local_nameserver.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 = { networking = {
hostName = hostname; hostName = hostname;

View File

@ -1,13 +1,36 @@
{ config, pkgs, ... }: { lib, config, pkgs, ... }:
{ let
config.time.timeZone = "America/Winnipeg"; hostname = config.networking.hostName;
config.services.cron = { www-root = "/var/www";
index = pkgs.writeTextFile {
name = "index.html";
text = ''
<html>
<head>
<title>${hostname}</title>
</head>
<body>
<h1>${hostname}</title>
</body>
</html>
'';
destination = www-root + ("/" + hostname);
};
in {
config = {
time.timeZone = "America/Winnipeg";
services.cron = {
mailto = "admin@fudo.org"; mailto = "admin@fudo.org";
}; };
config.networking = { networking = {
domain = "fudo.org"; domain = "fudo.org";
search = ["fudo.org"]; search = ["fudo.org"];
@ -21,7 +44,69 @@
nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ]; nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ];
}; };
config.services.prometheus.exporters = { security.acme.certs."${hostname}" = {
node.enable = true; 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";
};
};
};
};
};
}; };
} }

View File

@ -1,8 +1,6 @@
{ stdenv, fetchurl }: { stdenv, fetchurl }:
let let
# url = "https://letsencrypt.org/certs/isrgrootx1.pem.txt";
# sha256 = "4c99356c265ee06c0ae0502e74d38231263513726d001cfe28ea25e70af2cc7f";
url = "https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt"; url = "https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt";
sha256 = "b6dd03f7fb8508e4f7ffe82ca8a3f98dde163e0bd44897e112a0850a5b606acf"; sha256 = "b6dd03f7fb8508e4f7ffe82ca8a3f98dde163e0bd44897e112a0850a5b606acf";

View File

@ -1,10 +0,0 @@
{ pkgs, ... }:
{
nixpkgs.config.packageOverrides = pkgs: rec {
acme-ca = import ./acme-ca.nix {
stdenv = pkgs.stdenv;
fetchurl = builtins.fetchurl;
};
};
}

View File

@ -2,6 +2,11 @@
{ {
nixpkgs.config.packageOverrides = pkgs: rec { 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 { minecraft-server_1_15_1 = pkgs.minecraft-server.overrideAttrs (oldAttrs: rec {
version = "1.15.1"; version = "1.15.1";
src = builtins.fetchurl { src = builtins.fetchurl {
@ -9,5 +14,10 @@
sha256 = "a0c062686bee5a92d60802ca74d198548481802193a70dda6d5fe7ecb7207993"; sha256 = "a0c062686bee5a92d60802ca74d198548481802193a70dda6d5fe7ecb7207993";
}; };
}); });
postgresql_11_gssapi = pkgs.postgresql_11.overrideAttrs (oldAttrs: rec {
configureFlags = oldAttrs.configureFlags ++ [ "--with-gssapi" ];
buildInputs = oldAttrs.buildInputs ++ [ pkgs.krb5 ];
});
}; };
} }

View File

@ -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 }.
'';
};
};
};
}

View File

@ -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 ];
});
};
}

View File

@ -77,6 +77,10 @@
services.dbus.socketActivated = true; services.dbus.socketActivated = true;
services.openssh.forwardX11 = true;
programs.ssh.forwardX11 = true;
sound.enable = true; sound.enable = true;
hardware.pulseaudio.enable = true; hardware.pulseaudio.enable = true;

View File

@ -1,9 +1,15 @@
{ config, pkgs, ... }: { config, pkgs, ... }:
{ {
environment.systemPackages = with pkgs; [ environment = {
systemPackages = with pkgs; [
]; ];
noXlibs = true;
};
security.hideProcessInformation = true;
boot.tmpOnTmpfs = true; boot.tmpOnTmpfs = true;
services.xserver.enable = false; services.xserver.enable = false;

View File

@ -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'';
};
};
};
}

View File

@ -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";
};
};
}

View File

@ -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
'';
};
}

View File

@ -1,23 +1,17 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDvzCCAyigAwIBAgIJAIO7c/KlNXiJMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD MIICqTCCAhICCQD6IEI142ZdAjANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMC
VQQGEwJDQTERMA8GA1UECBMITWFuaXRvYmExETAPBgNVBAcTCFdpbm5pcGVnMREw Q0ExETAPBgNVBAgMCE1hbml0b2JhMREwDwYDVQQHDAhXaW5uaXBlZzERMA8GA1UE
DwYDVQQKEwhGdWRvLm9yZzERMA8GA1UECxMIU2VjdXJpdHkxIjAgBgNVBAMTGUZ1 CgwIRnVkby5vcmcxHjAcBgNVBAsMFVNlY3VyaXR5ICYgRW5jcnlwdGlvbjERMA8G
ZG8ub3JnIFJvb3QgQ2VydGlmaWNhdGUxHTAbBgkqhkiG9w0BCQEWDmFkbWluQGZ1 A1UEAwwIZnVkby5vcmcxHTAbBgkqhkiG9w0BCQEWDmFkbWluQGZ1ZG8ub3JnMB4X
ZG8ub3JnMB4XDTA2MTIyMjIyMTYxMVoXDTE2MTIxOTIyMTYxMVowgZwxCzAJBgNV DTIwMDEwNzIzMzA0NFoXDTQ5MTIzMDIzMzA0NFowgZgxCzAJBgNVBAYTAkNBMREw
BAYTAkNBMREwDwYDVQQIEwhNYW5pdG9iYTERMA8GA1UEBxMIV2lubmlwZWcxETAP DwYDVQQIDAhNYW5pdG9iYTERMA8GA1UEBwwIV2lubmlwZWcxETAPBgNVBAoMCEZ1
BgNVBAoTCEZ1ZG8ub3JnMREwDwYDVQQLEwhTZWN1cml0eTEiMCAGA1UEAxMZRnVk ZG8ub3JnMR4wHAYDVQQLDBVTZWN1cml0eSAmIEVuY3J5cHRpb24xETAPBgNVBAMM
by5vcmcgUm9vdCBDZXJ0aWZpY2F0ZTEdMBsGCSqGSIb3DQEJARYOYWRtaW5AZnVk CGZ1ZG8ub3JnMR0wGwYJKoZIhvcNAQkBFg5hZG1pbkBmdWRvLm9yZzCBnzANBgkq
by5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANZpJiFZjgs1M744PTLH hkiG9w0BAQEFAAOBjQAwgYkCgYEA1mkmIVmOCzUzvjg9MsecBBUwLZXMfvr6o1ss
nAQVMC2VzH76+qNbLClNK3n6dknrx+FMFq35naXnJLnkmEhHW5DFMeQBudCAD1tv KU0refp2SevH4UwWrfmdpeckueSYSEdbkMUx5AG50IAPW28NOPorGAFsGgwUhd+6
DTj6KxgBbBoMFIXfukQjMOjFIXcPE0MsbfJowjJxGDA3KFE5pLs5u5suGPLXPpog RCMw6MUhdw8TQyxt8mjCMnEYMDcoUTmkuzm7my4Y8tc+miDoBJJODWfvlysVJT93
6ASSTg1n75crFSU/d9hN+drVAgMBAAGjggEFMIIBATAdBgNVHQ4EFgQUQS8uOVCa 2E352tUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQA4SLrx3nJfHrq/AJmemzw+IZoc
rLmMGYU6T0pIkDAnQr8wgdEGA1UdIwSByTCBxoAUQS8uOVCarLmMGYU6T0pIkDAn WWERdSIMzCwM7iYSlp0dV0muAlm+QasOif9ZIDQF7Sl41DP6kznQO1VLVH4MBL0w
Qr+hgaKkgZ8wgZwxCzAJBgNVBAYTAkNBMREwDwYDVQQIEwhNYW5pdG9iYTERMA8G 67qJzmuXDO2nj959YxsblDHrP4vTR/Am2aBejTFTEgYHqVgiZA+mHzd6YuJgDc/8
A1UEBxMIV2lubmlwZWcxETAPBgNVBAoTCEZ1ZG8ub3JnMREwDwYDVQQLEwhTZWN1 x61QIkPH0uK79dit/w==
cml0eTEiMCAGA1UEAxMZRnVkby5vcmcgUm9vdCBDZXJ0aWZpY2F0ZTEdMBsGCSqG
SIb3DQEJARYOYWRtaW5AZnVkby5vcmeCCQCDu3PypTV4iTAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBBQUAA4GBAH2ZUJoSeNcslGlQUs7xPWwTSKVZ0OGpfhdI/pmA
WQGC6Kj5MzlEunqaBEKaLSJ9yx/t0l5c5aFT77ERFacH0lhWme+AACEDAKuCbMeL
fRnsQYoPZ0jEygnxvdG4IHl9dmKWr9SR361OWOP0uYpvWtiuF5w0GvFLJ0L5x7jy
xZuP
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -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-----