From ce06f43476863da90dc60dcee606d2b6c5a89a8e Mon Sep 17 00:00:00 2001 From: sefidel Date: Wed, 29 Mar 2023 20:54:19 +0900 Subject: project: initial commit --- overlays/mjolnir-module/default.nix | 242 ++++++++++++++++++++++++ overlays/mjolnir-module/mjolnir.md | 110 +++++++++++ overlays/mjolnir-module/pantalaimon-options.nix | 70 +++++++ 3 files changed, 422 insertions(+) create mode 100644 overlays/mjolnir-module/default.nix create mode 100644 overlays/mjolnir-module/mjolnir.md create mode 100644 overlays/mjolnir-module/pantalaimon-options.nix (limited to 'overlays/mjolnir-module') diff --git a/overlays/mjolnir-module/default.nix b/overlays/mjolnir-module/default.nix new file mode 100644 index 0000000..87ed761 --- /dev/null +++ b/overlays/mjolnir-module/default.nix @@ -0,0 +1,242 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.mjolnir; + + yamlConfig = { + inherit (cfg) dataPath managementRoom protectedRooms; + + accessToken = "@ACCESS_TOKEN@"; # will be replaced in "generateConfig" + homeserverUrl = + if cfg.pantalaimon.enable then + "http://${cfg.pantalaimon.options.listenAddress}:${toString cfg.pantalaimon.options.listenPort}" + else + cfg.homeserverUrl; + + rawHomeserverUrl = cfg.homeserverUrl; + + pantalaimon = { + inherit (cfg.pantalaimon) username; + + use = cfg.pantalaimon.enable; + password = "@PANTALAIMON_PASSWORD@"; # will be replaced in "generateConfig" + }; + }; + + moduleConfigFile = pkgs.writeText "module-config.yaml" ( + generators.toYAML { } (filterAttrs (_: v: v != null) + (fold recursiveUpdate { } [ yamlConfig cfg.settings ]))); + + # these config files will be merged one after the other to build the final config + configFiles = [ + "${pkgs.mjolnir}/libexec/mjolnir/deps/mjolnir/config/default.yaml" + moduleConfigFile + ]; + + # this will generate the default.yaml file with all configFiles as inputs and + # replace all secret strings using replace-secret + generateConfig = pkgs.writeShellScript "mjolnir-generate-config" ( + let + yqEvalStr = concatImapStringsSep " * " (pos: _: "select(fileIndex == ${toString (pos - 1)})") configFiles; + yqEvalArgs = concatStringsSep " " configFiles; + in + '' + set -euo pipefail + + umask 077 + + # mjolnir will try to load a config from "./config/default.yaml" in the working directory + # -> let's place the generated config there + mkdir -p ${cfg.dataPath}/config + + # merge all config files into one, overriding settings of the previous one with the next config + # e.g. "eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' filea.yaml fileb.yaml" will merge filea.yaml with fileb.yaml + ${pkgs.yq-go}/bin/yq eval-all -P '${yqEvalStr}' ${yqEvalArgs} > ${cfg.dataPath}/config/default.yaml + + ${optionalString (cfg.accessTokenFile != null) '' + ${pkgs.replace-secret}/bin/replace-secret '@ACCESS_TOKEN@' '${cfg.accessTokenFile}' ${cfg.dataPath}/config/default.yaml + ''} + ${optionalString (cfg.pantalaimon.passwordFile != null) '' + ${pkgs.replace-secret}/bin/replace-secret '@PANTALAIMON_PASSWORD@' '${cfg.pantalaimon.passwordFile}' ${cfg.dataPath}/config/default.yaml + ''} + '' + ); +in +{ + options.services.mjolnir = { + enable = mkEnableOption (lib.mdDoc "Mjolnir, a moderation tool for Matrix"); + + homeserverUrl = mkOption { + type = types.str; + default = "https://matrix.org"; + description = lib.mdDoc '' + Where the homeserver is located (client-server URL). + + If `pantalaimon.enable` is `true`, this option will become the homeserver to which `pantalaimon` connects. + The listen address of `pantalaimon` will then become the `homeserverUrl` of `mjolnir`. + ''; + }; + + accessTokenFile = mkOption { + type = with types; nullOr path; + default = null; + description = lib.mdDoc '' + File containing the matrix access token for the `mjolnir` user. + ''; + }; + + pantalaimon = mkOption { + description = lib.mdDoc '' + `pantalaimon` options (enables E2E Encryption support). + + This will create a `pantalaimon` instance with the name "mjolnir". + ''; + default = { }; + type = types.submodule { + options = { + enable = mkEnableOption (lib.mdDoc '' + If true, accessToken is ignored and the username/password below will be + used instead. The access token of the bot will be stored in the dataPath. + ''); + + username = mkOption { + type = types.str; + description = lib.mdDoc "The username to login with."; + }; + + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + description = lib.mdDoc '' + File containing the matrix password for the `mjolnir` user. + ''; + }; + + options = mkOption { + type = types.submodule (import ./pantalaimon-options.nix); + default = { }; + description = lib.mdDoc '' + passthrough additional options to the `pantalaimon` service. + ''; + }; + }; + }; + }; + + dataPath = mkOption { + type = types.path; + default = "/var/lib/mjolnir"; + description = lib.mdDoc '' + The directory the bot should store various bits of information in. + ''; + }; + + managementRoom = mkOption { + type = types.str; + default = "#moderators:example.org"; + description = lib.mdDoc '' + The room ID where people can use the bot. The bot has no access controls, so + anyone in this room can use the bot - secure your room! + This should be a room alias or room ID - not a matrix.to URL. + Note: `mjolnir` is fairly verbose - expect a lot of messages from it. + ''; + }; + + protectedRooms = mkOption { + type = types.listOf types.str; + default = [ ]; + example = literalExpression '' + [ + "https://matrix.to/#/#yourroom:example.org" + "https://matrix.to/#/#anotherroom:example.org" + ] + ''; + description = lib.mdDoc '' + A list of rooms to protect (matrix.to URLs). + ''; + }; + + settings = mkOption { + default = { }; + type = (pkgs.formats.yaml { }).type; + example = literalExpression '' + { + autojoinOnlyIfManager = true; + automaticallyRedactForReasons = [ "spam" "advertising" ]; + } + ''; + description = lib.mdDoc '' + Additional settings (see [mjolnir default config](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml) for available settings). These settings will override settings made by the module config. + ''; + }; + }; + + config = mkIf config.services.mjolnir.enable { + assertions = [ + { + assertion = !(cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile == null); + message = "Specify pantalaimon.passwordFile"; + } + { + assertion = !(cfg.pantalaimon.enable && cfg.accessTokenFile != null); + message = "Do not specify accessTokenFile when using pantalaimon"; + } + { + assertion = !(!cfg.pantalaimon.enable && cfg.accessTokenFile == null); + message = "Specify accessTokenFile when not using pantalaimon"; + } + ]; + + services.pantalaimon-headless.instances."mjolnir" = mkIf cfg.pantalaimon.enable + { + homeserver = cfg.homeserverUrl; + } // cfg.pantalaimon.options; + + systemd.services.mjolnir = { + description = "mjolnir - a moderation tool for Matrix"; + wants = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ]; + after = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = ''${pkgs.mjolnir}/bin/mjolnir''; + ExecStartPre = [ generateConfig ]; + WorkingDirectory = cfg.dataPath; + StateDirectory = "mjolnir"; + StateDirectoryMode = "0700"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + PrivateDevices = true; + User = "mjolnir"; + Restart = "on-failure"; + + /* TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME" + DynamicUser = true; + LoadCredential = [] ++ + optionals (cfg.accessTokenFile != null) [ + "access_token:${cfg.accessTokenFile}" + ] ++ + optionals (cfg.pantalaimon.passwordFile != null) [ + "pantalaimon_password:${cfg.pantalaimon.passwordFile}" + ]; + */ + }; + }; + + users = { + users.mjolnir = { + group = "mjolnir"; + isSystemUser = true; + }; + groups.mjolnir = { }; + }; + }; + + meta = { + doc = ./mjolnir.md; + maintainers = with maintainers; [ jojosch ]; + }; +} diff --git a/overlays/mjolnir-module/mjolnir.md b/overlays/mjolnir-module/mjolnir.md new file mode 100644 index 0000000..f6994ee --- /dev/null +++ b/overlays/mjolnir-module/mjolnir.md @@ -0,0 +1,110 @@ +# Mjolnir (Matrix Moderation Tool) {#module-services-mjolnir} + +This chapter will show you how to set up your own, self-hosted +[Mjolnir](https://github.com/matrix-org/mjolnir) instance. + +As an all-in-one moderation tool, it can protect your server from +malicious invites, spam messages, and whatever else you don't want. +In addition to server-level protection, Mjolnir is great for communities +wanting to protect their rooms without having to use their personal +accounts for moderation. + +The bot by default includes support for bans, redactions, anti-spam, +server ACLs, room directory changes, room alias transfers, account +deactivation, room shutdown, and more. + +See the [README](https://github.com/matrix-org/mjolnir#readme) +page and the [Moderator's guide](https://github.com/matrix-org/mjolnir/blob/main/docs/moderators.md) +for additional instructions on how to setup and use Mjolnir. + +For [additional settings](#opt-services.mjolnir.settings) +see [the default configuration](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml). + +## Mjolnir Setup {#module-services-mjolnir-setup} + +First create a new Room which will be used as a management room for Mjolnir. In +this room, Mjolnir will log possible errors and debugging information. You'll +need to set this Room-ID in [services.mjolnir.managementRoom](#opt-services.mjolnir.managementRoom). + +Next, create a new user for Mjolnir on your homeserver, if not present already. + +The Mjolnir Matrix user expects to be free of any rate limiting. +See [Synapse #6286](https://github.com/matrix-org/synapse/issues/6286) +for an example on how to achieve this. + +If you want Mjolnir to be able to deactivate users, move room aliases, shutdown rooms, etc. +you'll need to make the Mjolnir user a Matrix server admin. + +Now invite the Mjolnir user to the management room. + +It is recommended to use [Pantalaimon](https://github.com/matrix-org/pantalaimon), +so your management room can be encrypted. This also applies if you are looking to moderate an encrypted room. + +To enable the Pantalaimon E2E Proxy for mjolnir, enable +[services.mjolnir.pantalaimon](#opt-services.mjolnir.pantalaimon.enable). This will +autoconfigure a new Pantalaimon instance, which will connect to the homeserver +set in [services.mjolnir.homeserverUrl](#opt-services.mjolnir.homeserverUrl) and Mjolnir itself +will be configured to connect to the new Pantalaimon instance. + +``` +{ + services.mjolnir = { + enable = true; + homeserverUrl = "https://matrix.domain.tld"; + pantalaimon = { + enable = true; + username = "mjolnir"; + passwordFile = "/run/secrets/mjolnir-password"; + }; + protectedRooms = [ + "https://matrix.to/#/!xxx:domain.tld" + ]; + managementRoom = "!yyy:domain.tld"; + }; +} +``` + +### Element Matrix Services (EMS) {#module-services-mjolnir-setup-ems} + +If you are using a managed ["Element Matrix Services (EMS)"](https://ems.element.io/) +server, you will need to consent to the terms and conditions. Upon startup, an error +log entry with a URL to the consent page will be generated. + +## Synapse Antispam Module {#module-services-mjolnir-matrix-synapse-antispam} + +A Synapse module is also available to apply the same rulesets the bot +uses across an entire homeserver. + +To use the Antispam Module, add `matrix-synapse-plugins.matrix-synapse-mjolnir-antispam` +to the Synapse plugin list and enable the `mjolnir.Module` module. + +``` +{ + services.matrix-synapse = { + plugins = with pkgs; [ + matrix-synapse-plugins.matrix-synapse-mjolnir-antispam + ]; + extraConfig = '' + modules: + - module: mjolnir.Module + config: + # Prevent servers/users in the ban lists from inviting users on this + # server to rooms. Default true. + block_invites: true + # Flag messages sent by servers/users in the ban lists as spam. Currently + # this means that spammy messages will appear as empty to users. Default + # false. + block_messages: false + # Remove users from the user directory search by filtering matrix IDs and + # display names by the entries in the user ban list. Default false. + block_usernames: false + # The room IDs of the ban lists to honour. Unlike other parts of Mjolnir, + # this list cannot be room aliases or permalinks. This server is expected + # to already be joined to the room - Mjolnir will not automatically join + # these rooms. + ban_lists: + - "!roomid:example.org" + ''; + }; +} +``` diff --git a/overlays/mjolnir-module/pantalaimon-options.nix b/overlays/mjolnir-module/pantalaimon-options.nix new file mode 100644 index 0000000..3945a70 --- /dev/null +++ b/overlays/mjolnir-module/pantalaimon-options.nix @@ -0,0 +1,70 @@ +{ config, lib, name, ... }: + +with lib; +{ + options = { + dataPath = mkOption { + type = types.path; + default = "/var/lib/pantalaimon-${name}"; + description = lib.mdDoc '' + The directory where `pantalaimon` should store its state such as the database file. + ''; + }; + + logLevel = mkOption { + type = types.enum [ "info" "warning" "error" "debug" ]; + default = "warning"; + description = lib.mdDoc '' + Set the log level of the daemon. + ''; + }; + + homeserver = mkOption { + type = types.str; + example = "https://matrix.org"; + description = lib.mdDoc '' + The URI of the homeserver that the `pantalaimon` proxy should + forward requests to, without the matrix API path but including + the http(s) schema. + ''; + }; + + ssl = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether or not SSL verification should be enabled for outgoing + connections to the homeserver. + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = "localhost"; + description = lib.mdDoc '' + The address where the daemon will listen to client connections + for this homeserver. + ''; + }; + + listenPort = mkOption { + type = types.port; + default = 8009; + description = lib.mdDoc '' + The port where the daemon will listen to client connections for + this homeserver. Note that the listen address/port combination + needs to be unique between different homeservers. + ''; + }; + + extraSettings = mkOption { + type = types.attrs; + default = { }; + description = lib.mdDoc '' + Extra configuration options. See + [pantalaimon(5)](https://github.com/matrix-org/pantalaimon/blob/master/docs/man/pantalaimon.5.md) + for available options. + ''; + }; + }; +} -- cgit 1.4.1