feat: Matrix module
This commit is contained in:
15
sp-modules/matrix/config-paths-needed.json
Normal file
15
sp-modules/matrix/config-paths-needed.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
[ "selfprivacy", "domain" ],
|
||||||
|
[ "selfprivacy", "modules", "auth", "enable" ],
|
||||||
|
[ "services", "jitsi-meet", "enable" ],
|
||||||
|
[ "services", "jitsi-meet", "hostName" ],
|
||||||
|
[ "nixpkgs", "flake", "source" ],
|
||||||
|
[ "services", "postgresql", "package" ],
|
||||||
|
[ "selfprivacy", "modules", "matrix" ],
|
||||||
|
[ "selfprivacy", "passthru", "auth", "mkOAuth2ClientSecretFP" ],
|
||||||
|
[ "selfprivacy", "passthru", "auth", "mkServiceAccountTokenFP" ],
|
||||||
|
[ "selfprivacy", "passthru", "auth", "oauth2-discovery-url" ],
|
||||||
|
[ "selfprivacy", "passthru", "auth", "oauth2-provider-name" ],
|
||||||
|
[ "selfprivacy", "sso", "enable" ],
|
||||||
|
[ "selfprivacy", "useBinds" ]
|
||||||
|
]
|
55
sp-modules/matrix/flake.nix
Normal file
55
sp-modules/matrix/flake.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
description = "PoC SP module for Matrix service";
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
nixosModules.default = import ./module.nix;
|
||||||
|
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||||
|
meta =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
spModuleSchemaVersion = 1;
|
||||||
|
id = "matrix";
|
||||||
|
name = "Matrix";
|
||||||
|
description = "An open network for secure, decentralised communication";
|
||||||
|
svgIcon = builtins.readFile ./icon.svg;
|
||||||
|
primarySubdomain = "elementSubdomain";
|
||||||
|
isMovable = true;
|
||||||
|
isRequired = false;
|
||||||
|
backupDescription = "Messages, sessions, server signing keys and attachments";
|
||||||
|
systemdServices = [
|
||||||
|
"matrix-synapse.service"
|
||||||
|
"matrix-authentication-service.service"
|
||||||
|
"mas-kanidm-sync.service"
|
||||||
|
];
|
||||||
|
ownedFolders = [
|
||||||
|
{
|
||||||
|
path = "/var/lib/matrix-synapse";
|
||||||
|
owner = "matrix-synapse";
|
||||||
|
group = "matrix-synapse";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
path = "/var/lib/matrix-authentication-service";
|
||||||
|
owner = "matrix-authentication-service";
|
||||||
|
group = "matrix-authentication-service";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
folders = [];
|
||||||
|
postgreDatabases = [
|
||||||
|
"matrix-synapse"
|
||||||
|
"matrix-authentication-service"
|
||||||
|
];
|
||||||
|
license = [
|
||||||
|
lib.licenses.agpl3Plus
|
||||||
|
];
|
||||||
|
homepage = "https://matrix.org";
|
||||||
|
sourcePage = "https://github.com/element-hq";
|
||||||
|
supportLevel = "experimental";
|
||||||
|
sso = {
|
||||||
|
userGroup = "sp.matrix.users";
|
||||||
|
adminGroup = "sp.matrix.admins";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
14
sp-modules/matrix/icon.svg
Normal file
14
sp-modules/matrix/icon.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 520 520" style="enable-background:new 0 0 520 520;" xml:space="preserve">
|
||||||
|
<path d="M13.7,11.9v496.2h35.7V520H0V0h49.4v11.9H13.7z"/>
|
||||||
|
<path d="M166.3,169.2v25.1h0.7c6.7-9.6,14.8-17,24.2-22.2c9.4-5.3,20.3-7.9,32.5-7.9c11.7,0,22.4,2.3,32.1,6.8
|
||||||
|
c9.7,4.5,17,12.6,22.1,24c5.5-8.1,13-15.3,22.4-21.5c9.4-6.2,20.6-9.3,33.5-9.3c9.8,0,18.9,1.2,27.3,3.6c8.4,2.4,15.5,6.2,21.5,11.5
|
||||||
|
c6,5.3,10.6,12.1,14,20.6c3.3,8.5,5,18.7,5,30.7v124.1h-50.9V249.6c0-6.2-0.2-12.1-0.7-17.6c-0.5-5.5-1.8-10.3-3.9-14.3
|
||||||
|
c-2.2-4.1-5.3-7.3-9.5-9.7c-4.2-2.4-9.9-3.6-17-3.6c-7.2,0-13,1.4-17.4,4.1c-4.4,2.8-7.9,6.3-10.4,10.8c-2.5,4.4-4.2,9.4-5,15.1
|
||||||
|
c-0.8,5.6-1.3,11.3-1.3,17v103.3h-50.9v-104c0-5.5-0.1-10.9-0.4-16.3c-0.2-5.4-1.3-10.3-3.1-14.9c-1.8-4.5-4.8-8.2-9-10.9
|
||||||
|
c-4.2-2.7-10.3-4.1-18.5-4.1c-2.4,0-5.6,0.5-9.5,1.6c-3.9,1.1-7.8,3.1-11.5,6.1c-3.7,3-6.9,7.3-9.5,12.9c-2.6,5.6-3.9,13-3.9,22.1
|
||||||
|
v107.6h-50.9V169.2H166.3z"/>
|
||||||
|
<path d="M506.3,508.1V11.9h-35.7V0H520v520h-49.4v-11.9H506.3z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
119
sp-modules/matrix/mas-kanidm-sync.py
Normal file
119
sp-modules/matrix/mas-kanidm-sync.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import psycopg
|
||||||
|
|
||||||
|
from psycopg.rows import dict_row
|
||||||
|
from ulid import ULID
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(path):
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def getenv(name):
|
||||||
|
try:
|
||||||
|
return os.environ[name]
|
||||||
|
except KeyError:
|
||||||
|
print(f"Missing environment variable {name}. You should NOT run this script by hand, please use systemd unit mas-kanidm-sync.service.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
KANIDM_ULID = ULID.from_str(getenv("KANIDM_ULID"))
|
||||||
|
KANIDM_UUID = str(KANIDM_ULID.to_uuid())
|
||||||
|
MAS_URL = getenv("MAS_URL")
|
||||||
|
KANIDM_URL = getenv("KANIDM_URL")
|
||||||
|
KANIDM_TOKEN = read_file(getenv("KANIDM_TOKEN_PATH"))
|
||||||
|
CLIENT_ID = getenv("CLIENT_ID")
|
||||||
|
CLIENT_SECRET = read_file(getenv("CLIENT_SECRET_PATH"))
|
||||||
|
MAS_POSTGRES_URL = getenv("MAS_POSTGRES_URL")
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{MAS_URL}/.well-known/openid-configuration", timeout=3)
|
||||||
|
r.raise_for_status()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
print(
|
||||||
|
f"MAS instance at {MAS_URL} is not responding, trying again in 3 seconds..."
|
||||||
|
)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
mas_access_token_req = requests.post(
|
||||||
|
f"{MAS_URL}/oauth2/token",
|
||||||
|
auth=(CLIENT_ID, CLIENT_SECRET),
|
||||||
|
data={"grant_type": "client_credentials", "scope": "urn:mas:admin"},
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
mas_access_token_req.raise_for_status()
|
||||||
|
|
||||||
|
MAS_ACCESS_TOKEN = mas_access_token_req.json()["access_token"]
|
||||||
|
|
||||||
|
|
||||||
|
def sync_accounts():
|
||||||
|
kanidm_persons_request = requests.get(
|
||||||
|
f"{KANIDM_URL}/v1/person",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {KANIDM_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
kanidm_persons_request.raise_for_status()
|
||||||
|
kanidm_persons = kanidm_persons_request.json()
|
||||||
|
|
||||||
|
with psycopg.connect(
|
||||||
|
MAS_POSTGRES_URL, autocommit=True, row_factory=dict_row
|
||||||
|
) as pg_conn:
|
||||||
|
users = pg_conn.execute(
|
||||||
|
"SELECT users.user_id, upstream_oauth_links.subject, users.can_request_admin, users.username FROM users INNER JOIN upstream_oauth_links ON upstream_oauth_links.user_id = users.user_id WHERE upstream_oauth_links.upstream_oauth_provider_id = %s;",
|
||||||
|
(KANIDM_UUID,),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
for user_row in users:
|
||||||
|
found_in_kanidm = False
|
||||||
|
is_admin_in_kanidm = False
|
||||||
|
for kanidm_person in kanidm_persons:
|
||||||
|
if kanidm_person["attrs"]["uuid"][0] == user_row["subject"]:
|
||||||
|
found_in_kanidm = True
|
||||||
|
for group in kanidm_person["attrs"]["memberof"]:
|
||||||
|
if group.startswith("sp.matrix.admins@"):
|
||||||
|
is_admin_in_kanidm = True
|
||||||
|
username = user_row["username"]
|
||||||
|
if found_in_kanidm and user_row["can_request_admin"] != is_admin_in_kanidm:
|
||||||
|
mas_user_id = str(ULID.from_uuid(user_row["user_id"]))
|
||||||
|
print(
|
||||||
|
f"Updating user {username} ({mas_user_id}) can_request_admin field to {is_admin_in_kanidm}"
|
||||||
|
)
|
||||||
|
response = requests.post(
|
||||||
|
f"{MAS_URL}/api/admin/v1/users/{mas_user_id}/set-admin",
|
||||||
|
data=json.dumps({"admin": is_admin_in_kanidm}),
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {MAS_ACCESS_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(
|
||||||
|
f"ERROR: Failed to update can_request_admin field of user {username}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"Updated user {username} ({mas_user_id})")
|
||||||
|
elif not found_in_kanidm:
|
||||||
|
print(
|
||||||
|
"ERROR: User {username} is in MAS, but doesn't exist in Kanidm. TODO: should we deactivate it?"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sync_accounts()
|
||||||
|
except Exception as e:
|
||||||
|
print("Failed to sync MAS and Kanidm admin rights:")
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
time.sleep(30)
|
511
sp-modules/matrix/module.nix
Normal file
511
sp-modules/matrix/module.nix
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
sp = config.selfprivacy;
|
||||||
|
cfg = sp.modules.matrix;
|
||||||
|
oauthClientID = "matrix";
|
||||||
|
auth-passthru = config.selfprivacy.passthru.auth;
|
||||||
|
oauth2-provider-name = auth-passthru.oauth2-provider-name;
|
||||||
|
oauthDiscoveryURL = auth-passthru.oauth2-discovery-url oauthClientID;
|
||||||
|
|
||||||
|
# TODO: Make background in Element configurable. This will require new option type in the app.
|
||||||
|
backgroundImage = builtins.fetchurl {
|
||||||
|
url = "https://git.selfprivacy.org/SelfPrivacy/imagery/raw/commit/44004ccc4467e22f5d6014bbf6b10b28fb8d511a/social/mastodon_instance_cover.png";
|
||||||
|
sha256 = "sha256-em9+IkG1OIgH6+Pi00dtrw1yfFp85X6LNsP5y/r/T7o=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# SelfPrivacy uses SP Module ID to identify the group!
|
||||||
|
adminsGroup = "sp.matrix.admins";
|
||||||
|
usersGroup = "sp.matrix.users";
|
||||||
|
|
||||||
|
serviceAccountFP = auth-passthru.mkServiceAccountTokenFP "matrix-authentication-service";
|
||||||
|
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP "matrix-authentication-service";
|
||||||
|
|
||||||
|
synapseBase = "${cfg.subdomain}.${sp.domain}";
|
||||||
|
synapseBaseURL = "https://" + synapseBase;
|
||||||
|
|
||||||
|
clientConfig."m.homeserver" = {
|
||||||
|
base_url = synapseBaseURL;
|
||||||
|
server_name = sp.domain;
|
||||||
|
};
|
||||||
|
serverConfig."m.server" = "${synapseBase}:443";
|
||||||
|
mkWellKnown = data: ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Strict-Transport-Security $hsts_header;
|
||||||
|
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||||
|
add_header X-Frame-Options DENY;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
return 200 '${builtins.toJSON data}';
|
||||||
|
'';
|
||||||
|
|
||||||
|
elementConfig = {
|
||||||
|
default_server_config = clientConfig;
|
||||||
|
disable_guests = true;
|
||||||
|
disable_login_language_selector = false;
|
||||||
|
default_country_code = "US";
|
||||||
|
show_labs_settings = true;
|
||||||
|
default_theme = "light";
|
||||||
|
|
||||||
|
room_directory.servers = [
|
||||||
|
"selfprivacy.org"
|
||||||
|
"matrix.org"
|
||||||
|
];
|
||||||
|
|
||||||
|
jitsi.preffered_domain = lib.mkIf config.services.jitsi-meet.enable "https://${config.services.jitsi-meet.hostName}";
|
||||||
|
|
||||||
|
branding.welcome_background_url = "https://${cfg.elementSubdomain}.${sp.domain}/background.png";
|
||||||
|
|
||||||
|
setting_defaults = {
|
||||||
|
"UIFeature.feedback" = false;
|
||||||
|
"UIFeature.passwordReset" = false;
|
||||||
|
"UIFeature.deactivate" = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
yamlFormat = pkgs.formats.yaml { };
|
||||||
|
|
||||||
|
kanidmUlid = "01G65Z755AFWAKHE12NY0CQ9FH";
|
||||||
|
synapseUlid = "0000000000000000000SYNAPSE";
|
||||||
|
masKanidmSyncClientId = "01JZQSK4HXHXR2QATT40Y7497J";
|
||||||
|
|
||||||
|
masConfig = {
|
||||||
|
policy.data.admin_clients = lib.singleton masKanidmSyncClientId;
|
||||||
|
http = {
|
||||||
|
public_base = "https://${cfg.masSubdomain}.${sp.domain}";
|
||||||
|
|
||||||
|
listeners = [
|
||||||
|
{
|
||||||
|
name = "web";
|
||||||
|
resources = [
|
||||||
|
{ name = "discovery"; }
|
||||||
|
{ name = "human"; }
|
||||||
|
{ name = "oauth"; }
|
||||||
|
{ name = "compat"; }
|
||||||
|
{ name = "graphql"; }
|
||||||
|
{ name = "adminapi"; }
|
||||||
|
{
|
||||||
|
name = "assets";
|
||||||
|
path = "${pkgs.matrix-authentication-service}/share/matrix-authentication-service/assets";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
binds = [
|
||||||
|
{
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = 8068;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
proxy_protocol = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
database = {
|
||||||
|
uri = "postgresql:///matrix-authentication-service?host=/run/postgresql";
|
||||||
|
};
|
||||||
|
matrix = {
|
||||||
|
homeserver = sp.domain;
|
||||||
|
endpoint = "http://127.0.0.1:8078";
|
||||||
|
};
|
||||||
|
passwords = {
|
||||||
|
enabled = true;
|
||||||
|
schemes = [
|
||||||
|
{
|
||||||
|
version = 1;
|
||||||
|
algorithm = "argon2id";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
upstreamOauth2Template = pkgs.writeText "upstream_oauth2_template" (
|
||||||
|
builtins.toJSON {
|
||||||
|
id = kanidmUlid;
|
||||||
|
issuer = lib.strings.removeSuffix "/.well-known/openid-configuration" oauthDiscoveryURL;
|
||||||
|
token_endpoint_auth_method = "client_secret_basic";
|
||||||
|
human_name = oauth2-provider-name;
|
||||||
|
client_id = oauthClientID;
|
||||||
|
scope = "openid email profile";
|
||||||
|
pkce_method = "always";
|
||||||
|
|
||||||
|
claims_imports.email.action = "force";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
experimentalMsc3861Template = pkgs.writeText "experimental_msc3861_template" (
|
||||||
|
builtins.toJSON {
|
||||||
|
enabled = true;
|
||||||
|
issuer = "http://localhost:8068";
|
||||||
|
client_id = synapseUlid;
|
||||||
|
client_auth_method = "client_secret_basic";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
masConfigFile = yamlFormat.generate "config.yaml" masConfig;
|
||||||
|
|
||||||
|
masCliWithConfigs = "${pkgs.matrix-authentication-service}/bin/mas-cli ${
|
||||||
|
lib.concatMapStringsSep " " (x: "--config ${x}") [
|
||||||
|
masConfigFile
|
||||||
|
"${masDataDir}/secrets.yaml"
|
||||||
|
"${masDataDir}/oauth2_secrets.yaml"
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
masCliUserWrapper = pkgs.writeShellScriptBin "mas-cli" ''
|
||||||
|
su matrix-authentication-service -s /bin/sh -c "${masCliWithConfigs} $*"
|
||||||
|
'';
|
||||||
|
|
||||||
|
synapseDataDir = "/var/lib/matrix-synapse";
|
||||||
|
masDataDir = "/var/lib/matrix-authentication-service";
|
||||||
|
|
||||||
|
# jitsi pollutes pkgs causing element-web to rebuild.
|
||||||
|
element-web =
|
||||||
|
(import config.nixpkgs.flake.source {
|
||||||
|
system = pkgs.system;
|
||||||
|
}).element-web.override
|
||||||
|
{
|
||||||
|
conf = elementConfig;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.selfprivacy.modules.matrix = {
|
||||||
|
enable =
|
||||||
|
(lib.mkOption {
|
||||||
|
default = false;
|
||||||
|
type = lib.types.bool;
|
||||||
|
description = "Enable Matrix";
|
||||||
|
})
|
||||||
|
// {
|
||||||
|
meta = {
|
||||||
|
type = "enable";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
location =
|
||||||
|
(lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Matrix location";
|
||||||
|
})
|
||||||
|
// {
|
||||||
|
meta = {
|
||||||
|
type = "location";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
subdomain =
|
||||||
|
(lib.mkOption {
|
||||||
|
default = "synapse";
|
||||||
|
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
description = "Matrix server subdomain";
|
||||||
|
})
|
||||||
|
// {
|
||||||
|
meta = {
|
||||||
|
widget = "subdomain";
|
||||||
|
type = "string";
|
||||||
|
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
weight = 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
elementSubdomain =
|
||||||
|
(lib.mkOption {
|
||||||
|
default = "element";
|
||||||
|
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
description = "Element client subdomain";
|
||||||
|
})
|
||||||
|
// {
|
||||||
|
meta = {
|
||||||
|
widget = "subdomain";
|
||||||
|
type = "string";
|
||||||
|
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
weight = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
masSubdomain =
|
||||||
|
(lib.mkOption {
|
||||||
|
default = "mas";
|
||||||
|
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
description = "Matrix Authentication Service subdomain";
|
||||||
|
})
|
||||||
|
// {
|
||||||
|
meta = {
|
||||||
|
widget = "subdomain";
|
||||||
|
type = "string";
|
||||||
|
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||||
|
weight = 2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.subdomain != cfg.elementSubdomain;
|
||||||
|
message = "Element should be hosted on separate subdomain";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.subdomain != cfg.masSubdomain;
|
||||||
|
message = "MAS should be hosted on separate subdomain";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.elementSubdomain != cfg.masSubdomain;
|
||||||
|
message = "MAS should be hosted on separate subdomain, not on Element domain";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = sp.sso.enable;
|
||||||
|
message = "Matrix cannot be enabled when SSO is disabled as Matrix uses OIDC for user authentication.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
fileSystems = lib.mkIf sp.useBinds {
|
||||||
|
${synapseDataDir} = {
|
||||||
|
device = "/volumes/${cfg.location}/matrix-synapse";
|
||||||
|
options = [ "bind" ];
|
||||||
|
};
|
||||||
|
${masDataDir} = {
|
||||||
|
device = "/volumes/${cfg.location}/matrix-authentication-service";
|
||||||
|
options = [ "bind" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgresql = {
|
||||||
|
enable = true;
|
||||||
|
ensureDatabases = [ "matrix-authentication-service" ];
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "matrix-synapse";
|
||||||
|
ensureClauses.createdb = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "matrix-authentication-service";
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.matrix-synapse = {
|
||||||
|
enable = true;
|
||||||
|
extras = [ "oidc" ]; # add authlib to PYTHONPATH
|
||||||
|
settings.server_name = sp.domain;
|
||||||
|
settings.public_baseurl = synapseBaseURL;
|
||||||
|
settings.allow_guest_access = false;
|
||||||
|
settings.listeners = [
|
||||||
|
{
|
||||||
|
port = 8078;
|
||||||
|
bind_addresses = [ "127.0.0.1" ];
|
||||||
|
type = "http";
|
||||||
|
tls = false;
|
||||||
|
x_forwarded = true;
|
||||||
|
resources = [
|
||||||
|
{
|
||||||
|
names = [
|
||||||
|
"client"
|
||||||
|
"federation"
|
||||||
|
];
|
||||||
|
compress = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
extraConfigFiles = [
|
||||||
|
"${synapseDataDir}/mas_secrets.yaml"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts.${sp.domain} = {
|
||||||
|
locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
|
||||||
|
locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts.${synapseBase} = {
|
||||||
|
useACMEHost = sp.domain;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:8078";
|
||||||
|
locations."~ ^/_matrix/client/(.*)/(login|logout|refresh)".proxyPass = "http://127.0.0.1:8068";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."${cfg.masSubdomain}.${sp.domain}" = {
|
||||||
|
useACMEHost = sp.domain;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:8068";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."${cfg.elementSubdomain}.${sp.domain}" = {
|
||||||
|
useACMEHost = sp.domain;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
root = element-web;
|
||||||
|
|
||||||
|
locations."= /background.png" = {
|
||||||
|
extraConfig = ''
|
||||||
|
alias ${backgroundImage};
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.matrix-authentication-service = {
|
||||||
|
group = "matrix-authentication-service";
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
users.groups.matrix-authentication-service = { };
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
services.matrix-authentication-service = {
|
||||||
|
after = [
|
||||||
|
"postgresql.service"
|
||||||
|
"kanidm.service"
|
||||||
|
];
|
||||||
|
wants = [
|
||||||
|
"postgresql.service"
|
||||||
|
"kanidm.service"
|
||||||
|
];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [
|
||||||
|
pkgs.matrix-authentication-service
|
||||||
|
pkgs.yq
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Slice = "matrix.slice";
|
||||||
|
User = "matrix-authentication-service";
|
||||||
|
Group = "matrix-authentication-service";
|
||||||
|
ExecStartPre = [
|
||||||
|
(
|
||||||
|
"+"
|
||||||
|
+ (pkgs.writeShellScript "matrix-authentication-service-prepare-secrets" ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mkdir -p ${synapseDataDir}
|
||||||
|
mkdir -p ${masDataDir}
|
||||||
|
if [ ! -f ${masDataDir}/sync-client-secret ]; then
|
||||||
|
SYNC_CLIENT_SECRET=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 50)
|
||||||
|
echo -n "$SYNC_CLIENT_SECRET" > ${masDataDir}/sync-client-secret
|
||||||
|
fi
|
||||||
|
if [ ! -f ${masDataDir}/matrix-token ]; then
|
||||||
|
MATRIX_TOKEN=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 50)
|
||||||
|
echo -n "$MATRIX_TOKEN" > ${masDataDir}/matrix-token
|
||||||
|
fi
|
||||||
|
if [ ! -f ${masDataDir}/secrets.yaml ]; then
|
||||||
|
mas-cli config generate > ${masDataDir}/secrets.yaml
|
||||||
|
fi
|
||||||
|
cat ${masDataDir}/secrets.yaml | yq --arg matrixtoken "$(cat ${masDataDir}/matrix-token)" --arg syncclientsecret "$(cat ${masDataDir}/sync-client-secret)" --slurpfile template ${experimentalMsc3861Template} '{
|
||||||
|
secrets: .secrets,
|
||||||
|
matrix: {secret: .matrix.secret},
|
||||||
|
clients: [
|
||||||
|
{ client_id: "${synapseUlid}", client_auth_method: "client_secret_basic", client_secret: $matrixtoken },
|
||||||
|
{ client_id: "${masKanidmSyncClientId}", client_auth_method: "client_secret_basic", client_secret: $syncclientsecret }
|
||||||
|
]
|
||||||
|
}' > ${masDataDir}/secrets.yaml
|
||||||
|
cat ${upstreamOauth2Template} | yq --rawfile clientsecret ${oauthClientSecretFP} '{upstream_oauth2: {providers: [ . * {client_secret: $clientsecret}]}}' > ${masDataDir}/oauth2_secrets.yaml
|
||||||
|
cat ${masDataDir}/secrets.yaml | yq --slurpfile template ${experimentalMsc3861Template} '{experimental_features: {msc3861: ($template[0] * {client_secret: .clients[0].client_secret, admin_token: .matrix.secret })}}' > ${synapseDataDir}/mas_secrets.yaml
|
||||||
|
chown matrix-authentication-service:matrix-authentication-service ${masDataDir} -R
|
||||||
|
chmod 640 ${masDataDir}/*
|
||||||
|
chown matrix-synapse:matrix-synapse ${synapseDataDir}
|
||||||
|
chown matrix-synapse:matrix-synapse ${synapseDataDir}/mas_secrets.yaml
|
||||||
|
chmod 640 ${synapseDataDir}/mas_secrets.yaml
|
||||||
|
'')
|
||||||
|
)
|
||||||
|
];
|
||||||
|
ExecStart = ''
|
||||||
|
${masCliWithConfigs} server
|
||||||
|
'';
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "1s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.matrix-synapse-prepare-db = {
|
||||||
|
description = "Create Synapse database";
|
||||||
|
after = [ "postgresql.service" ];
|
||||||
|
requires = [ "postgresql.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Slice = "matrix.slice";
|
||||||
|
User = "postgres";
|
||||||
|
Group = "postgres";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
${config.services.postgresql.package}/bin/psql -d postgres -f ${pkgs.writeText "create-synapse-db.sql" ''
|
||||||
|
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
|
||||||
|
TEMPLATE template0
|
||||||
|
LC_COLLATE = "C"
|
||||||
|
LC_CTYPE = "C";
|
||||||
|
''} || true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
services.mas-kanidm-sync = {
|
||||||
|
after = [
|
||||||
|
"matrix-authentication-service.service"
|
||||||
|
"matrix-synapse-prepare-db.service"
|
||||||
|
];
|
||||||
|
requires = [
|
||||||
|
"kanidm.service"
|
||||||
|
"matrix-authentication-service.service"
|
||||||
|
];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = {
|
||||||
|
PYTHONUNBUFFERED = "1";
|
||||||
|
KANIDM_TOKEN_PATH = "%d/kanidm-token";
|
||||||
|
MAS_URL = "https://${cfg.masSubdomain}.${sp.domain}";
|
||||||
|
KANIDM_URL = "https://auth.${sp.domain}";
|
||||||
|
KANIDM_ULID = kanidmUlid;
|
||||||
|
CLIENT_ID = masKanidmSyncClientId;
|
||||||
|
CLIENT_SECRET_PATH = "%d/client-secret";
|
||||||
|
MAS_POSTGRES_URL = "postgresql:///matrix-authentication-service?host=/run/postgresql";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Slice = "matrix.slice";
|
||||||
|
User = "matrix-authentication-service";
|
||||||
|
Group = "matrix-authentication-service";
|
||||||
|
LoadCredential = ["kanidm-token:${serviceAccountFP}" "client-secret:${masDataDir}/sync-client-secret"];
|
||||||
|
ExecStart = pkgs.writers.writePython3 "mas-kanidm-sync" {
|
||||||
|
doCheck = false;
|
||||||
|
libraries = with pkgs.python3Packages; [
|
||||||
|
requests
|
||||||
|
psycopg
|
||||||
|
python-ulid
|
||||||
|
];
|
||||||
|
} (builtins.readFile ./mas-kanidm-sync.py);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.matrix-synapse = {
|
||||||
|
after = [
|
||||||
|
"matrix-authentication-service.service"
|
||||||
|
"matrix-synapse-prepare-db.service"
|
||||||
|
];
|
||||||
|
wants = [
|
||||||
|
"matrix-authentication-service.service"
|
||||||
|
];
|
||||||
|
requires = [
|
||||||
|
"matrix-synapse-prepare-db.service"
|
||||||
|
];
|
||||||
|
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/matrix-synapse";
|
||||||
|
serviceConfig.Slice = "matrix.slice";
|
||||||
|
};
|
||||||
|
slices.matrix = {
|
||||||
|
description = "Matrix server";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
masCliUserWrapper
|
||||||
|
];
|
||||||
|
|
||||||
|
selfprivacy.auth.clients.${oauthClientID} = {
|
||||||
|
inherit adminsGroup usersGroup;
|
||||||
|
subdomain = cfg.masSubdomain;
|
||||||
|
isTokenNeeded = true;
|
||||||
|
originLanding = "https://${cfg.masSubdomain}.${sp.domain}/";
|
||||||
|
originUrl = "https://${cfg.masSubdomain}.${sp.domain}/upstream/callback/${kanidmUlid}";
|
||||||
|
clientSystemdUnits = [ "matrix-authentication-service.service" ];
|
||||||
|
enablePkce = true;
|
||||||
|
linuxUserOfClient = "matrix-authentication-service";
|
||||||
|
linuxGroupOfClient = "matrix-authentication-service";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.kanidm.provision.systems.oauth2.${oauthClientID}.enableLegacyCrypto = true;
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user