diff --git a/configuration/default.nix b/configuration/default.nix index c9c71ec..239f9f6 100644 --- a/configuration/default.nix +++ b/configuration/default.nix @@ -17,7 +17,7 @@ ./services/backups.nix ./services/battery-manager.nix - ./services/conduit.nix + ./services/conduit ./services/crowdsec.nix ./services/foundryvtt.nix ./services/gitea.nix diff --git a/configuration/services/conduit.nix b/configuration/services/conduit/default.nix similarity index 62% rename from configuration/services/conduit.nix rename to configuration/services/conduit/default.nix index 4e53241..c3803f4 100644 --- a/configuration/services/conduit.nix +++ b/configuration/services/conduit/default.nix @@ -1,5 +1,4 @@ { - pkgs, config, lib, ... @@ -12,6 +11,11 @@ let turn-realm = "turn.${config.services.nginx.domain}"; in { + imports = [ + ./heisenbridge.nix + ./matrix-hookshot.nix + ]; + services.matrix-conduit = { enable = true; settings.global = { @@ -40,91 +44,6 @@ in }; }; - systemd.services.heisenbridge = - let - replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret"; - registrationFile = builtins.toFile "heisenbridge-registration.yaml" ( - builtins.toJSON { - id = "heisenbridge"; - url = "http://127.0.0.1:9898"; - as_token = "@AS_TOKEN@"; - hs_token = "@HS_TOKEN@"; - rate_limited = false; - sender_localpart = "heisenbridge"; - namespaces = { - users = [ - { - regex = "@irc_.*"; - exclusive = true; - } - { - regex = "@heisenbridge:.*"; - exclusive = true; - } - ]; - aliases = [ ]; - rooms = [ ]; - }; - } - ); - - # TODO(tlater): Starting with systemd 253 it will become possible - # to do the credential setup as part of ExecStartPre/preStart - # instead. - # - # This will also make it possible to actually set caps on the - # heisenbridge process using systemd, so that we can run the - # identd process. - execScript = pkgs.writeShellScript "heisenbridge" '' - cp ${registrationFile} "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml" - chmod 600 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml - ${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_as-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml" - ${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_hs-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml" - chmod 400 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml - - ${pkgs.heisenbridge}/bin/heisenbridge \ - --config $RUNTIME_DIRECTORY/heisenbridge-registration.yaml \ - --owner @tlater:matrix.tlater.net \ - 'http://localhost:${toString cfg.settings.global.port}' - ''; - in - { - description = "Matrix<->IRC bridge"; - wantedBy = [ "multi-user.target" ]; - after = [ "conduit.service" ]; - - serviceConfig = { - Type = "simple"; - - LoadCredential = "heisenbridge:/run/secrets/heisenbridge"; - - ExecStart = execScript; - - DynamicUser = true; - RuntimeDirectory = "heisenbridge"; - RuntimeDirectoryMode = "0700"; - - RestrictNamespaces = true; - PrivateUsers = true; - ProtectHostname = true; - ProtectClock = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectKernelLogs = true; - ProtectControlGroups = true; - RestrictAddressFamilies = [ "AF_INET AF_INET6" ]; - LockPersonality = true; - RestrictRealtime = true; - ProtectProc = "invisible"; - ProcSubset = "pid"; - UMask = 77; - - # For the identd port - # CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; - # AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; - }; - }; - # Pass in the TURN secret via EnvironmentFile, not supported by # upstream module currently. # diff --git a/configuration/services/conduit/heisenbridge.nix b/configuration/services/conduit/heisenbridge.nix new file mode 100644 index 0000000..f0f7e49 --- /dev/null +++ b/configuration/services/conduit/heisenbridge.nix @@ -0,0 +1,78 @@ +{ + pkgs, + lib, + config, + ... +}: +let + conduitCfg = config.services.matrix-conduit; + matrixLib = pkgs.callPackage ./lib.nix { }; +in +{ + systemd.services.heisenbridge = + let + registration = matrixLib.writeRegistrationScript { + id = "heisenbridge"; + url = "http://127.0.0.1:9898"; + sender_localpart = "heisenbridge"; + + namespaces = { + users = [ + { + regex = "@irc_.*"; + exclusive = true; + } + { + regex = "@heisenbridge:.*"; + exclusive = true; + } + ]; + + aliases = [ ]; + rooms = [ ]; + }; + }; + in + { + description = "Matrix<->IRC bridge"; + wantedBy = [ "multi-user.target" ]; + after = [ "conduit.service" ]; + + serviceConfig = { + Type = "exec"; + + LoadCredential = "heisenbridge:/run/secrets/heisenbridge"; + + inherit (registration) ExecStartPre; + ExecStart = lib.concatStringsSep " " [ + "${lib.getExe pkgs.heisenbridge}" + "--config \${RUNTIME_DIRECTORY}/heisenbridge-registration.yaml" + "--owner @tlater:matrix.tlater.net" + "http://localhost:${toString conduitCfg.settings.global.port}" + ]; + + DynamicUser = true; + RuntimeDirectory = "heisenbridge"; + RuntimeDirectoryMode = "0700"; + + RestrictNamespaces = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET AF_INET6" ]; + LockPersonality = true; + RestrictRealtime = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + UMask = 77; + + # For the identd port + # CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + # AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + }; + }; +} diff --git a/configuration/services/conduit/lib.nix b/configuration/services/conduit/lib.nix new file mode 100644 index 0000000..ef407cf --- /dev/null +++ b/configuration/services/conduit/lib.nix @@ -0,0 +1,67 @@ +{ + lib, + writeShellScript, + formats, + replace-secret, +}: +let + replaceSecretBin = "${lib.getExe replace-secret}"; +in +{ + # Write a script that will set up the service's registration.yaml + # with secrets from systemd credentials. + # + # The credentials should be named `${id}_as-token` and + # `${id}_hs-token`. + # + # This registration file needs to be manually added to conduit by + # messaging the admin with the yaml file. + # + # TODO(tlater): Conduwuit seems to support a CLI interface for this, + # may want to migrate to that sometime. + writeRegistrationScript = + { + id, # Must be unique among all registered appservices/bots + url, # The URL on which the service listens + sender_localpart, + rate_limited ? false, + namespaces ? { + aliases = [ ]; + rooms = [ ]; + users = [ ]; + }, + extraSettings ? { }, + # The location to place the file; assumes systemd runtime dir + runtimeRegistration ? "$RUNTIME_DIRECTORY/${id}-registration.yaml", + }: + let + registrationFile = (formats.yaml { }).generate "${id}-registration.yaml" ( + { + inherit + id + url + sender_localpart + rate_limited + namespaces + ; + + as_token = "@AS_TOKEN@"; + hs_token = "@HS_TOKEN@"; + } + // extraSettings + ); + in + { + inherit runtimeRegistration; + ExecStartPre = writeShellScript "${id}-registration-setup.sh" '' + cp -f ${registrationFile} "${runtimeRegistration}" + chmod 600 "${runtimeRegistration}" + + # Write actual secrets into config + ${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_as-token" "${runtimeRegistration}" + ${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_hs-token" "${runtimeRegistration}" + + chmod 400 "${runtimeRegistration}" + ''; + }; +} diff --git a/configuration/services/conduit/matrix-hookshot.nix b/configuration/services/conduit/matrix-hookshot.nix new file mode 100644 index 0000000..6f11728 --- /dev/null +++ b/configuration/services/conduit/matrix-hookshot.nix @@ -0,0 +1,144 @@ +{ + pkgs, + lib, + config, + ... +}: +let + matrixLib = pkgs.callPackage ./lib.nix { }; + + cfg = config.services.matrix-hookshot; + conduitCfg = config.services.matrix-conduit; + + domain = conduitCfg.settings.global.server_name; + + registration = matrixLib.writeRegistrationScript { + id = "matrix-hookshot"; + url = "http://127.0.0.1:9993"; + sender_localpart = "hookshot"; + + namespaces = { + aliases = [ ]; + rooms = [ ]; + users = [ + { + regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}"; + exclusive = true; + } + ]; + }; + + # Encryption support + extraSettings = { + "de.sorunome.msc2409.push_ephemeral" = true; + push_ephemeral = true; + "org.matrix.msc3202" = true; + }; + + runtimeRegistration = "${cfg.registrationFile}"; + }; +in +{ + systemd.services.matrix-hookshot = { + serviceConfig = { + Type = lib.mkForce "exec"; + + LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot"; + inherit (registration) ExecStartPre; + + # Some library in matrix-hookshot wants a home directory + Environment = [ "HOME=/run/matrix-hookshot" ]; + + DynamicUser = true; + StateDirectory = "matrix-hookshot"; + RuntimeDirectory = "matrix-hookshot"; + RuntimeDirectoryMode = "0700"; + + RestrictNamespaces = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET AF_INET6" ]; + LockPersonality = true; + RestrictRealtime = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + UMask = 77; + }; + }; + + services.matrix-hookshot = { + enable = true; + + serviceDependencies = [ + "conduit.service" + ]; + + registrationFile = "/run/matrix-hookshot/registration.yaml"; + + settings = { + bridge = { + inherit domain; + url = "http://localhost:${toString conduitCfg.settings.global.port}"; + mediaUrl = conduitCfg.settings.global.well_known.client; + port = 9993; + bindAddress = "127.0.0.1"; + }; + + bot.displayname = "Hookshot"; + + generic = { + enabled = true; + outbound = false; + # Only allow webhooks from localhost for the moment + urlPrefix = "http://127.0.0.1:9000/webhook"; + userIdPrefix = "_webhooks_"; + allowJsTransformationFunctions = true; + }; + + encryption.storagePath = "/var/lib/matrix-hookshot/cryptostore"; + + permissions = [ + { + actor = "matrix.tlater.net"; + services = [ + { + service = "*"; + level = "notifications"; + } + ]; + } + { + actor = "@tlater:matrix.tlater.net"; + services = [ + { + service = "*"; + level = "admin"; + } + ]; + } + ]; + + listeners = [ + { + port = 9000; + resources = [ + "webhooks" + ]; + } + { + port = 9001; + resources = [ + "metrics" + ]; + } + ]; + + metrics.enable = true; + }; + }; +} diff --git a/configuration/services/metrics/victoriametrics.nix b/configuration/services/metrics/victoriametrics.nix index 5cfc614..d72215e 100644 --- a/configuration/services/metrics/victoriametrics.nix +++ b/configuration/services/metrics/victoriametrics.nix @@ -26,6 +26,10 @@ config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_port; in [ "${address}:${toString port}" ]; + + # Configured in the hookshot listeners, but it's hard to filter + # the correct values out of that config. + matrixHookshot.targets = [ "127.0.0.1:9001" ]; }; }; } diff --git a/configuration/sops.nix b/configuration/sops.nix index bbfb888..4800e6b 100644 --- a/configuration/sops.nix +++ b/configuration/sops.nix @@ -34,6 +34,10 @@ "heisenbridge/as-token" = { }; "heisenbridge/hs-token" = { }; + # Matrix-hookshot + "matrix-hookshot/as-token" = { }; + "matrix-hookshot/hs-token" = { }; + # Nextcloud "nextcloud/tlater" = { owner = "nextcloud"; diff --git a/keys/production.yaml b/keys/production.yaml index 19df9bd..0e906bd 100644 --- a/keys/production.yaml +++ b/keys/production.yaml @@ -16,6 +16,9 @@ steam: heisenbridge: as-token: ENC[AES256_GCM,data:+2yo6T18j34622H8ZWblAFB2phLw1q0k0vUQEZ5sFj7dQaRnkEiAMi0R3p17Zq0pOtGEC0RRZuPLYkcZ1oKP0w==,iv:lGwrQYp//FufpmJocrLIVyy9RK7lEEVcpAi0wmkjr34=,tag:yV06UbhAYJQz36O2XdhY+A==,type:str] hs-token: ENC[AES256_GCM,data:u52WpkQFd/J7JFoE/rfNluebyZQLOokvkVdL7+AEAvrhJhrkJli1ztkD79lbC+6tGUH4tT3T+nX9wvGKnrRUQg==,iv:as+9fVuvMg2IoE2WIKD9mHi+znhNcWRh5Zq+yr0xcDQ=,tag:mZ7fh7U0MfgI8hyq/28Bcg==,type:str] +matrix-hookshot: + as-token: ENC[AES256_GCM,data:nXTanPhDyDF7R3AllLqpM5dzljBrHwlh1KJnTGIi5PhbDY2lPj4+uXkMEwvm1u+hQjPyM7vKZPfK+0/dms6Y7A==,iv:fSakJN+yai0gfOJKFxxaxgyUtk0pNmIeqVgrdq92/24=,tag:Qc7+SUnm5/Nq5+QIScR9kQ==,type:str] + hs-token: ENC[AES256_GCM,data:Bwyj0JTTN0NNnwOs1zA8CqbtZSNcvlINeT7QVc2eJiHda92J6vQk7bSxy6KuqCN9DxlUsK13ggYjNORY2vic5w==,iv:Npnp8arYQ3Yb6CXrnKgE03hD7ZjGINPa/DwFI8D+5tA=,tag:FqNE6yI0nF4puEUw9MGAjQ==,type:str] wireguard: server-key: ENC[AES256_GCM,data:mXb7ZznJHf5CgV8rI4uzPBATMRbmd7LimgtCkQM9kAjbIaGwUBqJZBN3fXs=,iv:3Po1Orinzov9rnEm9cLzgJY1PeD+5Jl9115MriABHh8=,tag:E/2CjDO1JCvJzxCnqKcNyw==,type:str] restic: @@ -34,8 +37,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2025-02-01T10:16:20Z" - mac: ENC[AES256_GCM,data:oFJNljU0RJdgsdK7qRXKCqRs7kPXgHqSyYcexEs8kXFnn68mKHNKKfl7skepCPKk0U7h6JqJQ+EOnAA0eo6mraBAMKLSXUcucTzqsfcI+V04rYcP2nGPMUiNDGdKHCcb6OmBhfvKw7+elnonPxKsBlyK31AqB9RFDKaTKXpcNMw=,iv:Q9t7ZkUrevHm5I4JBW95TfvZ88dl2Fq3Yq/E642dV6s=,tag:p2XWfii168qq29wX/RCJuQ==,type:str] + lastmodified: "2025-02-07T19:44:49Z" + mac: ENC[AES256_GCM,data:+0hpd/E7GxK/27f2Itf0hDV+3Ga4gHb8xxLutJ32HLBWLZ5Y+dN03xgkz8jBTiM+BeHwS4gz70Cs9X3zLMHbosWVuIV9DLuRaHRq/IU9KiADwqmCySZALqCf3+T5QKZr3Qs4AZJHwaAXkRX9HbnRFriIAFDJW/BGdIHdoROquxY=,iv:TeXI8LGqHVa5wo61sGdNbZ2nJvSlPdgn9R3Lq5qUggU=,tag:TFort5wxVTdi9LMlMeT/DQ==,type:str] pgp: - created_at: "2025-01-21T17:55:44Z" enc: |- diff --git a/keys/staging.yaml b/keys/staging.yaml index 67e47ad..876d60e 100644 --- a/keys/staging.yaml +++ b/keys/staging.yaml @@ -16,6 +16,9 @@ steam: heisenbridge: as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str] hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str] +matrix-hookshot: + as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str] + hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str] wireguard: server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str] restic: @@ -34,8 +37,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2025-02-01T10:16:31Z" - mac: ENC[AES256_GCM,data:N4RQHOyWvSXW16fepQvRznNbmGerct03kptyiY3IoTpYaJ+43cyFjW15ZqfpaRFyV66QIeqmceqV8c4eP8YSndj6e55e04w0RCyqREXQlFPR6Eh5elaBenokoJhjF6BCsq+xX1C+LUEcxiR/dgy5cwA3mAD/dLCm+G11a06EG6k=,iv:wt5fEOVP6CXHCzmMH9hNCQDDgPa66bLMOa39Eipux9Y=,tag:kWZPnWD1stANVAmWmvOjCg==,type:str] + lastmodified: "2025-02-07T17:43:24Z" + mac: ENC[AES256_GCM,data:akmD/bfgeTyFzW1quvM16cdj0fC6+CbJ8WyX9173H11yKGxvE1USQYcErpl1SHOx9Jk8LVb7f+MsUm2fjQF1MEq6xaWI74jem12lZ9CGXFaTL7e87JvfbK7pV+aKpxSBBNFyJgbYm30ibdUwxwKmNVfPb1e0HT9qwenvoV7RobM=,iv:mKqOW0ULXL711uczUbRf9NPo6uPTQoS/IbR46S+JID4=,tag:vE6NYzYLbQHDImov1XGTcg==,type:str] pgp: - created_at: "2025-01-21T17:55:30Z" enc: |-