From c40e56d652682229fad49fbc9faed82e85b9e143 Mon Sep 17 00:00:00 2001 From: Julian Jacobi Date: Wed, 30 Oct 2024 19:21:09 +0100 Subject: [PATCH] Unfinished znuny deployment, mainly mail io missing --- nixos/.sops.yaml | 10 ++ nixos/hosts.nix | 2 +- nixos/hosts/mail/mail.nix | 14 +- nixos/hosts/tickets/default.nix | 62 ++++++++ nixos/hosts/tickets/secrets.yaml | 48 ++++++ nixos/hosts/tickets/znuny.nix | 260 +++++++++++++++++++++++++++++++ 6 files changed, 388 insertions(+), 8 deletions(-) create mode 100644 nixos/hosts/tickets/default.nix create mode 100644 nixos/hosts/tickets/secrets.yaml create mode 100644 nixos/hosts/tickets/znuny.nix diff --git a/nixos/.sops.yaml b/nixos/.sops.yaml index 84ba2b11..43c666cd 100644 --- a/nixos/.sops.yaml +++ b/nixos/.sops.yaml @@ -5,6 +5,7 @@ keys: - &admin_keepass age13yl99pyktjyssdm487pa5ucm4rxcrdrt4lq8qk2vkvdwyfhcvahqs4e6cw - &host_tel age1glkmsh6pex9g5v95vwx78a8xksmnkvsu7ccnhxzu09yvnfnjudls3lfkru - &host_mail age1zcj3dt7uc3gc3kyt6l7m86qjzm9vlgq7kcsm9wh9gank6rqff4gqwrtzpa + - &host_tickets age1d3xrfzn6uht3ls7sg68emz33wxxwytmjjd7y4rv3zt9e366dealqkex4j8 creation_rules: - path_regex: hosts/tel/.* @@ -25,3 +26,12 @@ creation_rules: - *admin_hexchen - *admin_jayjay - *host_mail + - path_regex: hosts/tickets/.* + key_groups: + - pgp: +# - *admin_n0emis + age: + - *admin_keepass + - *admin_hexchen + - *admin_jayjay + - *host_tickets diff --git a/nixos/hosts.nix b/nixos/hosts.nix index 64e5b0ea..07bbd30f 100644 --- a/nixos/hosts.nix +++ b/nixos/hosts.nix @@ -4,7 +4,7 @@ netbox = { ... }: {}; sso = { ... }: {}; tel = { ... }: {}; - rt = { ... }: {}; + tickets = { ... }: {}; loudness-player = { ... }: { }; } diff --git a/nixos/hosts/mail/mail.nix b/nixos/hosts/mail/mail.nix index 438bcfd5..963e4e2e 100644 --- a/nixos/hosts/mail/mail.nix +++ b/nixos/hosts/mail/mail.nix @@ -24,6 +24,8 @@ in { "c3voc.de" ]; + debug = true; + # Use Let's Encrypt certificates. Note that this needs to set up a stripped # down nginx and opens port 80. certificateScheme = "acme-nginx"; @@ -39,17 +41,17 @@ in { "muenchen" = "muenchen@lists.c3voc.de"; "studios" = "studios@lists.c3voc.de"; "voc" = "voc@lists.c3voc.de"; - } // lib.genAttrs [ # rt related addresses - "rt" - "rt-comment" - "rt-test" - ] (addr: "${addr}@rt.c3voc.de"); + }; # whitelist SPF checks from mng (for now) policydSPFExtraConfig = '' HELO_Whitelist = mng.c3voc.de skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1,185.106.84.49,2001:67c:20a0:e::179 ''; + + loginAccounts."znuny@c3voc.de" = { + hashedPassword = "$2b$05$KSWvSJXyURjzQjXfSIzPTeDTZ0lXjj2.z.t6QT8lL32q4UBwZQAQ6"; + }; }; sops.secrets.aliases = {}; @@ -66,8 +68,6 @@ in { networks = [ "127.0.0.1/32" "[::1]/128" - "185.106.84.19/32" # rt.c3voc.de uses mail.c3cov.de as mail relay - "[2001:67c:20a0:e::19]/128" # also rt.c3voc.de ]; }; diff --git a/nixos/hosts/tickets/default.nix b/nixos/hosts/tickets/default.nix new file mode 100644 index 00000000..860979e3 --- /dev/null +++ b/nixos/hosts/tickets/default.nix @@ -0,0 +1,62 @@ +{ config, lib, modulesPath, pkgs, ... }: + +with lib; + +let +in +{ + imports = [ + "${modulesPath}/virtualisation/proxmox-image.nix" + + ./znuny.nix + ]; + config = { + system.stateVersion = "23.11"; # do not touch + + sops.secrets."znuny_mail_password".owner = "znuny"; + + networking.useDHCP = false; + networking.interfaces.eth0.ipv4.addresses = [{ + address = "185.106.84.19"; + prefixLength = 26; + }]; + networking.interfaces.eth0.ipv6.addresses = [{ + address = "2001:67c:20a0:e::19"; + prefixLength = 64; + }]; + networking.defaultGateway = "185.106.84.1"; + networking.defaultGateway6 = "2001:67c:20a0:e::1"; + networking.nameservers = [ + "9.9.9.9" + "1.1.1.1" + ]; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + security.acme.acceptTerms = true; + security.acme.defaults.email = "voc@c3voc.de"; + + services.znuny.enable = true; + services.znuny.prefork = 4; + services.znuny.extraConfig = '' + $Self->{FQDN} = '${config.networking.fqdn}'; + + $Self->{AdminEmail} = 'voc@c3voc.de'; + + $Self->{Organization} = 'c3voc'; + + $Self->{'SendmailModule'} = 'Kernel::System::Email::SMTPTLS'; + $Self->{'SendmailModule::Host'} = 'mail.c3voc.de'; + $Self->{'SendmailModule::Port'} = '565'; + $Self->{'SendmailModule::AuthUser'} = 'znuny'; + use File::Slurper 'read_text'; + $Self->{'SendmailModule::AuthPassword'} = read_text('${config.sops.secrets."znuny_mail_password".path}'); + ''; + + services.nginx.enable = true; + services.nginx.virtualHosts."tickets.c3voc.de" = { + forceSSL = true; + enableACME = true; + } // config.services.znuny.nginxVirtualHostConfig; + }; +} diff --git a/nixos/hosts/tickets/secrets.yaml b/nixos/hosts/tickets/secrets.yaml new file mode 100644 index 00000000..4f144cef --- /dev/null +++ b/nixos/hosts/tickets/secrets.yaml @@ -0,0 +1,48 @@ +znuny_mail_password: ENC[AES256_GCM,data:QdOmah7eqq8L155k1Hn7vmeYouXMZJPD9ruYkaH4yVw=,iv:thuDkijzvN61wCsNl/h4DJWf+jPj37B3Nth3no2J/yw=,tag:MWAiD/MWjkkooy51MCFmrQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age13yl99pyktjyssdm487pa5ucm4rxcrdrt4lq8qk2vkvdwyfhcvahqs4e6cw + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXOGR0VkVFR3UzVTljaFhW + c25ZZW5QaUdWVFY4QWtiUlpPRlY1NEMzdEFnCjJpY2NGbXFYODV4bUN5SDBwUno3 + UVlVQThxOXJrZm5YMndrWWJiOXJURDQKLS0tIDJnM2RxL1dLUG84ekF3ZWgxSFNq + ZXRCZzl1dloxR3RqeWNYSm9uNEQ3TzQK8Id5mNLRRHp79Lm0Fd3GywYouRPmkHg1 + mbphdLN4WnKYE3YhUFc5vZwXSLFHQ9yMFddB23W6XpXVf6lvqDPr/g== + -----END AGE ENCRYPTED FILE----- + - recipient: age1wvtkhug4q7fcs7wz03kpn77ruqkkwp2xqq30npv4287wtf3w8ukq370vre + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1Mzk3bHh1dmxsSU9DMDF3 + ZjJsWlI3SVlZbk1IdU9KZ1BSN2NDVE5LSm1RCjFQZE5pZ0xUamg0SVNCMlRCTFV1 + WVZTUldIMkZmaTRtSC9QRFBOVTAzUVkKLS0tIDlMVFpicDdMdmtBUVlqckdCVitK + Z00zdGRXcG5paEgrQmtWbWFvN1U1SHMKTMm8uobpBugaL6V2AjrEcTGHUoDRk10E + mV3YA83H7BvmUgZgGf5wfldJSyexh69FPQni9LJPY/KOoqNPmCUD6Q== + -----END AGE ENCRYPTED FILE----- + - recipient: age1yyxdtt7lpcm9hr0y76g559yq4uqz8e8hjc2fzqtwnhctsj99fp6sf3ksl6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJMVVSOGRFcXpvcHZLbktP + KzVsT29WelhybmI1dnBBTk52eTVNVEd1alFJCmU2LzFrUUNzc3E0MS9KZ3FFM1V6 + ajJpd1NjYkFLWFcrc1dtVWFSWmhJVkkKLS0tIDBSQUJMYmt2Nnkwb24rUzE3ckg4 + VnlsaGFYUGM1ZDUzVFcrRnpQUmkra28Kyp19yfsvXyhnAypoPGf9QRMTtqfaNvJ9 + ECmx3PRgnYA49pIAZjQRd47h1AQZkNf8VIEIETjgQraBxlDk1c2/DQ== + -----END AGE ENCRYPTED FILE----- + - recipient: age1d3xrfzn6uht3ls7sg68emz33wxxwytmjjd7y4rv3zt9e366dealqkex4j8 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwWlRVaW01a2hYVUlVV09R + QXBjWHlYeGNuOTVCUXZHeGhyVGRQQkYxQm40CjVMMEsvcmgrL1BrZVNpZEpJamFH + M0dtQ01VSS91U2tvK3ozQ1dTTU42c3MKLS0tIGQyN2o5NkJDa0Z5aG9nNUp4NEZx + US9NUEZYSjNKMi9VdkU2ckpWdm9oNVkK6Vospcd2YXmq33rY4bCIC1I9TRq49lSJ + Vsdl5vVf5kWoUtLQywy2AgvzSUS8l4O3bKiFo7cCBYhTKwVEqA7BFw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2024-10-28T19:07:15Z" + mac: ENC[AES256_GCM,data:VDsnXCmlX0TgW1kYmkR9nHw2kyQe+MeHCWDxYKKnDqUbYu7p4FfczdgY9Xp+nJLij4OYQhED7nz633ZLYNRnSMFZTOMTC50qlWdIJNqchfZbjSgb6fpQ88SCy5wesHIL7hhnYV20+4n6w6XPLmtmkUZQd23TEykaz3V09Spi5u8=,iv:2Dk4ex2grWoJpGYJhK1drv/749Z4o2vbjdFgcsR5sb0=,tag:PNoxZpW8Z62HPWl0CcsbyQ==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.8.1 diff --git a/nixos/hosts/tickets/znuny.nix b/nixos/hosts/tickets/znuny.nix new file mode 100644 index 00000000..82032ec5 --- /dev/null +++ b/nixos/hosts/tickets/znuny.nix @@ -0,0 +1,260 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + perlModules = with pkgs.perlPackages; [ + Moo + TextCSV_XS + YAMLLibYAML + #ModPerlUtil + MailIMAPClient + JSONXS + EncodeHanExtra + CryptEksblowfish + DataUUID + DateTimeTimeZone + DateTime + DBDPg + DBI + HashMerge + NetLDAP + NetDNS + TemplateToolkit + XMLLibXML + XMLLibXSLT + XMLParser + SpreadsheetXLSX + PackageStash + JavaScriptMinifierXS + CSSMinifierXS + iCalParser + namespaceautoclean + AuthenSASL + CryptJWT + CryptOpenSSLX509 + DBDmysql + IOSocketSSL + NTLM + FileSlurper + ]; + pkg = pkgs.stdenv.mkDerivation rec { + pname = "znuny"; + version = "7.1.3"; + + src = pkgs.fetchFromGitHub { + owner = "znuny"; + repo = "Znuny"; + rev = "rel-${builtins.replaceStrings ["."] ["_"] version}"; + hash = "sha256-BWJ6I30yLvBSrwm4ADFVXbSdhSUArrVae8ThJLycoow="; + }; + + nativeBuildInputs = with pkgs; [ + makeWrapper + ]; + + buildInputs = with pkgs; [ + perlPackages.perl + ] ++ perlModules; + + installPhase = '' + mkdir $out + cp --recursive ./* $out/ + mv $out/Kernel/Config $out/Kernel/Config.dist + mv $out/var/cron $out/var/cron.dist + ln -fs /var/lib/znuny/config/Config $out/Kernel/Config + ln -fs /var/lib/znuny/config/Config.pm $out/Kernel/Config.pm + ln -fs /var/lib/znuny/var/tmp $out/var/tmp + ln -fs /var/lib/znuny/var/run $out/var/run + ln -fs /var/lib/znuny/var/cron $out/var/cron + ln -fs /var/lib/znuny/var/httpd/htdocs/js/js-cache $out/var/httpd/htdocs/js/js-cache + ln -fs /var/lib/znuny/var/httpd/htdocs/skins/Agent/default/css-cache $out/var/httpd/htdocs/skins/Agent/default/css-cache + rm -rf $out/var/log + ln -fs /var/log/znuny/ $out/var/log + ''; + + postFixup = '' + for i in $(find $out/bin/ -type f); do + wrapProgram $i \ + --prefix PERL5LIB : "${pkgs.perlPackages.makeFullPerlPath perlModules}" + done + wrapProgram $out/scripts/MigrateToZnuny7_1.pl \ + --prefix PERL5LIB : "${pkgs.perlPackages.makeFullPerlPath perlModules}" + ''; + }; + + znunyConfig = pkgs.writeText "znuny-config.pm" '' + package Kernel::Config; + + use strict; + use warnings; + use utf8; + + sub Load { + my $Self = shift; + $Self->{DatabaseDSN} = "DBI:Pg:dbname=znuny;"; + + $Self->{Home} = '${pkg}'; + + $Self->{SecureMode} = 1; + + ${cfg.extraConfig} + + return 1; + } + + use Kernel::Config::Defaults; # import Translatable() + use parent qw(Kernel::Config::Defaults); + + 1; + ''; + + znunyConsole = pkgs.writeScriptBin "znuny" '' + sudo -u ${cfg.user} ${pkg}/bin/znuny.Console.pl $@ + ''; + + cfg = config.services.znuny; +in +{ + options.services.znuny = { + enable = mkEnableOption "Enable Znuny web-based ticketing system"; + unixSocket = mkOption { + type = types.str; + default = "/run/znuny.sock"; + }; + user = mkOption { + type = types.str; + default = "znuny"; + readOnly = true; + }; + group = mkOption { + type = types.str; + default = "znuny"; + readOnly = true; + }; + prefork = mkOption { + type = types.ints.positive; + default = 1; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + }; + + nginxVirtualHostConfig = mkOption { + type = types.attrs; + readOnly = true; + default = { + root = "${pkg}/var/httpd/htdocs"; + locations."/favicon.ico".extraConfig = '' + access_log off; + log_not_found off; + ''; + locations."/znuny-web/".alias = "${pkg}/var/httpd/htdocs/"; + locations."~ ^/znuny/(.*\\.pl)(/.*)?$".extraConfig = '' + gzip off; + fastcgi_pass unix:${cfg.unixSocket}; + fastcgi_index index.pl; + fastcgi_param SCRIPT_FILENAME ${pkg}/bin/cgi-bin/$1; + + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + + fastcgi_param HTTPS $https if_not_empty; + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + services.postgresql.enable = true; + services.postgresql.ensureUsers = [{ + name = "znuny"; + ensureDBOwnership = true; + }]; + services.postgresql.ensureDatabases = [ + "znuny" + ]; + + environment.systemPackages = [ + znunyConsole + ]; + + users.users."${cfg.user}" = { + isSystemUser = true; + group = cfg.group; + }; + users.users."nginx".extraGroups = [ cfg.group ]; + users.groups."${cfg.group}" = {}; + + systemd.tmpfiles.rules = [ + "d /var/lib/znuny/config 0755 ${cfg.user} ${cfg.group}" + "d /var/lib/znuny/var/tmp 0755 ${cfg.user} ${cfg.group}" + "d /var/lib/znuny/var/run 0755 ${cfg.user} ${cfg.group}" + "d /var/lib/znuny/var/cron 0755 ${cfg.user} ${cfg.group}" + "d /var/lib/znuny/var/httpd/htdocs/js/js-cache 0755 ${cfg.user} ${cfg.group}" + "d /var/lib/znuny/var/httpd/htdocs/skins/Agent/default/css-cache 0755 ${cfg.user} ${cfg.group}" + "d /var/log/znuny 0755 ${cfg.user} ${cfg.group}" + ]; + + services.fcgiwrap.instances."znuny".socket.user = "nginx"; + services.fcgiwrap.instances."znuny".socket.group = "nignx"; + services.fcgiwrap.instances."znuny".socket.type = "unix"; + services.fcgiwrap.instances."znuny".socket.address = cfg.unixSocket; + services.fcgiwrap.instances."znuny".process.user = cfg.user; + services.fcgiwrap.instances."znuny".process.group = cfg.group; + services.fcgiwrap.instances."znuny".process.prefork = cfg.prefork; + + systemd.services."znuny-daemon" = { + path = [ pkgs.postgresql ]; + wantedBy = [ "multi-user.target" ]; + after = [ "postgresql.service" "systemd-tmpfiles-resetup.service" ]; + requires = [ "postgresql.service" ]; + preStart = '' + rm -rf /var/lib/znuny/Config + cp --recursive --force --no-preserve=mode ${pkg}/Kernel/Config.dist /var/lib/znuny/config/Config + cp --force ${znunyConfig} --no-preserve=mode /var/lib/znuny/config/Config.pm + cp --force --no-preserve=mode ${pkg}/var/cron.dist/* /var/lib/znuny/var/cron/ + + TABLES=$(psql znuny --csv -t -c "select count(*) from information_schema.tables where table_schema = 'public';" | tr -d '[:blank:]') + echo $TABLES + if [ "$TABLES" -eq "0" ]; then + echo "Empty database, start init" + psql znuny < ${pkg}/scripts/database/schema.postgresql.sql + psql znuny < ${pkg}/scripts/database/initial_insert.postgresql.sql + psql znuny < ${pkg}/scripts/database/schema-post.postgresql.sql + else + echo "Database allready initialized, start migration" + ${pkg}/scripts/MigrateToZnuny7_1.pl + fi + + ${pkg}/bin/znuny.Console.pl Maint::Config::Rebuild + ''; + serviceConfig = { + ExecStart = "${pkg}/bin/znuny.Daemon.pl start --foreground"; + User = cfg.user; + TimeoutStartSec = "300"; + }; + }; + }; +}