{ config, lib, ... }: let cfg = config.services.proxy; canonicalize = builtins.replaceStrings [ "*" "." ":" "[" "]" ] [ "all" "_" "_" "" "" ]; in { options.services.proxy = { enable = lib.mkOption { default = false; description = "whether to enable proxy"; type = lib.types.bool; }; proxyHosts = lib.mkOption { type = lib.types.listOf (lib.types.submodule { options = { hostNames = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Proxy these hostNames. ''; }; proxyTo = lib.mkOption { type = lib.types.submodule { options = { host = lib.mkOption { type = with lib.types; nullOr str; default = null; description = '' Host to forward traffic to. Any hostname may only be used once ''; }; httpPort = lib.mkOption { type = lib.types.port; default = 80; description = '' Port to forward http to. ''; }; httpsPort = lib.mkOption { type = lib.types.port; default = 443; description = '' Port to forward http to. ''; }; proxyHttpPort = lib.mkOption { type = lib.types.port; default = 8080; description = '' Port to forward http to when using the proxy protocol. ''; }; proxyHttpsPort = lib.mkOption { type = lib.types.port; default = 8443; description = '' Port to forward http to when using the proxy protocol. ''; }; }; }; description = '' { host = /* ip or fqdn */; httpPort = 80; httpsPort = 443; } to proxy to ''; default = { }; }; proxyProtocol = lib.mkOption { type = lib.types.bool; default = false; description = "Whether to use proxy protocol to connect to the server."; }; matchArg = lib.mkOption { type = lib.types.str; default = ""; description = "Optional argument to HAProxy `req.ssl_sni -i`"; }; }; }); default = [ ]; example = [{ hostNames = [ "test.hq.c3d2.de" "test.c3d2.de" ]; proxyTo = { host = "172.22.99.99"; httpPort = 80; httpsPort = 443; }; }]; }; }; config = lib.mkIf cfg.enable { assertions = [{ assertion = let proxyTo = lib.forEach cfg.proxyHosts (x: x.proxyTo.host); uniqueProxyTo = lib.unique proxyTo; in (builtins.length proxyTo) == (builtins.length uniqueProxyTo); message = "proxyTo must be unique across the list of proxyHosts. Insert your domain you want to proxy to the list of hostNames instead."; }]; services.haproxy = { enable = true; config = '' # TODO: upstream to nixos-modules global ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets ssl-dh-param-file ${config.security.dhparams.params.nginx.path} defaults timeout client 30000 timeout connect 5000 timeout check 5000 timeout server 30000 frontend http-in # tfo is tcp fastopen bind :::80 tfo v4v6 option http-keep-alive default_backend proxy-backend-http backend proxy-backend-http mode http option http-server-close option forwardfor http-request set-header X-Forwarded-Proto http http-request set-header X-Forwarded-Port 80 ${lib.concatMapStrings ({ proxyTo, proxyProtocol, hostNames, matchArg }: lib.optionalString (hostNames != [ ] && proxyTo.host != null) ( lib.concatMapStrings (hostname: '' use-server ${canonicalize hostname}-http if { req.hdr(host) -i ${matchArg} ${hostname} } server ${canonicalize hostname}-http ${proxyTo.host}:${ if proxyProtocol then "${toString proxyTo.proxyHttpPort} check send-proxy-v2" else "${toString proxyTo.httpPort} check" } '') hostNames ) ) cfg.proxyHosts } frontend https-in # tfo is tcp fastopen bind :::443 tfo v4v6 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } ${lib.concatMapStrings ({ proxyTo, hostNames, matchArg, ... }: lib.concatMapStrings (hostname: '' use_backend ${canonicalize proxyTo.host}-https if { req.ssl_sni -i ${matchArg} ${hostname} } '') hostNames ) cfg.proxyHosts} ${lib.concatMapStrings ({ proxyTo, proxyProtocol, ... }: '' backend ${canonicalize proxyTo.host}-https server ${canonicalize proxyTo.host}-https ${proxyTo.host}:${ if proxyProtocol then "${toString proxyTo.proxyHttpsPort} check send-proxy-v2" else "${toString proxyTo.httpsPort} check" } '') cfg.proxyHosts} ''; }; }; }