Compare commits

...

2 commits

Author SHA1 Message Date
5a33261482
expose Matrix admin APIs restricted to trusted networks
This is needed to have element-admin work.
2025-10-12 20:45:54 +02:00
27777156aa
public-web-static: host an element-admin instance 2025-10-12 20:19:27 +02:00
4 changed files with 158 additions and 0 deletions

View file

@ -33,6 +33,17 @@ let
}];
proxy_protocol = false;
}
{
name = "admin";
resources = [{
name = "adminapi";
}];
binds = [{
host = "localhost";
port = 8082;
}];
proxy_protocol = false;
}
];
trusted_proxies = [
"127.0.0.1/8"

View file

@ -65,6 +65,24 @@
'';
};
locations."~ ^/_synapse/admin" = {
# Only proxy to the local host on IPv4, because localhost doesn't seem to work
# even if matrix-synapse is listening on ::1 as well.
proxyPass = "http://127.0.0.1:8008";
extraConfig = ''
# Restrict access to admin API.
allow 185.161.129.132/32; # z9
allow 2a07:c480:0:100::/56; # z9
allow 2a07:c481:1::/48; # z9 new ipv6
allow 213.240.180.39/32; # stbe home
allow 2a01:170:118b::1/64; # stbe home
deny all;
# Nginx by default only allows file uploads up to 1M in size
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
client_max_body_size ${config.services.matrix-synapse.settings.max_upload_size};
'';
};
extraConfig = ''
# Make use of the ngx_http_realip_module to set the $remote_addr and
# $remote_port to the client address and client port, when using proxy
@ -94,6 +112,19 @@
proxyPass = "http://localhost:8080";
};
locations."~ ^/api/admin" = {
proxyPass = "http://localhost:8082";
extraConfig = ''
# Restrict access to admin API.
allow 185.161.129.132/32; # z9
allow 2a07:c480:0:100::/56; # z9
allow 2a07:c481:1::/48; # z9 new ipv6
allow 213.240.180.39/32; # stbe home
allow 2a01:170:118b::1/64; # stbe home
deny all;
'';
};
extraConfig = ''
# Make use of the ngx_http_realip_module to set the $remote_addr and
# $remote_port to the client address and client port, when using proxy

View file

@ -5,6 +5,7 @@
./branding-resources.hamburg.ccc.de.nix
./c3cat.de.nix
./cryptoparty-hamburg.de.nix
./element-admin.hamburg.ccc.de.nix
./element.hamburg.ccc.de.nix
./hacker.tours.nix
./hackertours.hamburg.ccc.de.nix

View file

@ -0,0 +1,115 @@
{ config, pkgs, ... }:
let
elementAdminVersion = "0.1.4";
elementAdmin = pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "element-admin";
version = elementAdminVersion;
src = pkgs.fetchzip {
url = "https://github.com/element-hq/element-admin/archive/refs/tags/v${elementAdminVersion}.zip";
sha256 = "sha256-dTHE0rg7W0k4e12s3v8yD/rBOYpIEqNN1VV4P3KtpQs=";
};
nativeBuildInputs = [
pkgs.nodejs
pkgs.pnpm.configHook
];
pnpmDeps = pkgs.pnpm.fetchDeps {
inherit (finalAttrs) pname version src;
fetcherVersion = 2;
hash = "sha256-YBSZIHNffS3Um0imYNmX9c1q193rphr+8lQ4tp7AcXw=";
};
buildPhase = ''
pnpm build
'';
installPhase = ''
cp -a dist $out
'';
});
in
{
services.nginx = {
enable = true;
virtualHosts."acme-element-admin.hamburg.ccc.de" = {
enableACME = true;
serverName = "element-admin.hamburg.ccc.de";
listen = [
{
addr = "0.0.0.0";
port = 31820;
}
];
};
virtualHosts."element-admin.hamburg.ccc.de" = {
forceSSL = true;
useACMEHost = "element-admin.hamburg.ccc.de";
listen = [
{
addr = "0.0.0.0";
port = 8443;
ssl = true;
proxyProtocol = true;
}
];
root = elementAdmin;
locations."/assets" = {
extraConfig = ''
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
# Security headers.
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src *; object-src 'none'; media-src 'self'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" always;
'';
};
locations."/" = {
index = "/index.html";
tryFiles = "$uri $uri/ /";
extraConfig = ''
# Security headers.
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src *; object-src 'none'; media-src 'self'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" always;
'';
};
extraConfig = ''
# Security headers.
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src *; object-src 'none'; media-src 'self'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" always;
# Make use of the ngx_http_realip_module to set the $remote_addr and
# $remote_port to the client address and client port, when using proxy
# protocol.
# First set our proxy protocol proxy as trusted.
set_real_ip_from 172.31.17.140;
# Then tell the realip_module to get the addreses from the proxy protocol
# header.
real_ip_header proxy_protocol;
'';
};
};
networking.firewall.allowedTCPPorts = [ 8443 31820 ];
}