migrate haproxy -> nginx

This commit is contained in:
nyanloutre 2020-04-08 12:45:36 +02:00
parent f86ef0518d
commit 2da8af253d
6 changed files with 169 additions and 314 deletions

View File

@ -1,39 +0,0 @@
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>503 Service Unavailable</title>
<meta name="description" content="Le serveur n'est pas joignable">
<meta name="author" content="Nyanloutre">
<style>
body {
background-image: url("https://storage.sbg3.cloud.ovh.net/v1/AUTH_5b4f5a8f8ed04ddb9725f42ae798cfab/images/1548346925-data-center-shot.jpg");
background-size: 100%;
color: white;
text-shadow: 0px 0px 3px black, 0px 0px 4px black;
text-align: center;
font-size: 2rem;
}
#travolta {
position: fixed;
height: 70%;
bottom: 0;
left: 40%;
}
</style>
</head>
<body>
<h1>503 Service non disponible</h1>
<img id="travolta" src="https://storage.sbg3.cloud.ovh.net/v1/AUTH_5b4f5a8f8ed04ddb9725f42ae798cfab/images/7VE.gif">
<p>Impossible de contacter le serveur demandé</p>
</body>
</html>

View File

@ -1,158 +0,0 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.services.haproxy-acme;
nginx_port = 54321;
haproxyConf = ''
global
log /dev/log local0
log /dev/log local1 notice
user haproxy
group haproxy
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
defaults
mode http
option forwardfor
option http-server-close
option httplog
option dontlognull
timeout client 10s
timeout connect 4s
timeout server 30s
timeout tunnel 3600s
errorfile 503 ${./errorfiles/503.html}
userlist LOUTRE
user paul password $6$YNjCpiPABu9$.iEp.3BgoswHcX3SMjz1/CiyqFQn/fjnxtT9CWBqQHBKynvK2kh/i62ije0WmCvhKRUhy9gdVbJStM3ciGXnC1
frontend http-in
bind :::80 v4v6
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if letsencrypt-acl
redirect scheme https code 301 if !letsencrypt-acl
frontend public
bind :::443 v4v6 ssl crt /var/lib/acme/${cfg.domaine}/full.pem alpn h2,http/1.1
http-response set-header Strict-Transport-Security max-age=15768000
http-request add-header X-Forwarded-Proto https
acl haproxy-acl path_beg /haproxy
use_backend haproxy_stats if haproxy-acl
${concatStrings (
mapAttrsToList (name: value:
" acl ${name}-acl hdr(host) -i ${name}\n"
+ " use_backend ${name}-backend if ${name}-acl\n"
) cfg.services)}
backend letsencrypt-backend
mode http
server letsencrypt 127.0.0.1:${toString nginx_port}
backend haproxy_stats
mode http
stats enable
stats hide-version
acl AuthOK_LOUTRE http_auth(LOUTRE)
http-request auth realm LOUTRE if !AuthOK_LOUTRE
${concatStrings (
mapAttrsToList (name: value:
''
backend ${name}-backend
mode http
${value.extraBackend}
${(
if value.socket == "" then
''
server ${name} ${value.ip}:${toString value.port}
''
else
''
server ${name} ${value.socket}
''
)}
${(if value.auth then (
value.extraAcls
+ ''
acl AUTH_OK http_auth(LOUTRE)
http-request auth realm LOUTRE if ${value.aclBool}
''
) else "")}
''
) cfg.services)}
'';
in
{
options.services.haproxy-acme = {
enable = mkEnableOption "HAproxy + ACME";
domaine = mkOption {
type = types.str;
example = "example.com";
description = ''
Sous domaine à utiliser
Il est necessaire d'avoir un enregistrement pointant sur la wildcard de ce domaine vers le serveur
'';
};
services = mkOption {
type = with types; attrsOf (submodule { options = {
ip = mkOption { type = str; description = "IP address"; };
port = mkOption { type = int; description = "Port number"; };
socket = mkOption { type = str; description = "Emplacement du socket"; default = ""; };
auth = mkOption { type = bool; description = "Enable authentification"; default = false; };
extraBackend = mkOption { type = str; description = "Options backend HaProxy suplémentaires"; default = ""; };
extraAcls = mkOption { type = str; description = "ACL HaProxy suplémentaires"; default = ""; };
aclBool = mkOption { type = str; description = "Logique d'authentification"; default = "!AUTH_OK"; };
}; });
example = ''
haproxy_backends = {
example = { ip = "127.0.0.1"; port = 1234; auth = false; };
};
'';
description = "Liste des noms de domaines associés à leur backend";
};
};
config = mkIf cfg.enable {
services.haproxy.enable = true;
services.haproxy.config = haproxyConf;
services.nginx.enable = true;
services.nginx.virtualHosts = {
"acme" = {
listen = [ { addr = "127.0.0.1"; port = nginx_port; } ];
locations = { "/" = { root = "/var/www/challenges"; }; };
};
};
security.acme.acceptTerms = true;
security.acme.certs = {
${cfg.domaine} = {
extraDomains = mapAttrs' (name: value:
nameValuePair ("${name}") (null)
) cfg.services;
webroot = "/var/www/challenges";
email = "paul@nyanlout.re";
allowKeysForGroup = true;
group = "acme";
postRun = ''
systemctl reload haproxy.service
nixos-container run vsftpd -- systemctl restart vsftpd
'';
};
};
users.groups.acme.members = [ "haproxy" ];
networking.firewall.allowedTCPPorts = [
80 443
];
};
}

