Merge branch 'reverseproxy' into 'main'

reverseproxy: add alias snl et nekorale

See merge request nounous/nixos!72
merge-requests/68/merge
Pyjacpp 2026-07-25 17:59:01 +02:00
commit f93cfa4f85
4 changed files with 337 additions and 137 deletions

View File

@ -1,4 +1,7 @@
{ pkgs, ... }: {
pkgs,
...
}:
let let
formatJSON = pkgs.formats.json { }; formatJSON = pkgs.formats.json { };
@ -128,28 +131,94 @@ let
]; ];
}; };
fetchFromCrans =
opts:
pkgs.fetchFromGitLab (
{
domain = "gitlab.adm.crans.org";
owner = "nounous";
}
// opts
);
installPartySite = pkgs.python3Packages.buildPythonApplication {
name = "site-install-party";
pyproject = false;
src = fetchFromCrans {
repo = "site-install-party";
rev = "master";
hash = "sha256-KVB4m/ms0WuArrkLn05INVLqhaGxzCg30GMICI6tM5E=";
};
build-system = with pkgs.python3Packages; [
mkdocs
mkdocs-material
];
buildPhase = ''
python3 -m mkdocs -- build -d $out
'';
};
homepagePkg = pkgs.stdenv.mkDerivation {
name = "site-homepage";
src = fetchFromCrans {
repo = "homepage";
rev = "master";
hash = "sha256-e9tttzKEWcBhogQY2ITDqqTbix/zcnBAylj1eKY791E=";
};
nativeBuildInputs = with pkgs; [
hugo
];
buildPhase = ''
hugo build -d $out
'';
};
in in
{ {
crans = { crans = {
reverseProxy = { reverseProxy = {
enable = true; enable = true;
virtualHosts = { virtualHosts = {
"autoconfig".serveLocalFiles = ./staticsites/autoconfig;
"cas".proxyPass = "172.16.10.120";
"collabora" = { "collabora" = {
target = "172.16.10.149"; proxyPass = "172.16.10.149";
proxyWebsockets = true; proxyWebsockets = true;
}; };
"eclat" = { "eclat" = {
anubisConfig = "${anubisMirrors}"; anubisConfig = "${anubisMirrors}";
httpOnly = true; httpOnly = true;
target = "172.16.10.104"; proxyPass = "172.16.10.104";
}; };
"eclats" = { "eclats" = {
anubisConfig = "${anubisMirrors}"; anubisConfig = "${anubisMirrors}";
target = "172.16.10.104"; proxyPass = "172.16.10.104";
};
"element".proxyPass = "172.16.10.118";
"ethercalc" = {
proxyPass = "172.16.10.133:8000";
serverAliases = [ "excel" ];
};
"framadate".proxyPass = "172.16.10.109";
"grafana" = {
proxyPass = "172.16.10.121:3000";
serverAliases = [ "fyre" ];
};
"imprimante" = {
proxyPass = "172.16.10.131";
serverAliases = [ "helloworld" ];
};
"intranet" = {
proxyPass = "172.16.10.156";
serverAliases = [ "re2o" ];
}; };
"install-party" = { "install-party" = {
anubisConfig = "${anubisChallenge}"; anubisConfig = "${anubisChallenge}";
target = "/var/www/install-party.crans.org"; serveLocalFiles = installPartySite;
serverAliases = [ serverAliases = [
"i-p" "i-p"
"adopteunmanchot" "adopteunmanchot"
@ -158,38 +227,68 @@ in
}; };
"lists" = { "lists" = {
anubisConfig = "${anubisChallenge}"; anubisConfig = "${anubisChallenge}";
target = "172.16.10.110"; proxyPass = "172.16.10.110";
}; };
"mediawiki" = { "mediawiki" = {
anubisConfig = "${anubisChallenge}"; anubisConfig = "${anubisChallenge}";
target = "172.16.10.144"; proxyPass = "172.16.10.144";
serverAliases = [ serverAliases = [ "mediakiwi" ];
"mediakiwi"
];
}; };
"mirrors" = { "mirrors" = {
anubisConfig = "${anubisMirrors}"; anubisConfig = "${anubisMirrors}";
target = "172.16.10.104"; proxyPass = "172.16.10.104";
}; };
"mirror" = { "mirror" = {
anubisConfig = "${anubisMirrors}"; anubisConfig = "${anubisMirrors}";
httpOnly = true; httpOnly = true;
target = "172.16.10.104"; proxyPass = "172.16.10.104";
};
"nekorale".globalRedirect = "perso.crans.org/club-nekorale";
"nextcloud".proxyPass = "172.16.10.146";
"owncloud".proxyPass = "172.16.10.136";
"pad".proxyPass = "172.16.10.130:9001";
"pdf" = {
proxyPass = "172.16.10.140";
serverAliases = [
"livre"
"stirling"
"stirling-pdf"
];
}; };
"perso" = { "perso" = {
anubisConfig = "${anubisPerso}"; anubisConfig = "${anubisPerso}";
target = "172.16.10.31"; proxyPass = "172.16.10.31";
serverAliases = [ serverAliases = [ "clubs" ];
"clubs" };
]; "pot-vieux".globalRedirect = "perso.crans.org/club-vieux";
"re2o-dev".proxyPass = "172.16.10.166";
"services".serveLocalFiles = fetchFromCrans {
repo = "services-page";
rev = "master";
hash = "sha256-ov4r6Oeta+vRcEv8bp7lFQg+2n4JTG4buetram5kN08=";
};
"snl".globalRedirect = "perso.crans.org/sonetlumens";
"test-melon".proxyPass = "172.16.10.150:8080";
"tmpad".proxyPass = "172.16.10.130:9002";
"vaultwarden" = {
proxyPass = "172.16.10.159";
serverAliases = [ "pass" ];
};
"webirc".proxyPass = "172.16.10.31:9000";
"webmail" = {
proxyPass = "172.16.10.107";
serverAliases = [ "roundcube" ];
}; };
"wiki" = { "wiki" = {
anubisConfig = "${anubisChallenge}"; anubisConfig = "${anubisChallenge}";
target = "172.16.10.161"; proxyPass = "172.16.10.161";
serverAliases = [ serverAliases = [ "wikipedia" ];
"wikipedia"
];
}; };
"www" = {
serveLocalFiles = homepagePkg;
serverAliases = [ "." ];
};
"zero".proxyPass = "172.16.10.130";
}; };
}; };
@ -197,4 +296,5 @@ in
acme.enable = true; acme.enable = true;
}; };
}; };
services.nginx.virtualHosts."www-alias-crans.org".default = true;
} }

