From 2da8af253dafbd6fc54b6e3b80407e7e9eea5954 Mon Sep 17 00:00:00 2001 From: nyanloutre Date: Wed, 8 Apr 2020 12:45:36 +0200 Subject: [PATCH] migrate haproxy -> nginx --- services/errorfiles/503.html | 39 ----- services/haproxy-acme.nix | 158 ------------------- services/site-max.nix | 42 ----- systems/LoutreOS/configuration.nix | 2 +- systems/LoutreOS/services.nix | 2 - systems/LoutreOS/web.nix | 240 ++++++++++++++++++++--------- 6 files changed, 169 insertions(+), 314 deletions(-) delete mode 100644 services/errorfiles/503.html delete mode 100644 services/haproxy-acme.nix delete mode 100644 services/site-max.nix diff --git a/services/errorfiles/503.html b/services/errorfiles/503.html deleted file mode 100644 index c511c00..0000000 --- a/services/errorfiles/503.html +++ /dev/null @@ -1,39 +0,0 @@ -HTTP/1.0 503 Service Unavailable -Cache-Control: no-cache -Connection: close -Content-Type: text/html - - - - - - - - 503 Service Unavailable - - - - - - - -

503 Service non disponible

- -

Impossible de contacter le serveur demandé

- - diff --git a/services/haproxy-acme.nix b/services/haproxy-acme.nix deleted file mode 100644 index f7b16e3..0000000 --- a/services/haproxy-acme.nix +++ /dev/null @@ -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 - ]; - - }; -} diff --git a/services/site-max.nix b/services/site-max.nix deleted file mode 100644 index da900b1..0000000 --- a/services/site-max.nix +++ /dev/null @@ -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"; - }; - }; - }; - }; - }; -} diff --git a/systems/LoutreOS/configuration.nix b/systems/LoutreOS/configuration.nix index a1d8c67..fb52a44 100644 --- a/systems/LoutreOS/configuration.nix +++ b/systems/LoutreOS/configuration.nix @@ -90,7 +90,7 @@ in }; firewall = { - allowedTCPPorts = [ ]; + allowedTCPPorts = [ 80 443 ]; allowedUDPPorts = [ ]; interfaces.eno2 = { allowedTCPPorts = [ diff --git a/systems/LoutreOS/services.nix b/systems/LoutreOS/services.nix index c0d1a5e..9ecd136 100644 --- a/systems/LoutreOS/services.nix +++ b/systems/LoutreOS/services.nix @@ -24,9 +24,7 @@ in { imports = [ - ../../services/haproxy-acme.nix ../../services/mail-server.nix - ../../services/site-max.nix ../../services/auto-pr.nix ../../services/python-ci.nix ../../services/sdtdserver.nix diff --git a/systems/LoutreOS/web.nix b/systems/LoutreOS/web.nix index d98099f..53f019e 100644 --- a/systems/LoutreOS/web.nix +++ b/systems/LoutreOS/web.nix @@ -2,88 +2,147 @@ 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 - domaine = "nyanlout.re"; + nginxSsoAuth = pkgs.writeText "nginx-sso_auth.inc" '' + # Protect this location using the auth_request + auth_request /sso-auth; - jellyfin_backend = '' - http-request set-header X-Forwarded-Port %[dst_port] - http-request add-header X-Forwarded-Proto https if { ssl_fc } - ''; - sonarr_acl = '' - acl API path_beg /api - ''; - sonarr_auth = '' - !AUTH_OK !API + # Redirect the user to the login page when they are not logged in + error_page 401 = @error401; + + location /sso-auth { + # Do not allow requests from outside + internal; + + # Access /auth endpoint to query login state + 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 { + security.acme = { + email = "paul@nyanlout.re"; + acceptTerms = true; + }; + services = { - haproxy-acme = { - enable = 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; - nginx = { enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; recommendedProxySettings = true; - appendHttpConfig = '' - set_real_ip_from 127.0.0.1; - real_ip_header X-Forwarded-For; + recommendedTlsSettings = true; + commonHttpConfig = '' + 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 = { - "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" = { - listen = [ { addr = "127.0.0.1"; port = 10003; } ]; + default = true; + enableACME = true; + forceSSL = true; locations = { "/" = { alias = "/var/www/site-perso/"; }; + "/errorpages/" = { + alias = "/var/www/errorpages/"; + }; "/.well-known/openpgpkey/" = { alias = "/var/lib/gnupg/wks/nyanlout.re"; 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" = { - listen = [ { addr = "127.0.0.1"; port = 10004; } ]; + enableACME = true; + forceSSL = true; locations = { "/" = { 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; - - site-max = { - enable = true; - port = 10007; - domaine = "maxspiegel.fr"; - }; }; systemd.services.site-musique = let