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 {
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 = ''

View File

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

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