From 9274618cf011b1ca91b04df15ab0c7fd28bb3b50 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Mon, 19 Feb 2024 15:16:49 -0800 Subject: [PATCH] chore(vouch): clean up local access --- modules/nixos/nginx-local.nix | 49 ---------- modules/nixos/nginx/local.nix | 94 +++++++++++++++++++ .../{nginx-vouch.nix => nginx/vouch.nix} | 60 ++++++++---- .../websocket.nix} | 0 nixos/access/kanidm.nix | 28 ++++-- nixos/access/vouch.nix | 51 +++++++--- systems/tei/cloudflared.nix | 4 - systems/tei/nixos.nix | 10 ++ tree.nix | 1 + 9 files changed, 208 insertions(+), 89 deletions(-) delete mode 100644 modules/nixos/nginx-local.nix create mode 100644 modules/nixos/nginx/local.nix rename modules/nixos/{nginx-vouch.nix => nginx/vouch.nix} (59%) rename modules/nixos/{nginx-websocket.nix => nginx/websocket.nix} (100%) diff --git a/modules/nixos/nginx-local.nix b/modules/nixos/nginx-local.nix deleted file mode 100644 index b68661c3..00000000 --- a/modules/nixos/nginx-local.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit (lib.modules) mkIf mkBefore; - inherit (lib.options) mkOption mkEnableOption; - inherit (lib.strings) concatMapStringsSep optionalString; - inherit (lib.lists) optionals; - inherit (config.services) tailscale; - inherit (config.networking.access) cidrForNetwork localaddrs; - localModule = { config, ... }: { - options = with lib.types; { - local = { - enable = mkEnableOption "local traffic only"; - }; - }; - config = mkIf config.local.enable { - extraConfig = let - mkAllow = cidr: "allow ${cidr};"; - allowAddresses = - cidrForNetwork.loopback.all - ++ cidrForNetwork.local.all - ++ optionals tailscale.enable cidrForNetwork.tail.all; - allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable '' - include ${localaddrs.stateDir}/*.nginx.conf; - ''; - in mkBefore '' - ${allows} - deny all; - ''; - }; - }; - hostModule = { config, ... }: { - imports = [ localModule ]; - - options = with lib.types; { - locations = mkOption { - type = attrsOf (submodule localModule); - }; - }; - }; -in { - options = with lib.types; { - services.nginx.virtualHosts = mkOption { - type = attrsOf (submodule hostModule); - }; - }; -} diff --git a/modules/nixos/nginx/local.nix b/modules/nixos/nginx/local.nix new file mode 100644 index 00000000..b035e02a --- /dev/null +++ b/modules/nixos/nginx/local.nix @@ -0,0 +1,94 @@ +{ + config, + lib, + ... +}: let + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkBefore mkOptionDefault; + inherit (lib.strings) concatMapStringsSep optionalString; + inherit (lib.lists) optionals; + inherit (config.services) tailscale; + inherit (config.networking.access) cidrForNetwork localaddrs; + localModule = { config, ... }: { + options.local = with lib.types; { + enable = mkOption { + type = bool; + description = "for local traffic only"; + defaultText = literalExpression "false"; + }; + denyGlobal = mkOption { + type = bool; + defaultText = literalExpression "config.local.enable"; + }; + trusted = mkOption { + type = bool; + defaultText = literalExpression "config.local.denyGlobal"; + }; + emitDenyGlobal = mkOption { + internal = true; + type = bool; + default = config.local.denyGlobal; + }; + }; + config = mkIf config.local.emitDenyGlobal { + extraConfig = let + mkAllow = cidr: "allow ${cidr};"; + allowAddresses = + cidrForNetwork.loopback.all + ++ cidrForNetwork.local.all + ++ optionals tailscale.enable cidrForNetwork.tail.all; + allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable '' + include ${localaddrs.stateDir}/*.nginx.conf; + ''; + in mkBefore '' + ${allows} + deny all; + ''; + }; + }; + locationModule = { config, virtualHost, ... }: { + imports = [ + localModule + ]; + + config.local = { + enable = mkOptionDefault virtualHost.local.enable; + denyGlobal = mkOptionDefault virtualHost.local.denyGlobal; + trusted = mkOptionDefault virtualHost.local.trusted; + emitDenyGlobal = virtualHost.local.emitDenyGlobal; + }; + }; + hostModule = { config, ... }: { + imports = [ localModule ]; + + options = with lib.types; { + locations = mkOption { + type = attrsOf (submoduleWith { + modules = [ locationModule ]; + shorthandOnlyDefinesConfig = true; + specialArgs = { + virtualHost = config; + }; + }); + }; + }; + + config.local = { + enable = mkOptionDefault false; + denyGlobal = mkOptionDefault config.local.enable; + trusted = mkOptionDefault config.local.denyGlobal; + }; + }; +in { + options = with lib.types; { + services.nginx.virtualHosts = mkOption { + type = attrsOf (submoduleWith { + modules = [ hostModule ]; + shorthandOnlyDefinesConfig = true; + specialArgs = { + nixosConfig = config; + }; + }); + }; + }; +} diff --git a/modules/nixos/nginx-vouch.nix b/modules/nixos/nginx/vouch.nix similarity index 59% rename from modules/nixos/nginx-vouch.nix rename to modules/nixos/nginx/vouch.nix index 922651a2..e6c17713 100644 --- a/modules/nixos/nginx-vouch.nix +++ b/modules/nixos/nginx/vouch.nix @@ -16,6 +16,10 @@ let type = str; default = "https://login.local.${networking.domain}"; }; + doubleProxy = mkOption { + type = bool; + default = true; + }; authUrl = mkOption { type = str; default = "https://id.${networking.domain}"; @@ -49,6 +53,7 @@ let in mkDefault "http://${host}:${toString port}"; authUrl = mkDefault vouch-proxy.authUrl; url = mkDefault vouch-proxy.url; + doubleProxy = mkDefault false; }; } { @@ -63,11 +68,21 @@ let ''; locations = { "/" = { - extraConfig = '' - add_header Access-Control-Allow-Origin ${config.vouch.url}; - add_header Access-Control-Allow-Origin ${config.vouch.authUrl}; - proxy_set_header X-Vouch-User $auth_resp_x_vouch_user; - ''; + extraConfig = mkMerge [ + '' + add_header Access-Control-Allow-Origin ${config.vouch.url}; + add_header Access-Control-Allow-Origin ${config.vouch.authUrl}; + '' + (mkIf config.local.enable '' + add_header Access-Control-Allow-Origin ${config.vouch.localUrl}; + '') + (mkIf (config.local.enable && tailscale.enable) '' + add_header Access-Control-Allow-Origin $scheme://${config.vouch.tailDomain}; + '') + '' + proxy_set_header X-Vouch-User $auth_resp_x_vouch_user; + '' + ]; }; "@error401" = { extraConfig = let @@ -78,32 +93,45 @@ let ''; tailVouchUrl = '' if ($http_host ~ "\.tail\.${networking.domain}$") { - set $vouch_url $scheme://${config.vouch.tailDomain}; + set $vouch_url $vouch_scheme://${config.vouch.tailDomain}; } ''; in mkMerge [ (mkBefore '' set $vouch_url ${config.vouch.url}; + set $vouch_scheme $scheme; '') + (mkIf config.local.trusted (mkBefore '' + if ($http_x_forwarded_proto) { + set $vouch_scheme $http_x_forwarded_proto; + } + '')) (mkIf (config.local.enable or false) localVouchUrl) (mkIf (config.local.enable or false && tailscale.enable) tailVouchUrl) '' - return 302 $vouch_url/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err; + return 302 $vouch_url/login?url=$vouch_scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err; '' ]; }; "/validate" = { recommendedProxySettings = false; proxyPass = "${config.vouch.proxyOrigin}/validate"; - extraConfig = '' - proxy_set_header Host $host; - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user; - auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt; - auth_request_set $auth_resp_err $upstream_http_x_vouch_err; - auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount; - ''; + extraConfig = mkMerge [ + (mkIf (!config.vouch.doubleProxy) '' + proxy_set_header Host $host; + '') + (mkIf config.vouch.doubleProxy '' + proxy_set_header X-Host $host; + '') + '' + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user; + auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt; + auth_request_set $auth_resp_err $upstream_http_x_vouch_err; + auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount; + '' + ]; }; }; }) diff --git a/modules/nixos/nginx-websocket.nix b/modules/nixos/nginx/websocket.nix similarity index 100% rename from modules/nixos/nginx-websocket.nix rename to modules/nixos/nginx/websocket.nix diff --git a/nixos/access/kanidm.nix b/nixos/access/kanidm.nix index 3b3ad9c8..d3a19b2a 100644 --- a/nixos/access/kanidm.nix +++ b/nixos/access/kanidm.nix @@ -7,10 +7,11 @@ let inherit (lib.options) mkOption; inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; - inherit (config.services) tailscale; - inherit (config.services.nginx) virtualHosts; + inherit (config) networking; + inherit (config.services) tailscale nginx; + inherit (nginx) virtualHosts; cfg = config.services.kanidm; - access = config.services.nginx.access.kanidm; + access = nginx.access.kanidm; proxyPass = mkDefault "https://${access.host}:${toString access.port}"; locations = { "/" = { @@ -20,6 +21,11 @@ let alias = "${cfg.server.unencrypted.package.ca}"; }; }; + localLocations = vouchDomain: { + "/".extraConfig = '' + proxy_redirect $scheme://${nginx.access.vouch.domain or "login.${networking.domain}"}/ $scheme://${vouchDomain}/; + ''; + }; in { imports = let inherit (meta) nixos; @@ -33,15 +39,15 @@ in { }; domain = mkOption { type = str; - default = "id.${config.networking.domain}"; + default = "id.${networking.domain}"; }; localDomain = mkOption { type = str; - default = "id.local.${config.networking.domain}"; + default = "id.local.${networking.domain}"; }; tailDomain = mkOption { type = str; - default = "id.tail.${config.networking.domain}"; + default = "id.tail.${networking.domain}"; }; port = mkOption { type = port; @@ -85,13 +91,19 @@ in { inherit (virtualHosts.${access.domain}) useACMEHost; addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL); local.enable = true; - inherit locations; + locations = mkMerge [ + locations + (localLocations nginx.access.vouch.localDomain or "login.local.${networking.domain}") + ]; }; ${access.tailDomain} = mkIf tailscale.enable { inherit (virtualHosts.${access.domain}) useACMEHost; addSSL = mkDefault (access.useACMEHost != null || virtualHosts.${access.domain}.forceSSL); local.enable = true; - inherit locations; + locations = mkMerge [ + locations + (localLocations nginx.access.vouch.tailDomain or "login.tail.${networking.domain}") + ]; }; }; }; diff --git a/nixos/access/vouch.nix b/nixos/access/vouch.nix index 056ea81a..690b622a 100644 --- a/nixos/access/vouch.nix +++ b/nixos/access/vouch.nix @@ -4,10 +4,11 @@ ... }: let inherit (lib.options) mkOption; - inherit (lib.modules) mkIf mkDefault mkOptionDefault; - inherit (config.services) tailscale; + inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; + inherit (config) networking; + inherit (config.services) tailscale nginx; cfg = config.services.vouch-proxy; - access = config.services.nginx.access.vouch; + access = nginx.access.vouch; in { options.services.nginx.access.vouch = with lib.types; { url = mkOption { @@ -15,15 +16,15 @@ in { }; domain = mkOption { type = str; - default = "login.${config.networking.domain}"; + default = "login.${networking.domain}"; }; localDomain = mkOption { type = str; - default = "login.local.${config.networking.domain}"; + default = "login.local.${networking.domain}"; }; tailDomain = mkOption { type = str; - default = "login.tail.${config.networking.domain}"; + default = "login.tail.${networking.domain}"; }; useACMEHost = mkOption { type = nullOr str; @@ -38,21 +39,47 @@ in { in mkOptionDefault "http://${host}:${toString cfg.port}"; }; virtualHosts = let - location = { - proxy.websocket.enable = true; - proxyPass = access.url; - recommendedProxySettings = false; + locations = { + "/" = { + proxyPass = mkDefault access.url; + extraConfig = '' + proxy_redirect default; + ''; + }; + "/validate" = { config, ... }: { + proxyPass = mkDefault (access.url + "/validate"); + recommendedProxySettings = mkDefault false; + extraConfig = if config.local.trusted then '' + if ($http_x_host = ''') { + set $http_x_host $host; + } + proxy_set_header Host $http_x_host; + '' else '' + proxy_set_header Host $host; + ''; + }; + }; + localLocations = kanidmDomain: { + "/".extraConfig = '' + proxy_redirect $scheme://${nginx.access.kanidm.domain or "id.${networking.domain}"}/ $scheme://${kanidmDomain}/; + ''; }; in { ${access.localDomain} = mkIf (access.useACMEHost != null) { local.enable = true; - locations."/" = location; + locations = mkMerge [ + locations + (localLocations nginx.access.kanidm.localDomain or "id.local.${networking.domain}") + ]; useACMEHost = mkDefault access.useACMEHost; forceSSL = true; }; ${access.tailDomain} = mkIf tailscale.enable { local.enable = true; - locations."/" = location; + locations = mkMerge [ + locations + (localLocations nginx.access.kanidm.tailDomain or "id.tail.${networking.domain}") + ]; useACMEHost = mkDefault access.useACMEHost; addSSL = mkIf (access.useACMEHost != null) (mkDefault true); }; diff --git a/systems/tei/cloudflared.nix b/systems/tei/cloudflared.nix index 4f0f336a..a85ee302 100644 --- a/systems/tei/cloudflared.nix +++ b/systems/tei/cloudflared.nix @@ -69,10 +69,6 @@ in { credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path; default = "http_status:404"; ingress = listToAttrs [ - (ingressForNginx { - host = config.networking.domain; - inherit hostName; - }) (ingressForNginx { host = config.services.zigbee2mqtt.domain; inherit hostName; diff --git a/systems/tei/nixos.nix b/systems/tei/nixos.nix index 82e10feb..c4505b0e 100644 --- a/systems/tei/nixos.nix +++ b/systems/tei/nixos.nix @@ -27,6 +27,16 @@ in { ./cloudflared.nix ]; + services.nginx = let + inherit (config.services.nginx) access; + in { + virtualHosts = { + ${access.zigbee2mqtt.domain} = { + local.denyGlobal = true; + }; + }; + }; + sops.defaultSopsFile = ./secrets.yaml; networking.firewall = { diff --git a/tree.nix b/tree.nix index eca3a87f..66939dff 100644 --- a/tree.nix +++ b/tree.nix @@ -56,6 +56,7 @@ }; }; "modules/nixos/network".functor.enable = true; + "modules/nixos/nginx".functor.enable = true; "modules/nixos/steam".functor.enable = true; "modules/meta".functor.enable = true; "modules/system".functor.enable = true;