Got ldap on nutboy3 and jabber on legatus

This commit is contained in:
niten 2021-12-10 18:47:50 -08:00
parent c87448ff13
commit 541890c08f
9 changed files with 211 additions and 158 deletions

View File

@ -111,11 +111,18 @@ in {
description = "Map of host to domains to domain options.";
default = { };
};
challenge-path = mkOption {
type = str;
description = "Web-accessible path for responding to ACME challenges.";
default = "/run/fudo-acme/challenge";
};
};
config = {
security.acme.certs = mapAttrs (domain: domainOpts: {
email = domainOpts.admin-email;
webroot = cfg.challenge-path;
extraDomainNames = domainOpts.extra-domains;
}) localDomains;
@ -130,12 +137,14 @@ in {
recommendedProxySettings = true;
virtualHosts.${config.instance.host-fqdn} = {
enableACME = true;
forceSSL = true;
# Just...force override if you want this to point somewhere.
locations."/" = {
return = "403 Forbidden";
serverAliases = attrNames localDomains;
locations = {
"/.well-known/acme-challenge" = {
root = cfg.challenge-path;
};
"/" = {
return = "301 https://$host$request_uri";
};
};
};
};
@ -156,7 +165,9 @@ in {
copyOpts.chain
copyOpts.private-key
]) copies;
in unique (concatMap (i: unique i) copy-paths);
in (unique (concatMap (i: unique i) copy-paths)) ++ [
"d \"${cfg.challenge-path}\" 755 acme nginx - -"
];
services = concatMapAttrs (domain: domainOpts:
concatMapAttrs (copy: copyOpts: let

View File

@ -55,6 +55,8 @@ let
in {
options.fudo.backplane = with types; {
enable = mkEnableOption "Enable backplane (jabber) server on this host.";
client-hosts = mkOption {
type = attrsOf (submodule clientHostOpts);
description = "List of backplane client options.";
@ -67,7 +69,7 @@ in {
default = {};
};
backplane-host = mkOption {
backplane-hostname = mkOption {
type = types.str;
description = "Hostname of the backplane XMPP server.";
};

View File

@ -86,7 +86,7 @@ in {
partOf = [ "backplane-dns.target" ];
requires = cfg.required-services ++ [ "postgresql.service" ];
environment = {
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = backplane-cfg.backplane-host;
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = backplane-cfg.backplane-hostname;
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane-role.role;
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane-role.password-file;
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.database.host;

View File

@ -2,13 +2,13 @@
with lib;
{
config = mkIf config.fudo.jabber.enable {
config = mkIf config.fudo.backplane.enable {
fudo = let
cfg = config.fudo.backplane;
hostname = config.instance.hostname;
backplane-server = cfg.backplane-host;
backplane-server = cfg.backplane-hostname;
generate-auth-file = name: files: let
make-entry = name: passwd-file:
@ -40,6 +40,8 @@ with lib;
};
jabber = {
enable = true;
environment = {
FUDO_HOST_PASSWD_FILE =
secrets.backplane-host-auth.target-file;

View File

@ -21,6 +21,18 @@ in {
example = "My Fancy Chat Site";
};
user = mkOption {
type = str;
description = "System user as which to run the server.";
default = "mattermost";
};
group = mkOption {
type = str;
description = "System group as which to run the server.";
default = "mattermost";
};
smtp = {
server = mkOption {
type = str;
@ -111,8 +123,6 @@ in {
};
mattermost-config-file-template =
pkgs.writeText "mattermost-config.json.template" (builtins.toJSON modified-config);
mattermost-user = "mattermost";
mattermost-group = "mattermost";
generate-mattermost-config = target: template: smtp-passwd-file: db-passwd-file:
pkgs.writeScript "mattermost-config-generator.sh" ''
@ -124,13 +134,12 @@ in {
in {
users = {
users = {
${mattermost-user} = {
${cfg.user} = {
isSystemUser = true;
group = mattermost-group;
};
};
groups = { ${mattermost-group} = { members = [ mattermost-user ]; }; };
groups.${cfg.group}.members = [ cfg.user ];
};
fudo.system.services.mattermost = {
@ -146,54 +155,25 @@ in {
cfg.database.password-file}
cp ${cfg.smtp.password-file} ${cfg.state-directory}/config/config.json
cp -uRL ${pkg}/client ${cfg.state-directory}
chown ${mattermost-user}:${mattermost-group} ${cfg.state-directory}/client
chown ${cfg.user}:${cfg.group} ${cfg.state-directory}/client
chmod 0750 ${cfg.state-directory}/client
'';
execStart = "${pkg}/bin/mattermost";
workingDirectory = cfg.state-directory;
user = mattermost-user;
group = mattermost-group;
user = cfg.user;
group = cfg.group;
};
systemd = {
tmpfiles.rules = [
"d ${cfg.state-directory} 0750 ${mattermost-user} ${mattermost-group} - -"
"d ${cfg.state-directory}/config 0750 ${mattermost-user} ${mattermost-group} - -"
"d ${cfg.state-directory} 0750 ${cfg.user} ${cfg.group} - -"
"d ${cfg.state-directory}/config 0750 ${cfg.user} ${cfg.group} - -"
"L ${cfg.state-directory}/bin - - - - ${pkg}/bin"
"L ${cfg.state-directory}/fonts - - - - ${pkg}/fonts"
"L ${cfg.state-directory}/i18n - - - - ${pkg}/i18n"
"L ${cfg.state-directory}/templates - - - - ${pkg}/templates"
];
# services.mattermost = {
# description = "Mattermost Chat Server";
# wantedBy = [ "multi-user.target" ];
# after = [ "network.target" ];
# preStart = ''
# ${generate-mattermost-config
# mattermost-config-target
# mattermost-config-file-template
# cfg.smtp.password-file
# cfg.database.password-file}
# cp ${cfg.smtp.password-file} ${cfg.state-directory}/config/config.json
# cp -uRL ${pkg}/client ${cfg.state-directory}
# chown ${mattermost-user}:${mattermost-group} ${cfg.state-directory}/client
# chmod 0750 ${cfg.state-directory}/client
# '';
# serviceConfig = {
# PermissionsStartOnly = true;
# ExecStart = "${pkg}/bin/mattermost";
# WorkingDirectory = cfg.state-directory;
# Restart = "always";
# RestartSec = "10";
# LimitNOFILE = "49152";
# User = mattermost-user;
# Group = mattermost-group;
# };
# };
};
services.nginx = {

View File

@ -76,7 +76,7 @@ in {
"${cfg.user}" = {
isSystemUser = true;
createHome = true;
home = "/var/home/${cfg.user}";
home = "/run/home/${cfg.user}";
group = cfg.user;
};
};
@ -90,8 +90,8 @@ in {
systemd = {
tmpfiles.rules = [
"d /var/home 755 root - - -"
"d /var/home/${cfg.user} 700 ${cfg.user} - - -"
"d /run/home 755 root - - -"
"d /run/home/${cfg.user} 700 ${cfg.user} - - -"
];
timers.backplane-dns-client = {

View File

@ -4,6 +4,8 @@ with lib;
let
hostname = config.instance.hostname;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
siteOpts = { ... }: with types; {
options = {
enableACME = mkOption {
@ -45,30 +47,34 @@ let
loglevel = cfg.log-level;
access_rules = {
c2s = { allow = "all"; };
announce = { allow = "admin"; };
configure = { allow = "admin"; };
pubsub_createnode = { allow = "local"; };
c2s.allow = "all";
announce.allow = "admin";
configure.allow = "admin";
pubsub_createnode.allow = "admin";
};
acl = {
admin = {
acl.admin = {
user = concatMap
(admin: map (site: "${admin}@${site}")
(attrNames cfg.sites))
cfg.admins;
};
};
hosts = attrNames cfg.sites;
listen = map (ip: {
# By default, listen on all ips
listen = let
common = {
port = cfg.port;
module = "ejabberd_c2s";
ip = ip;
starttls = true;
starttls_required = true;
}) cfg.listen-ips;
};
in
if (cfg.listen-ips != null) then
map (ip: { ip = ip; } // common)
cfg.listen-ips
else [ common ];
certfiles = concatMapAttrsToList
(site: siteOpts:
@ -86,27 +92,33 @@ let
in pkgs.writeText "ejabberd.config.yml.template" config-file;
enter-secrets = template: secrets: target: let
secret-readers = concatStringsSep "\n"
(mapAttrsToList
(secret: file: "${secret}=$(cat ${file})")
secrets);
secret-swappers = map
(secret: "sed s/${secret}/\$${secret}/g")
(attrNames secrets);
secrets;
swapper = concatStringsSep " | " secret-swappers;
in pkgs.writeShellScript "ejabberd-generate-config.sh" ''
[ -f \$${target} ] && rm -f ${target}
echo "Copying from ${template} to ${target}"
touch ${target}
chmod go-rwx ${target}
chmod u+rw ${target}
cat ${template} | ${swapper} > ${target}
echo "Copying from ${template} to ${target} completed"
'';
cfg = config.fudo.jabber;
log-dir = "${cfg.state-directory}/logs";
spool-dir = "${cfg.state-directory}/spool";
in {
options.fudo.jabber = with types; {
enable = mkEnableOption "Enable ejabberd server.";
listen-ips = mkOption {
type = listOf str;
type = nullOr (listOf str);
description = "IPs on which to listen for Jabber connections.";
default = null;
};
port = mkOption {
@ -147,7 +159,7 @@ in {
config-file = mkOption {
type = str;
description = "Location at which to generate the configuration file.";
default = "/run/ejabberd/ejabberd.yaml";
default = "/run/ejabberd/config/ejabberd.yaml";
};
log-level = mkOption {
@ -160,6 +172,12 @@ in {
default = 3;
};
state-directory = mkOption {
type = str;
description = "Path at which to store ejabberd state.";
default = "/var/lib/ejabberd";
};
environment = mkOption {
type = attrsOf str;
description = "Environment variables to set for the ejabberd daemon.";
@ -187,27 +205,38 @@ in {
};
}) cfg.sites;
system = {
services.ejabberd-config-generator = let
config-generator =
enter-secrets config-file-template cfg.secret-files cfg.config-file;
secrets.host-secrets.${hostname}.ejabberd-password-env = let
env-vars = mapAttrsToList (secret: file: "${secret}=${readFile file}")
cfg.secret-files;
in {
script = "${config-generator}";
readWritePaths = [ config-dir ];
workingDirectory = config-dir;
source-file = pkgs.writeText "ejabberd-password-env"
(concatStringsSep "\n" env-vars);
target-file = "/run/ejabberd/environment/config-passwords.env";
user = cfg.user;
description = "Generate ejabberd config file with necessary passwords.";
postStart = ''
chown ${cfg.user} ${cfg.config-file}
chmod 0400 ${cfg.config-file}
'';
};
};
# system = {
# services.ejabberd-config-generator = let
# config-generator =
# enter-secrets config-file-template cfg.secret-files cfg.config-file;
# in {
# script = "${config-generator}";
# readWritePaths = [ config-dir ];
# workingDirectory = config-dir;
# user = cfg.user;
# description = "Generate ejabberd config file with necessary passwords.";
# postStart = ''
# chown ${cfg.user}:${cfg.group} ${cfg.config-file}
# chmod 0400 ${cfg.config-file}
# '';
# };
# };
};
systemd = {
tmpfiles.rules = [
"d '${config-dir}' 0700 ${cfg.user} ${cfg.group} - -'"
"d ${config-dir} 0700 ${cfg.user} ${cfg.group} - -"
"d ${cfg.state-directory} 0750 ${cfg.user} ${cfg.group} - -"
];
services = {
@ -216,6 +245,22 @@ in {
requires = [ "ejabberd-config-generator.service" ];
environment = cfg.environment;
};
ejabberd-config-generator = let
config-generator =
enter-secrets config-file-template (attrNames cfg.secret-files) cfg.config-file;
in {
description = "Generate ejabberd config file containing passwords.";
serviceConfig = {
User = cfg.user;
ExecStart = "${config-generator}";
ExecStartPost = pkgs.writeShellScript "protect-ejabberd-config.sh" ''
chown ${cfg.user}:${cfg.group} ${cfg.config-file}
chmod 0400 ${cfg.config-file}
'';
EnvironmentFile = host-secrets.ejabberd-password-env.target-file;
};
};
};
};
@ -226,6 +271,9 @@ in {
group = cfg.group;
configFile = cfg.config-file;
logsDir = log-dir;
spoolDir = spool-dir;
};
};
}

View File

@ -364,12 +364,6 @@ in {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcFrontendConfig" ];
olcDatabase = "{-1}frontend";
olcAccess = makeAccess {
"*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"*" = "none";
};
};
};
};
"olcDatabase={0}config" = {
@ -378,7 +372,6 @@ in {
olcDatabase = "{0}config";
olcAccess = makeAccess {
"*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"*" = "none";
};
};
@ -392,36 +385,33 @@ in {
# olcRootDN = "cn=admin,${cfg.base}";
# olcRootPW = FIXME; # NOTE: this should be hashed...
olcDbDirectory = "${cfg.state-directory}/database";
olcDbIndex = [ "objectClass eq" "uid eq" ];
olcDbIndex = [ "objectClass eq" "uid pres,eq" ];
olcAccess = makeAccess {
"attrs=userPassword,shadowLastChange" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
# "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=auth_reader,${cfg.base}" = "read";
"dn.exact=cn=replicator,${cfg.base}" = "read";
"self" = "write";
"*" = "auth";
};
"dn=cn=admin,ou=groups,${cfg.base}" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"users" = "read";
"*" = "none";
# "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"anonymous" = "auth";
"dn.children=dc=fudo,dc=org" = "read";
};
"dn.subtree=ou=groups,${cfg.base} attrs=memberUid" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.regex=cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" = "write";
"users" = "read";
"*" = "none";
# "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
# "dn.regex=cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" = "write";
"anonymous" = "auth";
"dn.children=dc=fudo,dc=org" = "read";
};
"dn.subtree=ou=members,${cfg.base} attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"dn.exact=cn=user_db_reader,${cfg.base}" = "read";
"users" = "read";
"*" = "none";
# "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"anonymous" = "auth";
"dn.children=dc=fudo,dc=org" = "read";
};
"*" = {
"dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"users" = "read";
"*" = "none";
# "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage";
"anonymous" = "auth";
"dn.children=dc=fudo,dc=org" = "read";
};
};
};

View File

@ -1,8 +1,28 @@
{ config, lib, pkgs, ... }:
with lib;
{
let
cfg = config.fudo.ssh;
hostname = config.instance.hostname;
in {
options.fudo.ssh = with types; {
whitelistIPs = mkOption {
type = listOf str;
description =
"IPs to which fail2ban rules will not apply (on top of local networks).";
default = [];
};
};
config = {
services.fail2ban = {
ignoreIP =
config.instance.local-networks ++ cfg.whitelistIPs;
maxretry = if config.fudo.hosts.${hostname}.hardened then 3
else 20;
};
programs.ssh.knownHosts = let
keyed-hosts =
filterAttrs (h: o: o.ssh-pubkeys != [])