feat(vouch): local access

This commit is contained in:
arcnmx 2024-02-19 13:20:57 -08:00
parent ee2618061d
commit e4596f256f
5 changed files with 182 additions and 73 deletions

View file

@ -3,73 +3,115 @@
lib, lib,
... ...
}: }:
with lib; let let
inherit (config.services) vouch-proxy; inherit (lib.options) mkOption mkEnableOption;
in { inherit (lib.modules) mkIf mkMerge mkBefore mkDefault;
options = with types; { inherit (config) networking;
services.nginx.virtualHosts = let inherit (config.services) vouch-proxy tailscale;
vouchModule = { config, ... }: { vouchModule = { config, ... }: {
options = { options = with lib.types; {
vouch = { vouch = {
enable = mkEnableOption "vouch auth proxy"; enable = mkEnableOption "vouch auth proxy";
proxyOrigin = mkOption { proxyOrigin = mkOption {
type = str; type = str;
}; default = "https://login.local.${networking.domain}";
authUrl = mkOption { };
type = str; authUrl = mkOption {
}; type = str;
url = mkOption { default = "https://id.${networking.domain}";
type = str; };
}; url = mkOption {
type = str;
default = "https://login.${networking.domain}";
};
localUrl = mkOption {
type = str;
default = "https://login.local.${networking.domain}";
};
tailDomain = mkOption {
type = str;
default = "login.tail.${networking.domain}";
};
authRequestDirective = mkOption {
type = lines;
default = ''
auth_request /validate;
'';
};
};
};
config = mkMerge [
{
vouch = mkIf vouch-proxy.enable {
proxyOrigin = let
inherit (vouch-proxy.settings.vouch) listen port;
host = if listen == "0.0.0.0" || listen == "[::]" then "localhost" else listen;
in mkDefault "http://${host}:${toString port}";
authUrl = mkDefault vouch-proxy.authUrl;
url = mkDefault vouch-proxy.url;
};
}
{
vouch.proxyOrigin = mkIf (tailscale.enable && !vouch-proxy.enable) (mkDefault
"http://login.tail.${networking.domain}"
);
}
(mkIf config.vouch.enable {
extraConfig = ''
${config.vouch.authRequestDirective}
error_page 401 = @error401;
'';
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;
'';
};
"@error401" = {
extraConfig = let
localVouchUrl = ''
if ($http_host ~ "\.local\.${networking.domain}$") {
set $vouch_url ${config.vouch.localUrl};
}
'';
tailVouchUrl = ''
if ($http_host ~ "\.tail\.${networking.domain}$") {
set $vouch_url $scheme://${config.vouch.tailDomain};
}
'';
in mkMerge [
(mkBefore ''
set $vouch_url ${config.vouch.url};
'')
(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;
''
];
};
"/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;
'';
}; };
}; };
config = mkMerge [ })
{ ];
vouch = mkIf vouch-proxy.enable { };
proxyOrigin = let in {
inherit (vouch-proxy.settings.vouch) listen port; options = with lib.types; {
host = if listen == "0.0.0.0" || listen == "[::]" then "localhost" else listen; services.nginx.virtualHosts = mkOption {
in mkOptionDefault "http://${host}:${toString port}";
authUrl = mkOptionDefault vouch-proxy.authUrl;
url = mkOptionDefault vouch-proxy.url;
};
}
(mkIf config.vouch.enable {
extraConfig = ''
auth_request /validate;
error_page 401 = @error401;
'';
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;
'';
};
"@error401" = {
extraConfig = ''
return 302 ${config.vouch.url}/login?url=$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;
'';
};
};
})
];
};
in mkOption {
type = attrsOf (submodule vouchModule); type = attrsOf (submodule vouchModule);
}; };
}; };

61
nixos/access/vouch.nix Normal file
View file

