diff --git a/machines/koyomi/configuration.nix b/machines/koyomi/configuration.nix index 0898a80..34fa005 100644 --- a/machines/koyomi/configuration.nix +++ b/machines/koyomi/configuration.nix @@ -2,14 +2,13 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -{ config, lib, pkgs, ... }: - { imports = [ ./hardware-configuration.nix ../../modules ./services/hypervisor.nix + ./services/haproxy.nix ]; sbruder = { diff --git a/machines/koyomi/services/haproxy.nix b/machines/koyomi/services/haproxy.nix new file mode 100644 index 0000000..100bd60 --- /dev/null +++ b/machines/koyomi/services/haproxy.nix @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2024 Simon Bruder +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +{ config, lib, pkgs, ... }: +let + baseDomain = "koyomi.sbruder.de"; + backends = { }; + + fallbackCert = pkgs.runCommandNoCC "fallback-cert" { } '' + cat > openssl.cnf << EOF + [ ca ] + default_ca = CA_default + + [ CA_default ] + database = database + new_certs_dir = . + serial = serial + + default_md = default + policy = policy_default + + [ policy_default ] + EOF + echo 01 > serial + touch database + ${pkgs.openssl}/bin/openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out fallback.key + ${pkgs.openssl}/bin/openssl req -key fallback.key -new -out fallback.csr -subj "/" + ${pkgs.openssl}/bin/openssl ca -batch -config openssl.cnf -in fallback.csr -keyfile fallback.key -selfsign -out fallback.crt -startdate 19700101000000Z -enddate 20380119031407Z + + mkdir $out + cat fallback.{key,crt} > $out/full.pem + mv fallback.{crt,key} $out + ''; +in +{ + services.haproxy = { + enable = true; + config = '' + global + stats socket /var/run/haproxy/haproxy-admin.sock mode 600 level admin + stats timeout 2m + + defaults + timeout client 30s + timeout server 30s + timeout connect 30s + + resolvers system + parse-resolv-conf + + frontend http-in + bind :80 + mode http + ${lib.concatStrings (lib.mapAttrsToList (name: domains: '' + use_backend http-${name} if { hdr(Host) -i ${lib.concatStringsSep " " domains} and path_beg '/.well-known/acme-challenge/' } + '') backends)} + default_backend https-redirect + + frontend https-in + bind :443 + mode tcp + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + tcp-request content reject if WAIT_END + ${lib.concatStrings (lib.mapAttrsToList (name: domains: '' + use_backend https-${name} if { req.ssl_sni -i ${lib.concatStringsSep " " domains} } + '') backends)} + default_backend https-fallback + + frontend v6-in + bind [::]:80 + bind [::]:443 ssl crt ${fallbackCert}/full.pem + mode http + http-request return status 400 content-type text/html string "

400 Bad Request

For requests over IPv6, please use the address of the virtual machine directly." + + frontend fallback + bind /var/run/haproxy/fallback.sock ssl crt ${fallbackCert}/full.pem + mode http + + frontend stats + bind ${config.sbruder.wireguard.home.address}:8404 + mode http + http-request use-service prometheus-exporter if { path /metrics } + stats enable + stats uri /stats + stats refresh 10s + + backend https-redirect + mode http + http-request redirect scheme https + + backend https-fallback + server fallback /var/run/haproxy/fallback.sock + + ${lib.concatStrings (lib.mapAttrsToList (name: domains: '' + backend http-${name} + mode http + server ${name} ${name}.${baseDomain}:80 resolvers system resolve-prefer ipv4 send-proxy-v2 + '') backends)} + + ${lib.concatStrings (lib.mapAttrsToList (name: domains: '' + backend https-${name} + mode tcp + server ${name} ${name}.${baseDomain}:443 resolvers system resolve-prefer ipv4 send-proxy-v2 + '') backends)} + ''; + }; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; +} diff --git a/machines/renge/services/prometheus.nix b/machines/renge/services/prometheus.nix index 347b016..c10866d 100644 --- a/machines/renge/services/prometheus.nix +++ b/machines/renge/services/prometheus.nix @@ -188,6 +188,17 @@ in } ]; } + { + job_name = "haproxy"; + static_configs = mkStaticTargets [ + "koyomi.vpn.sbruder.de:8404" + ]; + relabel_configs = lib.singleton { + target_label = "instance"; + source_labels = lib.singleton "__address__"; + regex = "(.*)\\.vpn\\.sbruder\\.de:8404"; + }; + } ]; rules = diff --git a/modules/nginx.nix b/modules/nginx.nix index 8e2553a..03b2934 100644 --- a/modules/nginx.nix +++ b/modules/nginx.nix @@ -11,6 +11,14 @@ in hardening.enable = lib.mkEnableOption "nginx hardening"; privacy.enable = (lib.mkEnableOption "nginx privacy options") // { default = true; }; recommended.enable = (lib.mkEnableOption "recommended options") // { default = true; }; + proxyv4 = { + enable = (lib.mkEnableOption "PROXY protocol for IPv4 connections"); + trustedAddresses = (lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Trusted addresses which can override the source address"; + default = [ "10.0.0.0/8" "127.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ]; + }); + }; }; config = lib.mkMerge [ @@ -39,5 +47,22 @@ in recommendedTlsSettings = lib.mkDefault true; }; }) + (lib.mkIf cfg.proxyv4.enable { + services.nginx = { + commonHttpConfig = (lib.concatMapStrings + (address: '' + set_real_ip_from ${address}; + '') + cfg.proxyv4.trustedAddresses) + '' + real_ip_header proxy_protocol; + ''; + defaultListen = [ + { addr = "[::]"; port = 80; ssl = false; } + { addr = "0.0.0.0"; port = 80; proxyProtocol = true; ssl = false; } + { addr = "[::]"; port = 443; ssl = true; } + { addr = "0.0.0.0"; port = 443; proxyProtocol = true; ssl = true; } + ]; + }; + }) ]; }