From 541890c08f1ae0473d82b04fd193d3226f2381f3 Mon Sep 17 00:00:00 2001 From: niten Date: Fri, 10 Dec 2021 18:47:50 -0800 Subject: [PATCH] Got ldap on nutboy3 and jabber on legatus --- lib/fudo/acme-certs.nix | 25 +++++-- lib/fudo/backplane/common.nix | 4 +- lib/fudo/backplane/dns.nix | 2 +- lib/fudo/backplane/jabber.nix | 6 +- lib/fudo/chat.nix | 134 +++++++++++++++------------------- lib/fudo/client/dns.nix | 6 +- lib/fudo/jabber.nix | 130 ++++++++++++++++++++++----------- lib/fudo/ldap.nix | 40 ++++------ lib/fudo/ssh.nix | 22 +++++- 9 files changed, 211 insertions(+), 158 deletions(-) diff --git a/lib/fudo/acme-certs.nix b/lib/fudo/acme-certs.nix index f39a5e8..aa25baf 100644 --- a/lib/fudo/acme-certs.nix +++ b/lib/fudo/acme-certs.nix @@ -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 diff --git a/lib/fudo/backplane/common.nix b/lib/fudo/backplane/common.nix index a1d3e95..cf9f094 100644 --- a/lib/fudo/backplane/common.nix +++ b/lib/fudo/backplane/common.nix @@ -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."; }; diff --git a/lib/fudo/backplane/dns.nix b/lib/fudo/backplane/dns.nix index 6c97556..c584c94 100644 --- a/lib/fudo/backplane/dns.nix +++ b/lib/fudo/backplane/dns.nix @@ -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; diff --git a/lib/fudo/backplane/jabber.nix b/lib/fudo/backplane/jabber.nix index 8f6e988..f220090 100644 --- a/lib/fudo/backplane/jabber.nix +++ b/lib/fudo/backplane/jabber.nix @@ -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; diff --git a/lib/fudo/chat.nix b/lib/fudo/chat.nix index b885373..8e19f70 100644 --- a/lib/fudo/chat.nix +++ b/lib/fudo/chat.nix @@ -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,62 +155,33 @@ 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 = { enable = true; appendHttpConfig = '' - proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; - ''; + proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; + ''; virtualHosts = { "${cfg.hostname}" = { @@ -212,48 +192,48 @@ in { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' - client_max_body_size 50M; - proxy_set_header Connection ""; - 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 $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Frame-Options SAMEORIGIN; - proxy_buffers 256 16k; - proxy_buffer_size 16k; - proxy_read_timeout 600s; - proxy_cache mattermost_cache; - proxy_cache_revalidate on; - proxy_cache_min_uses 2; - proxy_cache_use_stale timeout; - proxy_cache_lock on; - proxy_http_version 1.1; - ''; + client_max_body_size 50M; + proxy_set_header Connection ""; + 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 $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_cache mattermost_cache; + proxy_cache_revalidate on; + proxy_cache_min_uses 2; + proxy_cache_use_stale timeout; + proxy_cache_lock on; + proxy_http_version 1.1; + ''; }; locations."~ /api/v[0-9]+/(users/)?websocket$" = { proxyPass = "http://127.0.0.1:8065"; extraConfig = '' - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - client_max_body_size 50M; - 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 $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Frame-Options SAMEORIGIN; - proxy_buffers 256 16k; - proxy_buffer_size 16k; - client_body_timeout 60; - send_timeout 300; - lingering_timeout 5; - proxy_connect_timeout 90; - proxy_send_timeout 300; - proxy_read_timeout 90s; - ''; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 50M; + 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 $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + client_body_timeout 60; + send_timeout 300; + lingering_timeout 5; + proxy_connect_timeout 90; + proxy_send_timeout 300; + proxy_read_timeout 90s; + ''; }; }; }; diff --git a/lib/fudo/client/dns.nix b/lib/fudo/client/dns.nix index 103a763..d9751d3 100644 --- a/lib/fudo/client/dns.nix +++ b/lib/fudo/client/dns.nix @@ -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 = { diff --git a/lib/fudo/jabber.nix b/lib/fudo/jabber.nix index 3905e76..218a37a 100644 --- a/lib/fudo/jabber.nix +++ b/lib/fudo/jabber.nix @@ -3,6 +3,8 @@ with lib; let hostname = config.instance.hostname; + + host-secrets = config.fudo.secrets.host-secrets.${hostname}; siteOpts = { ... }: with types; { options = { @@ -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 = { - user = concatMap - (admin: map (site: "${admin}@${site}") - (attrNames cfg.sites)) - cfg.admins; - }; + acl.admin = { + user = concatMap + (admin: map (site: "${admin}@${site}") + (attrNames cfg.sites)) + cfg.admins; }; hosts = attrNames cfg.sites; - listen = map (ip: { - port = cfg.port; - module = "ejabberd_c2s"; - ip = ip; - starttls = true; - starttls_required = true; - }) cfg.listen-ips; + # By default, listen on all ips + listen = let + common = { + port = cfg.port; + module = "ejabberd_c2s"; + starttls = true; + starttls_required = true; + }; + 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; - 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.config-file} - chmod 0400 ${cfg.config-file} - ''; - }; + secrets.host-secrets.${hostname}.ejabberd-password-env = let + env-vars = mapAttrsToList (secret: file: "${secret}=${readFile file}") + cfg.secret-files; + in { + source-file = pkgs.writeText "ejabberd-password-env" + (concatStringsSep "\n" env-vars); + target-file = "/run/ejabberd/environment/config-passwords.env"; + user = cfg.user; }; + + # 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; }; }; } diff --git a/lib/fudo/ldap.nix b/lib/fudo/ldap.nix index ebb4bab..1b930b7 100644 --- a/lib/fudo/ldap.nix +++ b/lib/fudo/ldap.nix @@ -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"; }; }; }; diff --git a/lib/fudo/ssh.nix b/lib/fudo/ssh.nix index 3f1f965..a56275d 100644 --- a/lib/fudo/ssh.nix +++ b/lib/fudo/ssh.nix @@ -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 != [])