View File

@ -0,0 +1,23 @@
<clientConfig version="1.0">
<emailProvider id="crans.org">
<domain>crans.org</domain>
<domain>crans.eu</domain>
<domain>crans.fr</domain>
<displayName>Cr@ns Mail</displayName>
<displayShortName>Cr@ns</displayShortName>
<incomingServer type="imap">
<hostname>imap.crans.org</hostname>
<port>993</port>
<socketType>SSL</socketType>
<username>%EMAILLOCALPART%</username>
<authentication>plain</authentication>
</incomingServer>
<outgoingServer type="smtp">
<hostname>smtp.crans.org</hostname>
<port>465</port>
<socketType>SSL</socketType>
<username>%EMAILLOCALPART%</username>
<authentication>plain</authentication>
</outgoingServer>
</emailProvider>
</clientConfig>

View File

@ -24,6 +24,8 @@ in
dnsPropagationCheck = false; dnsPropagationCheck = false;
}; };
# Certificat wildcard par défaut pour les services utilisant acme
# TODO: À déplacer sur les VMs qui en ont besoin (e.g. coturn…).
certs."crans.org" = { certs."crans.org" = {
domain = "*.crans.org"; domain = "*.crans.org";
dnsProvider = "rfc2136"; dnsProvider = "rfc2136";

View File

@ -1,5 +1,4 @@
{ {
pkgs,
lib, lib,
config, config,
... ...
@ -8,28 +7,6 @@
let let
cfg = config.crans.reverseProxy; cfg = config.crans.reverseProxy;
formatJSON = pkgs.formats.json { };
allowAll = formatJSON.generate "allow_all.json" {
bots = [
{
name = "allow_all";
path_regex = ".*";
action = "ALLOW";
}
];
};
open_graph = formatJSON.generate "opengraph.json" {
openGraph = [
{
enabled = true;
considerHost = true;
ttl = "24h";
}
];
};
mainTld = "org"; mainTld = "org";
otherTld = [ otherTld = [
"fr" "fr"
@ -65,18 +42,36 @@ in
''; '';
}; };
target = mkOption { proxyPass = mkOption {
type = types.str; type = types.nullOr types.str;
default = ""; default = null;
description = '' description = ''
Indique la destination. Il peut s'agir du chemin vers des fichiers statiques. Indique la destination à proxy.
''; '';
example = "172.16.10.128:8000"; example = "172.16.10.128:8000";
}; };
serveLocalFiles = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Chemin vers un dossier à exposer statiquement.
'';
example = "/var/local/adopter-un-manchot";
};
globalRedirect = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Si définie, toutes les requêtes sont redirigées (via 301) sur cet hôte.
'';
example = "perso.crans.org/club";
};
anubisConfig = mkOption { anubisConfig = mkOption {
type = types.str; type = types.nullOr types.str;
default = ""; default = null;
description = '' description = ''
Chemin du fichier de configuration Chemin du fichier de configuration
''; '';
@ -95,7 +90,7 @@ in
type = types.bool; type = types.bool;
default = false; default = false;
description = '' description = ''
Interdit les connexions en ssh Interdit les connexions en https
''; '';
example = "true"; example = "true";
}; };
@ -134,107 +129,187 @@ in
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
}) cfg.virtualHosts; }) cfg.virtualHosts;
# On fait un certificat commun pour tous les hosts
security.acme.certs."all.crans.all" = mkIf cfg.enable {
domain = "*.crans.${mainTld}";
dnsProvider = "rfc2136";
# Contient le serveur à contacter avec le protocole
# et le mot de passe
environmentFile = config.age.secrets.acme-env.path;
extraDomainNames =
lib.concatMap (tld: [
"*.crans.${tld}"
"crans.${tld}"
]) otherTld
++ [
"crans.${mainTld}"
];
group = config.services.nginx.group;
};
# Création des dossiers de logs
system.activationScripts = {
nginx-logs = {
text =
lib.concatMapStrings (f: "mkdir -p /var/log/nginx/${f}/\n") (
[ "anubis" ] ++ (lib.map (hn: "host/${hn}") (builtins.attrNames cfg.virtualHosts))
)
+ "chown -R ${config.services.nginx.user}:${config.services.nginx.group} /var/log/nginx/\n";
};
};
services = mkIf cfg.enable { services = mkIf cfg.enable {
anubis = { anubis = {
defaultOptions.group = "nginx"; defaultOptions.group = "nginx";
instances = lib.mapAttrs (vhostName: vhostConfig: { instances = lib.mapAttrs (
enable = true; vhostName: vhostConfig:
settings = { mkIf (vhostConfig.anubisConfig != null) {
BIND = "/run/anubis/anubis-${vhostName}/socket.sock"; enable = true;
BIND_NETWORK = "unix"; settings = {
METRICS_BIND = "/run/anubis/anubis-${vhostName}/anubis-${vhostName}-metrics.sock"; BIND = "/run/anubis/anubis-${vhostName}/socket.sock";
TARGET = "unix:///run/nginx/nginx-${vhostName}.sock"; BIND_NETWORK = "unix";
COOKIE_DOMAIN = "crans.org"; METRICS_BIND = "/run/anubis/anubis-${vhostName}/anubis-${vhostName}-metrics.sock";
REDIRECT_DOMAINS = "${vhostName}.crans.org"; TARGET = "unix:///run/nginx/nginx-${vhostName}.sock";
SOCKET_MODE = "0660"; COOKIE_DOMAIN = "crans.${mainTld}";
# OpenGraph config REDIRECT_DOMAINS = "${vhostName}.crans.${mainTld}";
OG_PASSTHROUGH = vhostConfig.anubisOpenGraph; SOCKET_MODE = "0660";
OG_EXPIRY_TIME = "24h"; # OpenGraph config
OG_CACHE_CONSIDER_HOST = true; OG_PASSTHROUGH = vhostConfig.anubisOpenGraph;
# Policy config OG_EXPIRY_TIME = "24h";
POLICY_FNAME = if (vhostConfig.anubisConfig == "") then "${allowAll}" else vhostConfig.anubisConfig; OG_CACHE_CONSIDER_HOST = true;
}; # Policy config
}) cfg.virtualHosts; POLICY_FNAME = vhostConfig.anubisConfig;
};
}
) cfg.virtualHosts;
}; };
logrotate.settings =
let
# https://github.com/NixOS/nixpkgs/blob/nixos-26.05/nixos/modules/services/web-servers/nginx/default.nix
base = {
su = "${config.services.nginx.user} ${config.services.nginx.group}";
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
frequency = "daily";
ifempty = true;
compress = true;
delaycompress = true;
dateyesterday = true;
};
in
{
nginx-anubis = base // {
files = [ "/var/log/nginx/anubis/*.log" ];
rotate = 7; # une semaine de log pour débugguer
};
nginx-hosts = base // {
files = [ "/var/log/nginx/host/*/*.log" ];
rotate = 173; # 6 mois de logs
dateext = true;
dateformat = "%Y-%m-%d";
};
};
nginx = nginx =
let let
# Configuration du serveur principal. configVhost =
mainConfig = lib.mapAttrs' (
vhostName: vhostConfig: vhostName: vhostConfig:
lib.nameValuePair (vhostName + "-anubis") { let
enableACME = !vhostConfig.httpOnly; sslConf = {
forceSSL = !vhostConfig.httpOnly; forceSSL = !vhostConfig.httpOnly;
rejectSSL = vhostConfig.httpOnly; rejectSSL = vhostConfig.httpOnly;
locations."/" = { useACMEHost = mkIf (!vhostConfig.httpOnly) "all.crans.all";
proxyPass = "http://unix:/run/anubis/anubis-${vhostName}/socket.sock"; acmeRoot = null;
proxyWebsockets = vhostConfig.proxyWebsockets;
}; };
serverName = "${vhostName}.crans.${mainTld}";
extraConfig = "
set_real_ip_from 172.16.0.0/16;
set_real_ip_from fd00::/56;
real_ip_header X-Real-Ip;
";
}
) cfg.virtualHosts;
# Redirections # Conf pour lhôte en entrée
redirectConfig = lib.mapAttrs (vhostName: vhostConfig: { entryExtraConf = {
# Redirection vers d'autres machines extraConfig = ''
locations = mkIf (!lib.strings.hasPrefix "/" vhostConfig.target) { set_real_ip_from 172.16.0.0/16;
"/favicon.ico".root = "/var/www/logo/"; set_real_ip_from fd00::/56;
"/" = { real_ip_header X-Real-Ip;
proxyPass = "http://${vhostConfig.target}"; '';
proxyWebsockets = vhostConfig.proxyWebsockets;
};
};
# Redirection vers des fichiers locaux
root = mkIf (lib.strings.hasPrefix "/" vhostConfig.target) vhostConfig.target;
listen = [
{ addr = "unix:/run/nginx/nginx-${vhostName}.sock"; }
];
serverName = "${vhostName}.crans.${mainTld}";
extraConfig = "
set_real_ip_from unix:;
real_ip_header X-Real-IP;
";
}) cfg.virtualHosts;
# Génération des alias
getAliases = name: config: lib.foldr (
tld: acc:
acc
++
(lib.foldr (alias: acc: acc ++ ["${alias}.crans.${tld}"]) ["${name}.crans.${tld}"] config.serverAliases)
)
(lib.foldr (alias: acc: acc ++ ["${alias}.crans.${mainTld}"]) [] config.serverAliases)
otherTld;
# Configuration des alias
aliasConfig = lib.foldr (
vhost: acc:
acc
//
lib.foldr (
alias: acc:
acc
//
{
"${vhost.name}-alias-${alias}" = rec {
rejectSSL = vhost.value.httpOnly;
forceSSL = !rejectSSL;
enableACME = !rejectSSL;
serverName = alias;
globalRedirect = "${vhost.name}.crans.${mainTld}";
};
} }
) { } (getAliases vhost.name vhost.value) // sslConf;
) { } (lib.attrsToList cfg.virtualHosts); # Conf supplémentaire pour le proxy principal
vhostExtraConf =
if vhostConfig.anubisConfig != null then
# Il reçoit les requêtes dAnubis
{
listen = [
{ addr = "unix:/run/nginx/nginx-${vhostName}.sock"; }
];
extraConfig = ''
set_real_ip_from unix:;
real_ip_header X-Real-IP;
'';
}
else
# Il est en entrée
entryExtraConf;
logHostConf = ''
access_log /var/log/nginx/host/${vhostName}/access.log;
error_log /var/log/nginx/host/${vhostName}/error.log;
'';
# Les alias : vhostName × otherTld U serverAliases × allTld
mkHostName = als: tld: if als == "." then "crans.${tld}" else "${als}.crans.${tld}";
aliases =
lib.foldr
(
tld: acc:
acc
++ (lib.foldr (alias: acc: acc ++ [ (mkHostName alias tld) ]) [
(mkHostName vhostName tld)
] vhostConfig.serverAliases)
)
(lib.foldr (alias: acc: acc ++ [ (mkHostName alias mainTld) ]) [ ] vhostConfig.serverAliases)
otherTld;
in
{
# Configuration du service à proxy.
"${vhostName}" = vhostExtraConf // {
serverName = mkHostName vhostName mainTld;
locations."/" = mkIf (vhostConfig.proxyPass != null) {
proxyPass = "http://${vhostConfig.proxyPass}";
proxyWebsockets = vhostConfig.proxyWebsockets;
};
root = vhostConfig.serveLocalFiles;
globalRedirect = vhostConfig.globalRedirect;
extraConfig = vhostExtraConf.extraConfig + logHostConf;
};
# Entrée dAnubis
"${vhostName}-anubis" = mkIf (vhostConfig.anubisConfig != null) (
entryExtraConf
// {
serverName = mkHostName vhostName mainTld;
locations."/" = {
proxyPass = "http://unix:/run/anubis/anubis-${vhostName}/socket.sock";
proxyWebsockets = vhostConfig.proxyWebsockets;
};
extraConfig = entryExtraConf.extraConfig + ''
access_log /var/log/nginx/anubis/access.log;
error_log /var/log/nginx/anubis/error.log;
'';
}
);
}
# Les alias
// lib.mergeAttrsList (
lib.map (alias: {
"${vhostName}-alias-${alias}" = sslConf // {
serverName = alias;
globalRedirect = lib.defaultTo "${vhostName}.crans.${mainTld}" vhostConfig.globalRedirect;
extraConfig = logHostConf;
};
}) aliases
);
in in
{ {
enable = true; enable = true;
virtualHosts = redirectConfig // aliasConfig // mainConfig; virtualHosts = lib.concatMapAttrs configVhost cfg.virtualHosts;
}; };
}; };
}; };