View File

@ -1,42 +0,0 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.services.site-max;
in
{
options.services.site-max = {
enable = mkEnableOption "Site Max Spiegel";
port = mkOption {
type = types.int;
example = 54321;
description = "Local listening port";
};
domaine = mkOption {
type = types.str;
example = "example.com";
description = "Domaine à utiliser";
};
};
config = mkIf cfg.enable {
services.haproxy-acme.services = {
${cfg.domaine} = { ip = "127.0.0.1"; port = cfg.port; auth = false; };
};
services.nginx = {
virtualHosts = {
"max" = {
listen = [ { addr = "127.0.0.1"; port = cfg.port; } ];
locations."/" = {
root = "/run/python-ci/nyanloutre/site-max";
};
};
};
};
};
}

View File

@ -90,7 +90,7 @@ in
}; };
firewall = { firewall = {
allowedTCPPorts = [ ]; allowedTCPPorts = [ 80 443 ];
allowedUDPPorts = [ ]; allowedUDPPorts = [ ];
interfaces.eno2 = { interfaces.eno2 = {
allowedTCPPorts = [ allowedTCPPorts = [

View File

@ -24,9 +24,7 @@ in
{ {
imports = [ imports = [
../../services/haproxy-acme.nix
../../services/mail-server.nix ../../services/mail-server.nix
../../services/site-max.nix
../../services/auto-pr.nix ../../services/auto-pr.nix
../../services/python-ci.nix ../../services/python-ci.nix
../../services/sdtdserver.nix ../../services/sdtdserver.nix

View File

@ -2,88 +2,147 @@
with lib; with lib;
#### VHost table ####
# 10000 riot.nyanlout.re
# 10001 factorio.nyanlout.re
# 10002 minecraft.nyanlout.re
# 10003 nyanlout.re
# 10004 musique-meyenheim.fr
# 10005 social.nyanlout.re
# 10006 pgmanage.nyanlout.re
# 10007 maxspiegel.fr
####
let let
domaine = "nyanlout.re"; nginxSsoAuth = pkgs.writeText "nginx-sso_auth.inc" ''
# Protect this location using the auth_request
auth_request /sso-auth;
jellyfin_backend = '' # Redirect the user to the login page when they are not logged in
http-request set-header X-Forwarded-Port %[dst_port] error_page 401 = @error401;
http-request add-header X-Forwarded-Proto https if { ssl_fc }
''; location /sso-auth {
sonarr_acl = '' # Do not allow requests from outside
acl API path_beg /api internal;
'';
sonarr_auth = '' # Access /auth endpoint to query login state
!AUTH_OK !API proxy_pass http://127.0.0.1:${toString(config.services.nginx.sso.configuration.listen.port)}/auth;
# Do not forward the request body (nginx-sso does not care about it)
proxy_pass_request_body off;
proxy_set_header Content-Length "";
# Set custom information for ACL matching: Each one is available as
# a field for matching: X-Host = x-host, ...
proxy_set_header X-Origin-URI $request_uri;
proxy_set_header X-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# If the user is lead to /logout redirect them to the logout endpoint
# of ngninx-sso which then will redirect the user to / on the current host
location /sso-logout {
return 302 https://login.nyanlout.re/logout?go=$scheme://$http_host/;
}
# Define where to send the user to login and specify how to get back
location @error401 {
return 302 https://login.nyanlout.re/login?go=$scheme://$http_host$request_uri;
}
''; '';
nginxGetFirstLocalPort = vh: (findFirst (x: x.addr == "127.0.0.1") (throw "No local port found") config.services.nginx.virtualHosts.${vh}.listen).port; nginxSimpleReverse = rport: {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString(rport)}/";
};
};
nginxAuthReverse = rport: {
enableACME = true;
forceSSL = true;
extraConfig = ''
include ${nginxSsoAuth};
'';
locations."/" = {
proxyPass = "http://127.0.0.1:${toString(rport)}/";
extraConfig = ''
auth_request_set $cookie $upstream_http_set_cookie;
add_header Set-Cookie $cookie;
'';
};
};
in in
{ {
services = { security.acme = {
haproxy-acme = { email = "paul@nyanlout.re";
enable = true; acceptTerms = true;
domaine = domaine;
services = {
"grafana.${domaine}" = { ip = "127.0.0.1"; port = config.services.grafana.port; auth = true; };
"emby.${domaine}" = { ip = "127.0.0.1"; port = 8096; auth = false; extraBackend = jellyfin_backend; };
"radarr.${domaine}" = { ip = "127.0.0.1"; port = 7878; auth = true; extraAcls = sonarr_acl; aclBool = sonarr_auth; };
"sonarr.${domaine}" = { ip = "127.0.0.1"; port = 8989; auth = true; extraAcls = sonarr_acl; aclBool = sonarr_auth; };
"transmission.${domaine}" = { ip = "127.0.0.1"; port = config.services.transmission.port; auth = true; };
"syncthing.${domaine}" = { ip = "127.0.0.1"; port = 8384; auth = true; };
"jackett.${domaine}" = { ip = "127.0.0.1"; port = 9117; auth = true; };
"searx.${domaine}" = { ip = "127.0.0.1"; port = 8888; auth = false; };
"riot.${domaine}" = { ip = "127.0.0.1"; port = nginxGetFirstLocalPort "riot.nyanlout.re"; auth = false; };
"matrix.${domaine}" = { ip = "127.0.0.1"; port = 8008; auth = false; };
"pgmanage.${domaine}" = { ip = "127.0.0.1"; port = config.services.pgmanage.port; auth = true; };
"gitea.${domaine}" = { ip = "127.0.0.1"; port = config.services.gitea.httpPort; auth = false; };
"ci.${domaine}" = { ip = "127.0.0.1"; port = 52350; auth = false; };
"factorio.${domaine}" = { ip = "127.0.0.1"; port = nginxGetFirstLocalPort "factorio.nyanlout.re"; auth = false; };
"airsonic.${domaine}" = { ip = "127.0.0.1"; port = 4040; auth = false; };
"${domaine}" = { ip = "127.0.0.1"; port = nginxGetFirstLocalPort "nyanlout.re"; auth = false; };
"musique-meyenheim.fr" = { ip = "127.0.0.1"; port = nginxGetFirstLocalPort "musique-meyenheim.fr"; auth = false; };
"minecraft.${domaine}" = { ip = "127.0.0.1"; port = nginxGetFirstLocalPort "minecraft.nyanlout.re"; auth = false; };
};
}; };
searx.enable = true; services = {
nginx = { nginx = {
enable = true; enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true; recommendedProxySettings = true;
appendHttpConfig = '' recommendedTlsSettings = true;
set_real_ip_from 127.0.0.1; commonHttpConfig = ''
real_ip_header X-Forwarded-For; map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
add_header Strict-Transport-Security $hsts_header;
add_header Referrer-Policy origin-when-cross-origin;
error_page 500 502 503 504 https://nyanlout.re/errorpages/50x.html;
''; '';
sso = {
enable = true;
environmentFile = "/mnt/secrets/nginx-sso.env";
configuration = {
listen = {
addr = "127.0.0.1";
port = 8082;
};
login = {
title = "LoutreOS login";
default_method = "simple";
hide_mfa_field = true;
names.simple = "Username / Password";
};
cookie = {
domain = ".nyanlout.re";
secure = true;
};
audit_log = {
targets = [ "fd://stdout" ];
events = [ "access_denied" "login_success" "login_failure" "logout" ];
};
providers.simple = {
enable_basic_auth = true;
users = {
paul = "$2y$10$RMqeJF/hUasXZ5/SLKAu4uKKp6ac6qXCaRu4OY/fIN6ZYucDXzqYm";
};
groups = {
admins = [ "paul" ];
};
};
acl = {
rule_sets = [
{
rules = [ { field = "x-host"; regexp = ".*"; } ];
allow = [ "@admins" ];
}
];
};
};
};
virtualHosts = { virtualHosts = {
"riot.nyanlout.re" = {
listen = [ { addr = "127.0.0.1"; port = 10000; } ];
locations = { "/" = { root = pkgs.riot-web; }; };
};
"factorio.nyanlout.re" = {
listen = [ { addr = "127.0.0.1"; port = 10001; } ];
locations = { "/" = { root = "/var/www/factorio"; }; };
};
"minecraft.nyanlout.re" = {
listen = [ { addr = "127.0.0.1"; port = 10002; } ];
locations = { "/" = { root = "/var/www/minecraft-overviewer"; }; };
};
"nyanlout.re" = { "nyanlout.re" = {
listen = [ { addr = "127.0.0.1"; port = 10003; } ]; default = true;
enableACME = true;
forceSSL = true;
locations = { locations = {
"/" = { "/" = {
alias = "/var/www/site-perso/"; alias = "/var/www/site-perso/";
}; };
"/errorpages/" = {
alias = "/var/www/errorpages/";
};
"/.well-known/openpgpkey/" = { "/.well-known/openpgpkey/" = {
alias = "/var/lib/gnupg/wks/nyanlout.re"; alias = "/var/lib/gnupg/wks/nyanlout.re";
extraConfig = '' extraConfig = ''
@ -92,8 +151,24 @@ in
}; };
}; };
}; };
"riot.nyanlout.re" = {
enableACME = true;
forceSSL = true;
locations = { "/" = { root = pkgs.riot-web; }; };
};
"factorio.nyanlout.re" = {
enableACME = true;
forceSSL = true;
locations = { "/" = { root = "/var/www/factorio"; }; };
};
"minecraft.nyanlout.re" = {
enableACME = true;
forceSSL = true;
locations = { "/" = { root = "/var/www/minecraft-overviewer"; }; };
};
"musique-meyenheim.fr" = { "musique-meyenheim.fr" = {
listen = [ { addr = "127.0.0.1"; port = 10004; } ]; enableACME = true;
forceSSL = true;
locations = { locations = {
"/" = { "/" = {
proxyPass = "http://unix:/run/site-musique.sock"; proxyPass = "http://unix:/run/site-musique.sock";
@ -106,6 +181,33 @@ in
}; };
}; };
}; };
"maxspiegel.fr" = {
enableACME = true;
forceSSL = true;
locations."/" = {
root = "/run/python-ci/nyanloutre/site-max";
};
};
"login.nyanlout.re" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString(config.services.nginx.sso.configuration.listen.port)}/";
};
};
"grafana.nyanlout.re" = nginxAuthReverse config.services.grafana.port;
"transmission.nyanlout.re" = nginxAuthReverse config.services.transmission.port;
"radarr.nyanlout.re" = nginxAuthReverse 7878;
"sonarr.nyanlout.re" = nginxAuthReverse 8989;
"syncthing.nyanlout.re" = nginxAuthReverse 8384;
"jackett.nyanlout.re" = nginxAuthReverse 9117;
"pgmanage.nyanlout.re" = nginxAuthReverse config.services.pgmanage.port;
"matrix.nyanlout.re" = nginxSimpleReverse 8008;
"airsonic.nyanlout.re" = nginxSimpleReverse 4040;
"emby.nyanlout.re" = nginxSimpleReverse 8096;
"ci.nyanlout.re" = nginxSimpleReverse 52350;
"gitea.nyanlout.re" = nginxSimpleReverse config.services.gitea.httpPort;
}; };
}; };
@ -140,12 +242,6 @@ in
}; };
python-ci.enable = true; python-ci.enable = true;
site-max = {
enable = true;
port = 10007;
domaine = "maxspiegel.fr";
};
}; };
systemd.services.site-musique = let systemd.services.site-musique = let