@ -0,0 +1,61 @@
{
config,
lib,
...
}: let
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkDefault mkOptionDefault;
inherit (config.services) tailscale;
cfg = config.services.vouch-proxy;
access = config.services.nginx.access.vouch;
in {
options.services.nginx.access.vouch = with lib.types; {
url = mkOption {
type = str;
};
domain = mkOption {
type = str;
default = "login.${config.networking.domain}";
};
localDomain = mkOption {
type = str;
default = "login.local.${config.networking.domain}";
};
tailDomain = mkOption {
type = str;
default = "login.tail.${config.networking.domain}";
};
useACMEHost = mkOption {
type = nullOr str;
default = null;
};
};
config.services.nginx = {
access.vouch = mkIf cfg.enable {
url = let
inherit (cfg.settings.vouch) listen;
host = if listen == "0.0.0.0" || listen == "[::]" then "localhost" else listen;
in mkOptionDefault "http://${host}:${toString cfg.port}";
};
virtualHosts = let
location = {
proxy.websocket.enable = true;
proxyPass = access.url;
recommendedProxySettings = false;
};
in {
${access.localDomain} = mkIf (access.useACMEHost != null) {
local.enable = true;
locations."/" = location;
useACMEHost = mkDefault access.useACMEHost;
forceSSL = true;
};
${access.tailDomain} = mkIf tailscale.enable {
local.enable = true;
locations."/" = location;
useACMEHost = mkDefault access.useACMEHost;
addSSL = mkIf (access.useACMEHost != null) (mkDefault true);
};
};
};
}

View file

@ -28,6 +28,7 @@ in {
nixos.access.nginx nixos.access.nginx
nixos.access.global nixos.access.global
nixos.access.gensokyo nixos.access.gensokyo
nixos.access.vouch
nixos.access.kanidm nixos.access.kanidm
nixos.access.freeipa nixos.access.freeipa
nixos.access.kitchencam nixos.access.kitchencam
@ -59,6 +60,14 @@ in {
inherit (config.services) nginx tailscale; inherit (config.services) nginx tailscale;
inherit (nginx) access; inherit (nginx) access;
in { in {
${access.vouch.localDomain} = {
inherit (nginx) group;
extraDomainNames = mkMerge [
(mkIf tailscale.enable [
access.vouch.tailDomain
])
];
};
${access.kanidm.domain} = { ${access.kanidm.domain} = {
inherit (nginx) group; inherit (nginx) group;
extraDomainNames = mkMerge [ extraDomainNames = mkMerge [
@ -128,15 +137,14 @@ in {
services.nginx = let services.nginx = let
inherit (config.services.nginx) access; inherit (config.services.nginx) access;
vouch = {
authUrl = vouch-proxy.authUrl;
url = vouch-proxy.url;
proxyOrigin = "http://${tei.networking.access.hostnameForNetwork.tail}:${toString vouch-proxy.settings.vouch.port}";
};
in { in {
access.plex = assert plex.enable; { access.plex = assert plex.enable; {
url = "http://${mediabox.networking.access.hostnameForNetwork.local}:32400"; url = "http://${mediabox.networking.access.hostnameForNetwork.local}:32400";
}; };
access.vouch = assert vouch-proxy.enable; {
url = "http://${tei.networking.access.hostnameForNetwork.tail}:${toString vouch-proxy.settings.vouch.port}";
useACMEHost = access.vouch.localDomain;
};
access.kanidm = assert kanidm.enableServer; { access.kanidm = assert kanidm.enableServer; {
inherit (kanidm.server.frontend) domain port; inherit (kanidm.server.frontend) domain port;
host = tei.networking.access.hostnameForNetwork.local; host = tei.networking.access.hostnameForNetwork.local;
@ -168,10 +176,8 @@ in {
useACMEHost = access.plex.domain; useACMEHost = access.plex.domain;
}; };
${access.kitchencam.domain} = { ${access.kitchencam.domain} = {
inherit vouch;
}; };
${access.invidious.domain} = { ${access.invidious.domain} = {
inherit vouch;
useACMEHost = access.invidious.domain; useACMEHost = access.invidious.domain;
forceSSL = true; forceSSL = true;
}; };

View file

@ -2,7 +2,6 @@
config, config,
lib, lib,
meta, meta,
pkgs,
... ...
}: let }: let
inherit (lib.modules) mkIf mkMerge; inherit (lib.modules) mkIf mkMerge;

View file

@ -18,6 +18,7 @@ module "hakurei_system_records" {
local_subdomains = [ local_subdomains = [
"prox", "prox",
"id", "id",
"login",
"ldap", "ldap",
"freeipa", "freeipa",
"smb", "smb",