feat(conduit): Refactor appservices and add matrix-hookshot

This commit is contained in:
Tristan Daniël Maat 2025-02-08 01:42:52 +08:00
parent 3c7e1f106b
commit 31b2527e40
Signed by: tlater
GPG key ID: 49670FD774E43268
8 changed files with 306 additions and 91 deletions

View file

@ -0,0 +1,173 @@
{
config,
lib,
...
}:
let
inherit (lib.strings) concatMapStringsSep;
cfg = config.services.matrix-conduit;
domain = "matrix.${config.services.nginx.domain}";
turn-realm = "turn.${config.services.nginx.domain}";
in
{
imports = [
./heisenbridge.nix
./matrix-hookshot.nix
];
services.matrix-conduit = {
enable = true;
settings.global = {
address = "127.0.0.1";
server_name = domain;
database_backend = "rocksdb";
# Set up delegation: https://docs.conduit.rs/delegation.html#automatic-recommended
# This is primarily to make sliding sync work
well_known = {
client = "https://${domain}";
server = "${domain}:443";
};
turn_uris =
let
address = "${config.services.coturn.realm}:${toString config.services.coturn.listening-port}";
tls-address = "${config.services.coturn.realm}:${toString config.services.coturn.tls-listening-port}";
in
[
"turn:${address}?transport=udp"
"turn:${address}?transport=tcp"
"turns:${tls-address}?transport=udp"
"turns:${tls-address}?transport=tcp"
];
};
};
# Pass in the TURN secret via EnvironmentFile, not supported by
# upstream module currently.
#
# See also https://gitlab.com/famedly/conduit/-/issues/314
systemd.services.conduit.serviceConfig.EnvironmentFile = config.sops.secrets."turn/env".path;
services.coturn = {
enable = true;
no-cli = true;
use-auth-secret = true;
static-auth-secret-file = config.sops.secrets."turn/secret".path;
realm = turn-realm;
relay-ips = [ "116.202.158.55" ];
# SSL config
#
# TODO(tlater): Switch to letsencrypt once google fix:
# https://github.com/vector-im/element-android/issues/1533
pkey = config.sops.secrets."turn/ssl-key".path;
cert = config.sops.secrets."turn/ssl-cert".path;
# Based on suggestions from
# https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md
# and
# https://www.foxypossibilities.com/2018/05/19/setting-up-a-turn-sever-for-matrix-on-nixos/
no-tcp-relay = true;
secure-stun = true;
extraConfig = ''
# Deny various local IP ranges, see
# https://www.rtcsec.com/article/cve-2020-26262-bypass-of-coturns-access-control-protection/
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255 denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
# *Allow* any IP addresses that we explicitly set as relay IPs
${concatMapStringsSep "\n" (ip: "allowed-peer-ip=${ip}") config.services.coturn.relay-ips}
# Various other security settings
no-tlsv1
no-tlsv1_1
# Monitoring
prometheus
'';
};
services.nginx.virtualHosts."${domain}" = {
useACMEHost = "tlater.net";
listen = [
{
addr = "0.0.0.0";
port = 80;
}
{
addr = "[::0]";
port = 80;
}
{
addr = "0.0.0.0";
port = 443;
ssl = true;
}
{
addr = "[::0]";
port = 443;
ssl = true;
}
{
addr = "0.0.0.0";
port = 8448;
ssl = true;
}
{
addr = "[::0]";
port = 8448;
ssl = true;
}
];
forceSSL = true;
enableHSTS = true;
extraConfig = ''
merge_slashes off;
'';
locations = {
"/_matrix" = {
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
# Recommended by conduit
extraConfig = ''
proxy_buffering off;
'';
};
"/.well-known/matrix" = {
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
};
};
};
services.backups.conduit = {
user = "root";
paths = [ "/var/lib/private/matrix-conduit/" ];
# Other services store their data in conduit, so no other services
# need to be shut down currently.
pauseServices = [ "conduit.service" ];
};
}

View file

@ -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" ];
};
};
}

View file

@ -0,0 +1,66 @@
{
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}"
'';
};
}

View file

@ -0,0 +1,142 @@
{
pkgs,
lib,
config,
...
}:
let
matrixLib = pkgs.callPackage ./lib.nix { };
cfg = config.services.matrix-hookshot;
conduitCfg = config.services.matrix-conduit;
address = "${config.services.matrix-hookshot.settings.bridge.bindAddress}";
domain = "matrix.${config.services.nginx.domain}";
port = config.services.matrix-hookshot.settings.bridge.port;
registration = matrixLib.writeRegistrationScript {
id = "matrix-hookshot";
url = "${address}:${toString port}";
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";
};
generic = {
enabled = true;
outbound = false;
# Only allow webhooks from localhost for the moment
urlPrefix = "${cfg.settings.bridge.url}/webhook";
userIdPrefix = "_webhooks_";
};
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"
"provisioning"
];
}
];
};
};
}