refactor(nginx): proxy options

This commit is contained in:
arcnmx 2024-04-18 10:40:35 -07:00
parent c3f3fe1fed
commit 02508ecbd3
18 changed files with 638 additions and 424 deletions

30
lib.nix
View file

@ -8,7 +8,7 @@
inherit (nixlib.strings) splitString toLower; inherit (nixlib.strings) splitString toLower;
inherit (nixlib.lists) imap0 elemAt findFirst; inherit (nixlib.lists) imap0 elemAt findFirst;
inherit (nixlib.attrsets) mapAttrs listToAttrs nameValuePair; inherit (nixlib.attrsets) mapAttrs listToAttrs nameValuePair;
inherit (nixlib.strings) hasPrefix hasInfix substring fixedWidthString replaceStrings concatMapStringsSep; inherit (nixlib.strings) hasPrefix hasInfix removePrefix substring fixedWidthString replaceStrings concatMapStringsSep match toInt;
inherit (nixlib.trivial) flip toHexString bitOr; inherit (nixlib.trivial) flip toHexString bitOr;
toHexStringLower = v: toLower (toHexString v); toHexStringLower = v: toLower (toHexString v);
@ -31,6 +31,18 @@
nibble0 + (fixedWidthString 1 "0" (toHexStringLower nibble1)); nibble0 + (fixedWidthString 1 "0" (toHexStringLower nibble1));
in "${part0 (part 0)}${part 1}:${part 2}ff:fe${part 3}:${part 4}${part 5}"; in "${part0 (part 0)}${part 1}:${part 2}ff:fe${part 3}:${part 4}${part 5}";
parseUrl = url: let
parts = match ''^([^:]+)://(\[[0-9a-fA-F:]+]|[^/:\[]+)(|:[0-9]+)(|/.*)$'' url;
port' = elemAt parts 2;
in assert parts != null; rec {
inherit url parts;
scheme = elemAt parts 0;
host = elemAt parts 1;
port = if port' != "" then toInt (removePrefix ":" port') else null;
hostport = host + port';
path = elemAt parts 3;
};
userIs = group: user: builtins.elem group (user.extraGroups ++ [user.group]); userIs = group: user: builtins.elem group (user.extraGroups ++ [user.group]);
mkWinPath = replaceStrings ["/"] ["\\"]; mkWinPath = replaceStrings ["/"] ["\\"];
@ -51,11 +63,17 @@
mkAlmostOptionDefault = mkOverride overrideAlmostOptionDefault; mkAlmostOptionDefault = mkOverride overrideAlmostOptionDefault;
mkAlmostDefault = mkOverride overrideAlmostDefault; mkAlmostDefault = mkOverride overrideAlmostDefault;
mkAlmostForce = mkOverride overrideAlmostForce; mkAlmostForce = mkOverride overrideAlmostForce;
orderJustBefore = 400;
orderBefore = 500; orderBefore = 500;
orderAlmostBefore = 600;
orderNone = 1000; orderNone = 1000;
orderAfter = 1500;
orderAlmostAfter = 1400; orderAlmostAfter = 1400;
mkAlmostAfter = mkOrder 1400; orderAfter = 1500;
orderJustAfter = 1600;
mkJustBefore = mkOrder orderJustBefore;
mkAlmostBefore = mkOrder orderAlmostBefore;
mkAlmostAfter = mkOrder orderAlmostAfter;
mkJustAfter = mkOrder orderJustAfter;
mapOverride = priority: mapAttrs (_: mkOverride priority); mapOverride = priority: mapAttrs (_: mkOverride priority);
mapOptionDefaults = mapOverride overrideOptionDefault; mapOptionDefaults = mapOverride overrideOptionDefault;
mapAlmostOptionDefaults = mapOverride overrideAlmostOptionDefault; mapAlmostOptionDefaults = mapOverride overrideAlmostOptionDefault;
@ -79,13 +97,13 @@ in {
lib = { lib = {
domain = "gensokyo.zone"; domain = "gensokyo.zone";
inherit treeToModulesOutput userIs inherit treeToModulesOutput userIs
eui64 mkWinPath mkBaseDn mkAddress6 eui64 parseUrl mkWinPath mkBaseDn mkAddress6
toHexStringLower hexCharToInt toHexStringLower hexCharToInt
mapListToAttrs coalesce mapListToAttrs coalesce
mkAlmostOptionDefault mkAlmostDefault mkAlmostForce mapOverride mapOptionDefaults mapAlmostOptionDefaults mapDefaults mkAlmostOptionDefault mkAlmostDefault mkAlmostForce mapOverride mapOptionDefaults mapAlmostOptionDefaults mapDefaults
overrideOptionDefault overrideAlmostOptionDefault overrideDefault overrideAlmostDefault overrideNone overrideAlmostForce overrideForce overrideVM overrideOptionDefault overrideAlmostOptionDefault overrideDefault overrideAlmostDefault overrideNone overrideAlmostForce overrideForce overrideVM
orderBefore orderNone orderAfter orderAlmostAfter orderJustBefore orderBefore orderAlmostBefore orderNone orderAfter orderAlmostAfter orderJustAfter
mkAlmostAfter; mkJustBefore mkAlmostBefore mkAlmostAfter mkJustAfter;
inherit (inputs.arcexprs.lib) unmerged json; inherit (inputs.arcexprs.lib) unmerged json;
}; };
gensokyo-zone = { gensokyo-zone = {

View file

@ -56,7 +56,7 @@
set ${varPrefix}client 1; set ${varPrefix}client 1;
} }
''; '';
localModule = {config, ...}: let localModule = {config, xvars, ...}: let
cfg = config.local; cfg = config.local;
in { in {
options.local = with lib.types; { options.local = with lib.types; {
@ -100,7 +100,7 @@
in mkMerge [ in mkMerge [
(mkIf cfg.emitDenyGlobal (mkBefore allowDirectives)) (mkIf cfg.emitDenyGlobal (mkBefore allowDirectives))
(mkIf cfg.emitVars (mkBefore (mkAddrVar "$remote_addr" "$local_"))) (mkIf cfg.emitVars (mkBefore (mkAddrVar "$remote_addr" "$local_")))
(mkIf cfg.emitVars (mkBefore (mkAddrVar "$x_remote_addr" "$x_local_"))) (mkIf (cfg.emitVars && config.xvars.enable) (mkBefore (mkAddrVar (xvars.remote_addr.get) "$x_local_")))
]; ];
}; };
}; };
@ -130,13 +130,7 @@
options = with lib.types; { options = with lib.types; {
locations = mkOption { locations = mkOption {
type = attrsOf (submoduleWith { type = attrsOf (submodule [locationModule]);
modules = [locationModule];
shorthandOnlyDefinesConfig = true;
specialArgs = {
virtualHost = config;
};
});
}; };
}; };
@ -149,13 +143,7 @@
in { in {
options = with lib.types; { options = with lib.types; {
services.nginx.virtualHosts = mkOption { services.nginx.virtualHosts = mkOption {
type = attrsOf (submoduleWith { type = attrsOf (submodule [hostModule]);
modules = [hostModule];
shorthandOnlyDefinesConfig = true;
specialArgs = {
nixosConfig = config;
};
});
}; };
}; };
} }

View file

@ -1,57 +1,27 @@
{ {
config,
lib, lib,
inputs, inputs,
... ...
}: let }: let
inherit (inputs.self.lib.lib) mkAlmostAfter mkAlmostOptionDefault; inherit (inputs.self.lib.lib) mkJustBefore mkAlmostOptionDefault orderJustBefore;
inherit (lib.options) mkOption mkEnableOption; inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkDefault mkOptionDefault; inherit (lib.modules) mkIf mkMerge mkOrder mkDefault mkOptionDefault;
inherit (lib.strings) optionalString splitString match; xHeadersProxied = { xvars }: ''
inherit (lib.attrsets) attrValues; ${xvars.init "forwarded_for" "$proxy_add_x_forwarded_for"}
inherit (lib.lists) length head /*optional*/ any;
inherit (lib.trivial) mapNullable;
#inherit (config) networking;
inherit (config.services) nginx;
schemeForUrl = url: let
parts = splitString ":" url;
in if length parts == 1 then null else head parts;
pathForUrl = url: let
parts = match ''[^:]+://[^/]+(.*)'' url;
in if parts == null then null else head parts;
hostForUrl = url: let
parts = match ''[^:]+://([^/]+).*'' url;
in if parts == null then null else head parts;
xHeadersDefaults = ''
set $x_scheme $scheme;
set $x_forwarded_for $remote_addr;
set $x_remote_addr $remote_addr;
set $x_forwarded_host $host;
set $x_forwarded_server $host;
set $x_host $host;
set $x_referer $http_referer;
set $x_proxy_host $x_host;
'';
xHeadersProxied = ''
set $x_forwarded_for $proxy_add_x_forwarded_for;
if ($http_x_forwarded_proto) { if ($http_x_forwarded_proto) {
set $x_scheme $http_x_forwarded_proto; ${xvars.init "scheme" "$http_x_forwarded_proto"}
} }
if ($http_x_real_ip) { if ($http_x_real_ip) {
set $x_remote_addr $http_x_real_ip; ${xvars.init "remote_addr" "$http_x_real_ip"}
} }
if ($http_x_forwarded_host) { if ($http_x_forwarded_host) {
set $x_forwarded_host $http_x_forwarded_host; ${xvars.init "host" "$http_x_forwarded_host"}
} }
if ($http_x_forwarded_server) { if ($http_x_forwarded_server) {
set $x_forwarded_server $http_x_forwarded_server; ${xvars.init "forwarded_server" "$http_x_forwarded_server"}
}
if ($x_referer ~ "^https?://([^/]*)/(.*)$") {
set $x_referer_host $1;
set $x_referer_path $2;
} }
''; '';
locationModule = { config, virtualHost, ... }: let locationModule = { config, virtualHost, xvars, ... }: let
cfg = config.proxied; cfg = config.proxied;
in { in {
options = with lib.types; { options = with lib.types; {
@ -64,94 +34,30 @@
type = bool; type = bool;
readOnly = true; readOnly = true;
}; };
xvars.enable = mkEnableOption "$x_variables";
redirectScheme = mkEnableOption "redirect to X-Forwarded-Proto" // {
default = cfg.enabled;
};
rewriteReferer = mkEnableOption "rewrite Referer header" // {
default = cfg.enabled;
};
};
proxy = {
enabled = mkOption {
type = bool;
readOnly = true;
};
scheme = mkOption {
type = nullOr str;
};
path = mkOption {
type = nullOr str;
};
host = mkOption {
type = nullOr str;
};
headers.enableRecommended = mkOption {
type = enum [ true false "nixpkgs" ];
};
}; };
}; };
config = let config = let
emitVars = cfg.enabled && !virtualHost.proxied.enabled; emitVars = cfg.enabled && !virtualHost.proxied.enabled;
emitRedirectScheme = config.proxy.enabled && cfg.redirectScheme;
emitRefererRewrite = config.proxy.enabled && cfg.rewriteReferer;
emitHeaders = config.proxy.enabled && config.proxy.headers.enableRecommended == true;
in { in {
proxied = { proxied = {
enabled = mkOptionDefault (virtualHost.proxied.enabled || cfg.enable != false); enabled = mkOptionDefault (virtualHost.proxied.enabled || cfg.enable != false);
xvars.enable = mkIf (cfg.enabled || emitRedirectScheme || emitHeaders) true;
}; };
proxy = { proxy = {
enabled = mkOptionDefault (config.proxyPass != null); headers = {
headers.enableRecommended = mkOptionDefault ( enableRecommended = mkIf cfg.enabled (mkAlmostOptionDefault true);
if !virtualHost.recommendedProxySettings then false rewriteReferer.enable = mkIf cfg.enabled (mkAlmostOptionDefault true);
else if cfg.enabled then true
else "nixpkgs"
);
scheme = mkOptionDefault (
mapNullable schemeForUrl config.proxyPass
);
path = mkOptionDefault (
mapNullable pathForUrl config.proxyPass
);
host = mkOptionDefault (
mapNullable hostForUrl config.proxyPass
);
}; };
recommendedProxySettings = mkMerge [ redirect.enable = mkIf cfg.enabled (mkAlmostOptionDefault true);
(mkAlmostOptionDefault (config.proxy.headers.enableRecommended == "nixpkgs")) };
]; xvars.enable = mkIf cfg.enabled true;
extraConfig = mkMerge [ extraConfig = mkMerge [
(mkIf emitVars ( (mkIf emitVars (
mkBefore xHeadersProxied mkJustBefore (xHeadersProxied { inherit xvars; })
)) ))
(mkIf emitRedirectScheme ''
proxy_redirect ${config.proxy.scheme}://$host/ $x_scheme://$host/;
'')
(mkIf emitRefererRewrite ''
if ($x_referer_host = $host) {
set $x_referer "${config.proxy.scheme}://${config.proxy.host}/$x_referer_path";
}
'')
(mkIf emitHeaders (mkAlmostAfter ''
if ($x_proxy_host = "") {
set $x_proxy_host $proxy_host;
}
if ($x_proxy_host = "") {
set $x_proxy_host ${config.proxy.host};
}
proxy_set_header Host $x_proxy_host;
proxy_set_header Referer $x_referer;
proxy_set_header X-Real-IP $x_remote_addr;
proxy_set_header X-Forwarded-For $x_forwarded_for;
proxy_set_header X-Forwarded-Proto $x_scheme;
proxy_set_header X-Forwarded-Host $x_forwarded_host;
proxy_set_header X-Forwarded-Server $x_forwarded_server;
''))
]; ];
}; };
}; };
hostModule = { config, ... }: let hostModule = { config, xvars, ... }: let
cfg = config.proxied; cfg = config.proxied;
in { in {
options = with lib.types; { options = with lib.types; {
@ -164,13 +70,6 @@
type = bool; type = bool;
default = cfg.enable != false; default = cfg.enable != false;
}; };
xvars.enable = mkEnableOption "$x_variables" // {
default = cfg.enabled;
};
};
recommendedProxySettings = mkOption {
type = bool;
default = nginx.recommendedProxySettings;
}; };
locations = mkOption { locations = mkOption {
type = attrsOf (submoduleWith { type = attrsOf (submoduleWith {
@ -181,23 +80,17 @@
}; };
config = { config = {
proxied = { xvars.enable = mkIf cfg.enabled true;
xvars.enable = mkIf (any (loc: loc.proxied.xvars.enable) (attrValues config.locations)) true;
};
local.denyGlobal = mkIf (cfg.enable == "cloudflared") (mkDefault true); local.denyGlobal = mkIf (cfg.enable == "cloudflared") (mkDefault true);
extraConfig = mkIf cfg.xvars.enable (mkBefore '' extraConfig = mkIf (cfg.enabled && config.xvars.enable) (
${xHeadersDefaults} mkOrder (orderJustBefore + 25) (xHeadersProxied { inherit xvars; })
${optionalString cfg.enabled xHeadersProxied} );
'');
}; };
}; };
in { in {
options = with lib.types; { options = with lib.types; {
services.nginx.virtualHosts = mkOption { services.nginx.virtualHosts = mkOption {
type = attrsOf (submoduleWith { type = attrsOf (submodule [hostModule]);
modules = [ hostModule ];
shorthandOnlyDefinesConfig = true;
});
}; };
}; };
} }

View file

@ -0,0 +1,214 @@
let
locationModule = { config, name, virtualHost, xvars, gensokyo-zone, lib, ... }: let
inherit (gensokyo-zone.lib) mkJustBefore mkJustAfter mkAlmostOptionDefault mapOptionDefaults coalesce parseUrl;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault;
inherit (lib.attrsets) filterAttrs mapAttrsToList;
inherit (lib.strings) hasPrefix removeSuffix concatStringsSep;
inherit (lib.trivial) mapNullable;
cfg = config.proxy;
in {
options = with lib.types; {
proxy = {
enable = mkEnableOption "proxy";
enabled = mkOption {
type = bool;
readOnly = true;
};
url = mkOption {
type = str;
};
path = mkOption {
type = str;
};
host = mkOption {
type = nullOr str;
};
websocket.enable = mkEnableOption "websocket proxy" // {
default = virtualHost.proxy.websocket.enable;
};
parsed = {
scheme = mkOption {
type = nullOr str;
};
path = mkOption {
type = nullOr str;
};
host = mkOption {
type = nullOr str;
};
hostport = mkOption {
type = nullOr str;
};
port = mkOption {
type = nullOr int;
};
};
headers = {
enableRecommended = mkOption {
type = enum [ true false "nixpkgs" ];
};
rewriteReferer.enable = mkEnableOption "rewrite referer host";
set = mkOption {
type = attrsOf (nullOr str);
};
};
redirect = {
enable = mkEnableOption "proxy_redirect";
fromHost = mkOption {
type = str;
default = xvars.get.host;
example = "xvars.get.proxy_host";
};
fromScheme = mkOption {
type = str;
default = xvars.get.scheme;
example = "xvars.get.proxy_scheme";
};
};
};
};
config = let
emitHeaders = setHeaders' != { };
url = parseUrl config.proxyPass;
recommendedHeaders = {
Host = if cfg.host == null then xvars.get.proxy_host else cfg.host;
Referer = xvars.get.referer;
X-Real-IP = xvars.get.remote_addr;
X-Forwarded-For = xvars.get.forwarded_for;
X-Forwarded-Proto = xvars.get.scheme;
X-Forwarded-Host = xvars.get.host;
X-Forwarded-Server = xvars.get.forwarded_server;
};
initProxyVars = ''
${xvars.init "proxy_scheme" cfg.parsed.scheme}
${xvars.init "proxy_host" "$proxy_host"}
if (${xvars.get.proxy_host} = "") {
${xvars.init "proxy_host" cfg.parsed.hostport}
}
'';
hostHeader = coalesce [
cfg.headers.set.Host or null
cfg.host
xvars.get.proxy_host
];
rewriteReferer = ''
set $x_set_referer ${xvars.get.referer};
if (${xvars.get.referer_host} = $host) {
set $x_set_referer ${config.proxy.parsed.scheme}://${hostHeader}${xvars.get.referer_path};
}
'';
redirect = ''
proxy_redirect ${cfg.redirect.fromScheme}://${cfg.redirect.fromHost}/ ${xvars.get.scheme}://${xvars.get.host}/;
'';
setHeaders' = filterAttrs (_: header: header != null) cfg.headers.set;
setHeaders = concatStringsSep "\n" (mapAttrsToList (
name: value: "proxy_set_header ${name} ${xvars.escapeString value};"
) setHeaders');
in {
proxy = {
enabled = mkOptionDefault (config.proxyPass != null);
path = mkIf (hasPrefix "/" name) (mkOptionDefault name);
url = mkIf (virtualHost.proxy.url != null) (mkOptionDefault virtualHost.proxy.url);
headers = {
enableRecommended = mkOptionDefault (
if cfg.enable && virtualHost.proxy.headers.enableRecommended != false then true
else virtualHost.proxy.headers.enableRecommended
);
set = mkMerge [
(mkOptionDefault { })
(mkIf (cfg.headers.enableRecommended == true) (mapOptionDefaults recommendedHeaders))
(mkIf (cfg.host != null) {
Host = mkIf (cfg.headers.enableRecommended != "nixpkgs") (mkAlmostOptionDefault cfg.host);
})
(mkIf cfg.headers.rewriteReferer.enable {
Referer = mkAlmostOptionDefault "$x_set_referer";
})
(mkIf cfg.websocket.enable (mapOptionDefaults {
Upgrade = "$http_upgrade";
Connection = "upgrade";
}))
];
};
host = mkOptionDefault (
if virtualHost.proxy.host != null then virtualHost.proxy.host
else if cfg.headers.enableRecommended == false then null
else xvars.get.host
);
parsed = {
scheme = mkOptionDefault (
mapNullable (_: url.scheme) config.proxyPass
);
path = mkOptionDefault (
mapNullable (_: url.path) config.proxyPass
);
host = mkOptionDefault (
mapNullable (_: url.host) config.proxyPass
);
hostport = mkOptionDefault (
mapNullable (_: url.hostport) config.proxyPass
);
port = mkOptionDefault (
mapNullable (_: url.port) config.proxyPass
);
};
};
proxyPass = mkIf cfg.enable (mkAlmostOptionDefault (removeSuffix "/" cfg.url + cfg.path));
recommendedProxySettings = mkAlmostOptionDefault (cfg.headers.enableRecommended == "nixpkgs");
extraConfig = mkMerge [
(mkIf (cfg.enabled && virtualHost.xvars.enable) (mkJustBefore initProxyVars))
(mkIf (cfg.enabled && cfg.headers.rewriteReferer.enable) (mkJustBefore rewriteReferer))
(mkIf (cfg.enabled && cfg.redirect.enable) (mkBefore redirect))
(mkIf (cfg.enabled && emitHeaders) (mkJustAfter setHeaders))
];
};
};
hostModule = { config, nixosConfig, lib, ... }: let
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) any;
inherit (nixosConfig.services) nginx;
cfg = config.proxy;
in {
options = with lib.types; {
proxy = {
host = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = nullOr str;
default = null;
};
websocket.enable = mkEnableOption "websocket proxy";
headers.enableRecommended = mkOption {
type = enum [ true false "nixpkgs" ];
default = if nginx.recommendedProxySettings then "nixpkgs" else false;
};
};
locations = mkOption {
type = attrsOf (submoduleWith {
modules = [ locationModule ];
shorthandOnlyDefinesConfig = true;
});
};
};
config = let
needsReferer = loc: loc.proxy.enabled && loc.proxy.headers.rewriteReferer.enable;
in {
xvars.parseReferer = mkIf (any needsReferer (attrValues config.locations)) true;
};
};
in {
lib,
...
}: let
inherit (lib.options) mkOption;
in {
options = with lib.types; {
services.nginx.virtualHosts = mkOption {
type = attrsOf (submodule [hostModule]);
};
};
}

View file

@ -10,9 +10,11 @@
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit (lib.trivial) warnIf; inherit (lib.trivial) warnIf;
inherit (config.services) nginx; inherit (config.services) nginx;
forceRedirectConfig = virtualHost: '' forceRedirectConfig = virtualHost: let
if ($x_scheme = http) { xvars = virtualHost.xvars.lib;
return ${toString virtualHost.redirectCode} https://$x_forwarded_host$request_uri; in ''
if (${xvars.get.scheme} = http) {
return ${toString virtualHost.redirectCode} https://${xvars.get.host}$request_uri;
} }
''; '';
locationModule = { config, virtualHost, ... }: let locationModule = { config, virtualHost, ... }: let
@ -23,7 +25,7 @@
force = mkEnableOption "redirect to SSL"; force = mkEnableOption "redirect to SSL";
}; };
config = { config = {
proxied.xvars.enable = mkIf emitForce true; xvars.enable = mkIf emitForce true;
extraConfig = mkIf emitForce (forceRedirectConfig virtualHost); extraConfig = mkIf emitForce (forceRedirectConfig virtualHost);
}; };
}; };
@ -119,7 +121,7 @@
sslCertificate = mkIf (cfg.cert.path != null) (mkAlmostOptionDefault cfg.cert.path); sslCertificate = mkIf (cfg.cert.path != null) (mkAlmostOptionDefault cfg.cert.path);
sslCertificateKey = mkIf (cfg.cert.keyPath != null) (mkAlmostOptionDefault cfg.cert.keyPath); sslCertificateKey = mkIf (cfg.cert.keyPath != null) (mkAlmostOptionDefault cfg.cert.keyPath);
proxied.xvars.enable = mkIf emitForce true; xvars.enable = mkIf emitForce true;
extraConfig = mkIf emitForce (forceRedirectConfig config); extraConfig = mkIf emitForce (forceRedirectConfig config);
}; };
}; };

View file

@ -8,11 +8,11 @@
inherit (lib.options) mkOption mkEnableOption; inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkOptionDefault; inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkOptionDefault;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) toLower replaceStrings; inherit (lib.strings) toLower replaceStrings removePrefix;
inherit (config) networking; inherit (config) networking;
inherit (config.services) vouch-proxy nginx tailscale; inherit (config.services) vouch-proxy nginx tailscale;
inherit (nginx) vouch; inherit (nginx) vouch;
locationModule = {config, virtualHost, ...}: { locationModule = {config, virtualHost, xvars, ...}: {
options.vouch = with lib.types; { options.vouch = with lib.types; {
requireAuth = mkEnableOption "require auth to access this location"; requireAuth = mkEnableOption "require auth to access this location";
setProxyHeader = mkOption { setProxyHeader = mkOption {
@ -33,20 +33,19 @@
(mkBefore virtualHost.vouch.auth.lua.accessLogic) (mkBefore virtualHost.vouch.auth.lua.accessLogic)
]; ];
}; };
proxied.xvars.enable = mkIf (enableVouchTail || virtualHost.vouch.auth.lua.enable) true; xvars.enable = mkIf (enableVouchTail || virtualHost.vouch.auth.lua.enable) true;
proxy.headers.set.X-Vouch-User = mkOptionDefault "$auth_resp_x_vouch_user";
extraConfig = assert virtualHost.vouch.enable; mkMerge [ extraConfig = assert virtualHost.vouch.enable; mkMerge [
(mkIf (!virtualHost.vouch.requireAuth) virtualHost.vouch.auth.requestDirective) (mkIf (!virtualHost.vouch.requireAuth) virtualHost.vouch.auth.requestDirective)
(allowOrigin vouch.url) (allowOrigin vouch.url)
(allowOrigin vouch.authUrl) (allowOrigin vouch.authUrl)
(mkIf enableVouchLocal (allowOrigin vouch.localUrl)) (mkIf enableVouchLocal (allowOrigin vouch.localUrl))
(mkIf enableVouchTail (allowOrigin "$x_scheme://${vouch.tailDomain}")) (mkIf enableVouchLocal (allowOrigin "sso.local.${networking.domain}"))
(mkIf config.vouch.setProxyHeader '' (mkIf enableVouchTail (allowOrigin "${xvars.get.scheme}://${vouch.tailDomain}"))
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
'')
]; ];
}; };
}; };
hostModule = {config, ...}: let hostModule = {config, xvars, ...}: let
cfg = config.vouch; cfg = config.vouch;
mkHeaderVar = header: toLower (replaceStrings [ "-" ] [ "_" ] header); mkHeaderVar = header: toLower (replaceStrings [ "-" ] [ "_" ] header);
mkUpstreamVar = header: "\$upstream_http_${mkHeaderVar header}"; mkUpstreamVar = header: "\$upstream_http_${mkHeaderVar header}";
@ -113,7 +112,7 @@
if ngx.ctx.auth_res ~= nil and ngx.ctx.auth_res.status == ngx.HTTP_UNAUTHORIZED then if ngx.ctx.auth_res ~= nil and ngx.ctx.auth_res.status == ngx.HTTP_UNAUTHORIZED then
local vouch_url = ngx.var["vouch_url"] or "${vouch.url}" local vouch_url = ngx.var["vouch_url"] or "${vouch.url}"
local query_args = ngx.encode_args { local query_args = ngx.encode_args {
url = string.format("%s://%s%s", ngx.var.x_scheme, ngx.var.x_forwarded_host, ngx.var.request_uri), url = string.format("%s://%s%s", ngx.var.${removePrefix "$" xvars.get.scheme}, ngx.var.${removePrefix "$" xvars.get.host}, ngx.var.request_uri),
["X-Vouch-Token"] = ngx.ctx.auth_res.header["X-Vouch-Token"] or "", ["X-Vouch-Token"] = ngx.ctx.auth_res.header["X-Vouch-Token"] or "",
error = ngx.ctx.auth_res.header["X-Vouch-Error"] or "", error = ngx.ctx.auth_res.header["X-Vouch-Error"] or "",
-- ["vouch-failcount"] is now a session variable and shouldn't be needed anymore -- ["vouch-failcount"] is now a session variable and shouldn't be needed anymore
@ -145,13 +144,13 @@
}; };
extraConfig = let extraConfig = let
localVouchUrl = '' localVouchUrl = ''
if ($x_forwarded_host ~ "\.local\.${networking.domain}$") { if (${xvars.get.host} ~ "\.local\.${networking.domain}$") {
set $vouch_url ${vouch.localUrl}; set $vouch_url ${vouch.localUrl};
} }
''; '';
tailVouchUrl = '' tailVouchUrl = ''
if ($x_forwarded_host ~ "\.tail\.${networking.domain}$") { if (${xvars.get.host} ~ "\.tail\.${networking.domain}$") {
set $vouch_url $x_scheme://${vouch.tailDomain}; set $vouch_url ${xvars.get.scheme}://${vouch.tailDomain};
} }
''; '';
setVouchUrl = [ setVouchUrl = [
@ -170,33 +169,32 @@
mkBefore "auth_request_set \$${authVar} ${mkUpstreamVar header};" mkBefore "auth_request_set \$${authVar} ${mkUpstreamVar header};"
)) cfg.auth.variables )) cfg.auth.variables
)); ));
proxied.xvars.enable = mkIf cfg.enable true; xvars.enable = mkIf cfg.enable true;
locations = mkIf cfg.enable { locations = mkIf cfg.enable {
"/" = mkIf cfg.requireAuth { "/" = mkIf cfg.requireAuth {
vouch.requireAuth = mkAlmostOptionDefault true; vouch.requireAuth = mkAlmostOptionDefault true;
}; };
${cfg.auth.errorLocation} = mkIf (cfg.auth.errorLocation != null) { ${cfg.auth.errorLocation} = mkIf (cfg.auth.errorLocation != null) {
proxied.xvars.enable = true; xvars.enable = true;
extraConfig = '' extraConfig = ''
return 302 $vouch_url/login?url=$x_scheme://$x_forwarded_host$request_uri&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err; return 302 $vouch_url/login?url=${xvars.get.scheme}://${xvars.get.host}$request_uri&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
''; '';
}; };
${cfg.auth.requestLocation} = { config, ... }: { ${cfg.auth.requestLocation} = { config, xvars, ... }: {
proxyPass = "${vouch.proxyOrigin}/validate"; proxy = {
proxy.headers.enableRecommended = false; enable = true;
proxied.rewriteReferer = false; url = vouch.proxyOrigin;
extraConfig = let
# nginx-proxied vouch must use X-Forwarded-Host, but vanilla vouch requires Host # nginx-proxied vouch must use X-Forwarded-Host, but vanilla vouch requires Host
vouchProxyHost = if vouch.doubleProxy.enable host = if vouch.doubleProxy.enable
then (if cfg.localSso.enable then vouch.doubleProxy.localServerName else vouch.doubleProxy.serverName) then (if cfg.localSso.enable then vouch.doubleProxy.localServerName else vouch.doubleProxy.serverName)
else "$x_forwarded_host"; else xvars.get.host;
in '' headers = {
proxy_set_header Host ${vouchProxyHost}; set.Content-Length = "";
proxy_set_header X-Forwarded-Host $x_forwarded_host; rewriteReferer.enable = false;
proxy_set_header Referer $x_referer; };
proxy_set_header X-Forwarded-Proto $x_scheme; };
extraConfig = ''
proxy_pass_request_body off; proxy_pass_request_body off;
proxy_set_header Content-Length "";
''; '';
}; };
}; };

View file

@ -1,30 +0,0 @@
{lib, ...}: let
inherit (lib.modules) mkIf;
inherit (lib.options) mkOption mkEnableOption;
wsModule = {config, ...}: {
options = with lib.types; {
proxy.websocket.enable = mkEnableOption "websocket proxy";
};
config = mkIf config.proxy.websocket.enable {
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
hostModule = {config, ...}: {
imports = [wsModule];
options = with lib.types; {
locations = mkOption {
type = attrsOf (submodule wsModule);
};
};
};
in {
options = with lib.types; {
services.nginx.virtualHosts = mkOption {
type = attrsOf (submodule hostModule);
};
};
}

View file

@ -0,0 +1,110 @@
let
locationModule = { config, virtualHost, lib, ... }: let
inherit (lib.options) mkEnableOption;
cfg = config.xvars;
in {
options.xvars = with lib.types; {
enable = mkEnableOption "$x_variables";
};
config = let
in {
};
};
hostModule = { config, nixosConfig, gensokyo-zone, xvars, lib, ... }: let
inherit (gensokyo-zone.lib) mkJustBefore;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.strings) concatStringsSep;
inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList;
inherit (lib.lists) any;
cfg = config.xvars;
escapeString = value: if value == "" then ''""'' else value;
in {
options = with lib.types; {
xvars = {
enable = mkEnableOption "$x_variables";
parseReferer = mkEnableOption "$x_referer_{scheme,host,path}";
defaults = mkOption {
type = attrsOf (nullOr str);
default = rec {
scheme = "$scheme";
forwarded_for = remote_addr;
remote_addr = "$remote_addr";
forwarded_server = host;
host = "$host";
referer = "$http_referer";
proxy_host = null;
proxy_scheme = null;
};
};
lib = mkOption {
type = attrs;
};
};
locations = mkOption {
type = attrsOf (submoduleWith {
modules = [ locationModule ];
shorthandOnlyDefinesConfig = true;
specialArgs = {
inherit nixosConfig gensokyo-zone xvars;
virtualHost = config;
};
});
};
};
config = let
defaults = concatStringsSep "\n" (mapAttrsToList (
name: value: "set $x_${name} ${escapeString value};"
) (filterAttrs (_: value: value != null) cfg.defaults));
parseReferer = ''
if (${xvars.get.referer} ~ "^(https?)://([^/]*)(/.*)$") {
${xvars.init "referer_scheme" "$1"}
${xvars.init "referer_host" "$2"}
${xvars.init "referer_path" "$3"}
}
'';
in {
xvars = {
enable = mkMerge [
(mkIf (any (loc: loc.xvars.enable) (attrValues config.locations)) true)
(mkIf cfg.parseReferer true)
];
defaults = mkIf cfg.parseReferer (mkOptionDefault {
referer_scheme = null;
referer_host = null;
referer_path = null;
});
lib = {
get = mapAttrs (name: default: if cfg.enable then "$x_${name}" else assert default != null; default) cfg.defaults;
init = name: value: assert cfg.enable && cfg.defaults ? ${name}; "set $x_${name} ${escapeString value};";
inherit escapeString;
};
};
extraConfig = mkMerge [
(mkIf cfg.enable (mkJustBefore defaults))
(mkIf (cfg.enable && cfg.parseReferer) (mkJustBefore parseReferer))
];
_module.args.xvars = config.xvars.lib;
};
};
in {
config,
lib,
gensokyo-zone,
...
}: let
inherit (lib.options) mkOption;
in {
options = with lib.types; {
services.nginx.virtualHosts = mkOption {
type = attrsOf (submoduleWith {
modules = [ hostModule ];
shorthandOnlyDefinesConfig = true;
specialArgs = {
inherit gensokyo-zone;
nixosConfig = config;
};
});
};
};
}

View file

@ -7,9 +7,6 @@
inherit (config.services) barcodebuddy nginx; inherit (config.services) barcodebuddy nginx;
name.shortServer = mkDefault "bbuddy"; name.shortServer = mkDefault "bbuddy";
serverName = "@bbuddy_internal"; serverName = "@bbuddy_internal";
extraConfig = ''
set $x_proxy_host ${serverName};
'';
in { in {
config.services.nginx.virtualHosts = { config.services.nginx.virtualHosts = {
barcodebuddy'php = mkIf barcodebuddy.enable { barcodebuddy'php = mkIf barcodebuddy.enable {
@ -18,35 +15,44 @@ in {
local.denyGlobal = true; local.denyGlobal = true;
}; };
barcodebuddy = { barcodebuddy = {
inherit name extraConfig; inherit name;
vouch = { vouch = {
enable = true; enable = true;
requireAuth = false; requireAuth = false;
}; };
locations = { proxy = {
"/api/" = { url = mkIf barcodebuddy.enable (mkDefault
proxy.headers.enableRecommended = true;
proxyPass = mkDefault "${nginx.virtualHosts.barcodebuddy.locations."/".proxyPass}/api/";
};
"/" = {
proxy.headers.enableRecommended = true;
vouch.requireAuth = true;
proxyPass = mkIf barcodebuddy.enable (mkDefault
"http://localhost:${toString nginx.defaultHTTPListenPort}" "http://localhost:${toString nginx.defaultHTTPListenPort}"
); );
host = mkDefault serverName;
};
locations = {
"/api/" = {
proxy.enable = true;
};
"/" = {
proxy.enable = true;
vouch.requireAuth = true;
}; };
}; };
}; };
barcodebuddy'local = { barcodebuddy'local = {
inherit name extraConfig; inherit name;
ssl.cert.copyFromVhost = "barcodebuddy"; ssl.cert.copyFromVhost = "barcodebuddy";
local.enable = mkDefault true; local.enable = mkDefault true;
locations."/" = { proxy = {
proxy.headers.enableRecommended = true; url = mkDefault nginx.virtualHosts.barcodebuddy.proxy.url;
host = mkDefault nginx.virtualHosts.barcodebuddy.proxy.host;
};
locations."/" = { config, ... }: {
proxy = {
headers.enableRecommended = true;
redirect = {
enable = true;
fromHost = config.proxy.host;
};
};
proxyPass = mkDefault nginx.virtualHosts.barcodebuddy.locations."/".proxyPass; proxyPass = mkDefault nginx.virtualHosts.barcodebuddy.locations."/".proxyPass;
extraConfig = ''
proxy_redirect $x_scheme://${serverName}/ $x_scheme://$x_host/;
'';
}; };
}; };
}; };

View file

@ -18,31 +18,28 @@ let
ssl_verify_client optional_no_ca; ssl_verify_client optional_no_ca;
''; '';
locations' = domain: { locations' = domain: {
"/" = { "/" = { config, xvars, ... }: {
proxy = {
enable = true;
url = mkDefault access.proxyPass;
host = mkDefault domain;
headers = {
rewriteReferer.enable = true;
set = {
X-SSL-CERT = "$ssl_client_escaped_cert";
};
};
redirect = {
enable = true;
fromHost = config.proxy.host;
fromScheme = xvars.get.proxy_scheme;
};
};
proxyPass = mkDefault access.proxyPass; proxyPass = mkDefault access.proxyPass;
recommendedProxySettings = false; recommendedProxySettings = false;
extraConfig = '' extraConfig = ''
proxy_set_header Host ${domain};
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
proxy_redirect https://${domain}/ $scheme://$host/;
proxy_ssl_server_name on; proxy_ssl_server_name on;
proxy_ssl_name ${domain}; proxy_ssl_name ${domain};
set $x_referer $http_referer;
if ($x_referer ~ "^https://([^/]*)/(.*)$") {
set $x_referer_host $1;
set $x_referer_path $2;
}
if ($x_referer_host = $host) {
set $x_referer "https://${domain}/$x_referer_path";
}
proxy_set_header Referer $x_referer;
''; '';
}; };
}; };

View file

@ -4,7 +4,7 @@
lib, lib,
... ...
}: let }: let
inherit (lib.modules) mkIf mkDefault; inherit (lib.modules) mkIf mkMerge mkDefault;
inherit (lib.lists) optional; inherit (lib.lists) optional;
inherit (config.services) nginx; inherit (config.services) nginx;
system = access.systemForService "freepbx"; system = access.systemForService "freepbx";
@ -15,38 +15,50 @@ in {
proxyScheme = "https"; proxyScheme = "https";
url = access.proxyUrlFor { serviceName = "freepbx"; portName = proxyScheme; }; url = access.proxyUrlFor { serviceName = "freepbx"; portName = proxyScheme; };
ucpUrl = access.proxyUrlFor { serviceName = "freepbx"; portName = "ucp-ssl"; }; ucpUrl = access.proxyUrlFor { serviceName = "freepbx"; portName = "ucp-ssl"; };
ucpPath = "/socket.io";
# TODO: ports.asterisk/asterisk-ssl? # TODO: ports.asterisk/asterisk-ssl?
extraConfig = '' extraConfig = ''
proxy_buffer_size 128k; proxy_buffer_size 128k;
proxy_buffers 4 256k; proxy_buffers 4 256k;
proxy_busy_buffers_size 256k; proxy_busy_buffers_size 256k;
set $pbx_scheme $scheme;
if ($http_x_forwarded_proto) {
set $pbx_scheme $http_x_forwarded_proto;
}
proxy_redirect ${proxyScheme}://$host/ $pbx_scheme://$host/;
''; '';
locations = { locations = {
"/" = { "/" = { xvars, ... }: {
proxyPass = mkDefault url; xvars.enable = true;
proxy = {
enable = true;
redirect = {
enable = true;
fromScheme = xvars.get.proxy_scheme;
};
};
};
${ucpPath} = { xvars, virtualHost, ... }: {
proxy = {
enable = true;
websocket.enable = true;
}; };
"/socket.io" = {
proxy.websocket.enable = true;
proxyPass = mkDefault "${ucpUrl}/socket.io";
extraConfig = '' extraConfig = ''
proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin $pbx_scheme://$host; add_header Access-Control-Allow-Origin ${xvars.get.scheme}://${virtualHost.serverName};
''; '';
}; };
}; };
allLocations = mkMerge [
locations
{
${ucpPath}.proxy.url = mkDefault nginx.virtualHosts.freepbx'ucp.proxy.url;
}
];
name.shortServer = mkDefault "pbx"; name.shortServer = mkDefault "pbx";
kTLS = mkDefault true; kTLS = mkDefault true;
in { in {
freepbx = { freepbx = {
vouch.enable = mkDefault true; vouch.enable = mkDefault true;
ssl.force = true; ssl.force = true;
inherit name locations extraConfig kTLS; proxy.url = mkDefault url;
locations = allLocations;
inherit name extraConfig kTLS;
}; };
freepbx'ucp = { freepbx'ucp = {
serverName = mkDefault nginx.virtualHosts.freepbx.serverName; serverName = mkDefault nginx.virtualHosts.freepbx.serverName;
@ -62,12 +74,14 @@ in {
extraParameters = [ "default_server" ]; extraParameters = [ "default_server" ];
}; };
}; };
proxy.websocket.enable = true; proxy = {
url = mkDefault ucpUrl;
websocket.enable = true;
};
vouch.enable = mkDefault true; vouch.enable = mkDefault true;
local.denyGlobal = mkDefault nginx.virtualHosts.freepbx.local.denyGlobal; local.denyGlobal = mkDefault nginx.virtualHosts.freepbx.local.denyGlobal;
locations."/socket.io" = { locations = {
inherit (locations."/socket.io") proxy extraConfig; inherit (locations) "/socket.io";
proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/socket.io".proxyPass;
}; };
inherit extraConfig kTLS; inherit extraConfig kTLS;
}; };
@ -84,16 +98,9 @@ in {
}; };
}; };
ssl.cert.copyFromVhost = "freepbx"; ssl.cert.copyFromVhost = "freepbx";
proxy.url = mkDefault nginx.virtualHosts.freepbx.proxy.url;
local.enable = true; local.enable = true;
locations = { locations = allLocations;
"/" = {
proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/".proxyPass;
};
"/socket.io" = {
inherit (locations."/socket.io") proxy extraConfig;
proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/socket.io".proxyPass;
};
};
inherit name extraConfig kTLS; inherit name extraConfig kTLS;
}; };
}; };

View file

@ -3,25 +3,24 @@
lib, lib,
... ...
}: let }: let
inherit (lib.modules) mkIf mkMerge mkDefault; inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault;
inherit (lib.strings) escapeRegex; inherit (lib.strings) removePrefix escapeRegex;
inherit (config.services) grocy nginx; inherit (config.services) grocy nginx;
inherit (config) networking; inherit (config) networking;
name.shortServer = mkDefault "grocy"; name.shortServer = mkDefault "grocy";
serverName = "@grocy_internal"; serverName = "@grocy_internal";
serverName'local = "@grocy_internal_local"; serverName'local = "@grocy_internal_local";
extraConfig = '' extraConfig = ''
set $x_proxy_host ${serverName};
set $grocy_user ""; set $grocy_user "";
''; '';
location = { locations."/" = {
vouch.setProxyHeader = true; vouch.setProxyHeader = true;
proxy.headers.enableRecommended = true; proxy = {
extraConfig = '' enable = true;
proxy_set_header X-Grocy-User $grocy_user; headers.set.X-Grocy-User = mkOptionDefault "$grocy_user";
'';
}; };
luaAuthHost = { config, ... }: { };
luaAuthHost = { config, xvars, ... }: {
vouch.auth.lua = { vouch.auth.lua = {
enable = true; enable = true;
accessRequest = '' accessRequest = ''
@ -34,7 +33,7 @@
status = ngx.HTTP_OK, status = ngx.HTTP_OK,
header = { }, header = { },
} }
-- elseif ngx.re.match(ngx.var["x_forwarded_host"], [[grocy\.(local|tail)\.${escapeRegex networking.domain}$]]) then -- elseif ngx.re.match(ngx.var["${removePrefix "$" (xvars.get.host)}"], [[grocy\.(local|tail)\.${escapeRegex networking.domain}$]]) then
-- ngx.ctx.auth_res = { -- ngx.ctx.auth_res = {
-- status = ngx.HTTP_OK, -- status = ngx.HTTP_OK,
-- header = { }, -- header = { },
@ -54,35 +53,40 @@ in {
inherit serverName; inherit serverName;
}; };
grocy = mkMerge [ luaAuthHost { grocy = mkMerge [ luaAuthHost {
inherit name extraConfig; inherit name extraConfig locations;
vouch.enable = true; vouch.enable = true;
locations."/" = mkMerge [ location { proxy = {
proxyPass = mkIf (grocy.enable) (mkDefault url = mkIf grocy.enable (mkDefault
"http://localhost:${toString nginx.defaultHTTPListenPort}" "http://localhost:${toString nginx.defaultHTTPListenPort}"
); );
} ]; host = mkDefault serverName;
};
} ]; } ];
grocy'local = { grocy'local = {
inherit name; inherit name;
local.enable = mkDefault true; local.enable = mkDefault true;
ssl.cert.copyFromVhost = "grocy"; ssl.cert.copyFromVhost = "grocy";
locations."/" = { proxy = {
proxy.headers.enableRecommended = true; url = mkDefault "http://localhost:${toString nginx.defaultHTTPListenPort}";
proxyPass = mkDefault "http://localhost:${toString nginx.defaultHTTPListenPort}"; host = nginx.virtualHosts.grocy'local'int.serverName;
};
locations."/" = {
proxy.enable = true;
}; };
extraConfig = ''
set $x_proxy_host ${serverName'local};
'';
}; };
grocy'local'int = mkMerge [ luaAuthHost { grocy'local'int = mkMerge [ luaAuthHost {
# internal proxy workaround for http2 lua compat issues # internal proxy workaround for http2 lua compat issues
serverName = serverName'local; serverName = serverName'local;
inherit name extraConfig; inherit name extraConfig locations;
proxy = {
url = mkDefault nginx.virtualHosts.grocy.proxy.url;
host = mkDefault nginx.virtualHosts.grocy.proxy.host;
};
proxied.enable = true; proxied.enable = true;
vouch.enable = true; vouch = {
locations."/" = mkMerge [ location { enable = true;
proxyPass = mkDefault nginx.virtualHosts.grocy.locations."/".proxyPass; localSso.enable = true;
} ]; };
} ]; } ];
}; };
}; };

View file

@ -22,15 +22,17 @@ in {
# Buffering off send to the client as soon as the data is received from invidious. # Buffering off send to the client as soon as the data is received from invidious.
proxy_redirect off; proxy_redirect off;
proxy_buffering off; proxy_buffering off;
set $x_proxy_host $x_forwarded_host;
''; '';
location = { location = { xvars, ... }: {
proxy.websocket.enable = true; proxy = {
proxy.headers.enableRecommended = true; enable = true;
websocket.enable = true;
headers.enableRecommended = true;
};
extraConfig = '' extraConfig = ''
proxy_hide_header content-security-policy; proxy_hide_header content-security-policy;
add_header content-security-policy "${contentSecurityPolicy}"; add_header content-security-policy "${contentSecurityPolicy}";
proxy_cookie_domain ${virtualHosts.invidious.serverName} $x_forwarded_host; proxy_cookie_domain ${virtualHosts.invidious.serverName} ${xvars.get.host};
''; '';
}; };
name.shortServer = mkDefault "yt"; name.shortServer = mkDefault "yt";
@ -40,20 +42,22 @@ in {
invidious = { invidious = {
# lua can't handle HTTP 2.0 requests, so layer it behind another proxy... # lua can't handle HTTP 2.0 requests, so layer it behind another proxy...
inherit name extraConfig kTLS; inherit name extraConfig kTLS;
locations."/" = { proxy = {
proxyPass = "http://localhost:${toString config.services.nginx.defaultHTTPListenPort}"; url = mkDefault "http://localhost:${toString config.services.nginx.defaultHTTPListenPort}";
proxy.headers.enableRecommended = true; host = mkDefault virtualHosts.invidious'int.serverName;
};
locations."/" = { xvars, ... }: {
proxy.enable = true;
extraConfig = '' extraConfig = ''
proxy_http_version 1.1; proxy_http_version 1.1;
set $x_proxy_host ${virtualHosts.invidious'int.serverName}; set $invidious_req_check ${xvars.get.scheme}:$request_uri;
set $invidious_req_check $x_scheme:$request_uri;
if ($invidious_req_check = "http:/") { if ($invidious_req_check = "http:/") {
return ${toString virtualHosts.invidious.redirectCode} https://$x_forwarded_host$request_uri; return ${toString virtualHosts.invidious.redirectCode} https://${xvars.get.host}$request_uri;
} }
''; '';
}; };
}; };
invidious'int = { config, ... }: { invidious'int = { config, xvars, ... }: {
serverName = "@invidious_internal"; serverName = "@invidious_internal";
proxied.enable = true; proxied.enable = true;
local.denyGlobal = true; local.denyGlobal = true;
@ -82,29 +86,31 @@ in {
''; '';
}; };
}; };
proxy = {
host = mkDefault xvars.get.host;
url = mkDefault (if cfg.enable
then "http://localhost:${toString cfg.port}"
else access.proxyUrlFor { serviceName = "invidious"; }
);
};
locations = { locations = {
"/" = mkMerge [ "/" = mkMerge [
location location
{ {
vouch.requireAuth = true; vouch.requireAuth = true;
proxyPass = mkDefault (if cfg.enable
then "http://localhost:${toString cfg.port}"
else access.proxyUrlFor { serviceName = "invidious"; }
);
} }
]; ];
}; };
inherit extraConfig; inherit extraConfig;
}; };
invidious'local = { invidious'local = { xvars, ... }: {
local.enable = true; local.enable = true;
ssl.cert.copyFromVhost = "invidious"; ssl.cert.copyFromVhost = "invidious";
locations."/" = mkMerge [ proxy = {
location host = mkDefault xvars.get.host;
{ url = mkDefault virtualHosts.invidious'int.proxy.url;
proxyPass = mkDefault virtualHosts.invidious'int.locations."/".proxyPass; };
} locations."/" = location;
];
inherit name extraConfig kTLS; inherit name extraConfig kTLS;
}; };
}; };

View file

@ -6,6 +6,7 @@
}: let }: let
inherit (lib.modules) mkDefault; inherit (lib.modules) mkDefault;
inherit (config.services) nginx; inherit (config.services) nginx;
cfg = config.services.unifi;
in { in {
config.services.nginx = { config.services.nginx = {
virtualHosts = let virtualHosts = let
@ -13,24 +14,34 @@ in {
proxy_redirect off; proxy_redirect off;
proxy_buffering off; proxy_buffering off;
''; '';
locations = {
"/" = {
proxy.enable = true;
};
"/wss/" = {
proxy = {
enable = true;
websocket.enable = true;
};
};
};
name.shortServer = mkDefault "unifi"; name.shortServer = mkDefault "unifi";
kTLS = mkDefault true; kTLS = mkDefault true;
in { in {
unifi = { unifi = {
inherit name extraConfig kTLS; inherit name extraConfig kTLS locations;
vouch.enable = mkDefault true; vouch.enable = mkDefault true;
ssl.force = mkDefault true; ssl.force = mkDefault true;
locations."/" = { proxy.url = mkDefault (if cfg.enable
proxyPass = mkDefault (access.proxyUrlFor { serviceName = "unifi"; portName = "management"; }); then "https://localhost:8443"
}; else access.proxyUrlFor { serviceName = "unifi"; portName = "management"; }
);
}; };
unifi'local = { unifi'local = {
inherit name extraConfig kTLS; inherit name extraConfig kTLS locations;
ssl.cert.copyFromVhost = "unifi"; ssl.cert.copyFromVhost = "unifi";
local.enable = true; local.enable = true;
locations."/" = { proxy.url = mkDefault nginx.virtualHosts.unifi.proxy.url;
proxyPass = mkDefault nginx.virtualHosts.unifi.locations."/".proxyPass;
};
}; };
}; };
}; };

View file

@ -14,52 +14,51 @@ in {
locations = { locations = {
"/" = { "/" = {
ssl.force = true; ssl.force = true;
proxy.enable = true;
extraConfig = '' extraConfig = ''
proxy_redirect default; proxy_redirect default;
set $x_proxy_host $x_forwarded_host;
''; '';
}; };
"/validate" = {config, virtualHost, ...}: { "/validate" = {config, virtualHost, ...}: {
proxied.enable = true; proxied.enable = true;
proxyPass = mkDefault (virtualHost.locations."/".proxyPass + "/validate"); proxy.enable = true;
proxy.headers.enableRecommended = true;
local.denyGlobal = true; local.denyGlobal = true;
extraConfig = ''
set $x_proxy_host $x_forwarded_host;
'';
}; };
}; };
localLocations = kanidmDomain: mkIf (nginx.vouch.localSso.enable && false) { localLocations = kanidmDomain: mkIf (nginx.vouch.localSso.enable && false) {
"/" = { "/" = { xvars, ... }: {
proxied.xvars.enable = true;
extraConfig = '' extraConfig = ''
proxy_redirect https://sso.${networking.domain}/ $x_scheme://${kanidmDomain}/; proxy_redirect https://sso.${networking.domain}/ ${xvars.get.scheme}://${kanidmDomain}/;
''; '';
}; };
}; };
name.shortServer = mkDefault "login"; name.shortServer = mkDefault "login";
in { in {
vouch = { vouch = { xvars, ... }: {
inherit name; inherit name locations;
serverAliases = [ nginx.vouch.doubleProxy.serverName ]; serverAliases = [ nginx.vouch.doubleProxy.serverName ];
proxied.enable = true; proxied.enable = true;
local.denyGlobal = true; proxy = {
locations = mkMerge [ url = mkDefault (
locations
{
"/".proxyPass = mkDefault (
access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login"; } access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login"; }
); );
} host = mkDefault xvars.get.host;
];
}; };
vouch'local = { local.denyGlobal = true;
};
vouch'local = { xvars, ... }: {
name = { name = {
inherit (name) shortServer; inherit (name) shortServer;
includeTailscale = mkDefault false; includeTailscale = mkDefault false;
}; };
serverAliases = mkIf cfg.enable [ nginx.vouch.doubleProxy.localServerName ]; serverAliases = mkIf cfg.enable [ nginx.vouch.doubleProxy.localServerName ];
proxied.enable = true; proxied.enable = true;
proxy = {
url = mkDefault (
access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login.local"; }
);
host = mkDefault xvars.get.host;
};
local.enable = true; local.enable = true;
ssl = { ssl = {
force = true; force = true;
@ -67,15 +66,10 @@ in {
}; };
locations = mkMerge [ locations = mkMerge [
locations locations
{
"/".proxyPass = mkDefault (
access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login.local"; }
);
}
(localLocations "sso.local.${networking.domain}") (localLocations "sso.local.${networking.domain}")
]; ];
}; };
vouch'tail = { vouch'tail = { xvars, ... }: {
enable = mkDefault (tailscale.enable && !nginx.virtualHosts.vouch'local.name.includeTailscale); enable = mkDefault (tailscale.enable && !nginx.virtualHosts.vouch'local.name.includeTailscale);
ssl.cert.copyFromVhost = "vouch'local"; ssl.cert.copyFromVhost = "vouch'local";
name = { name = {
@ -83,11 +77,12 @@ in {
qualifier = mkDefault "tail"; qualifier = mkDefault "tail";
}; };
local.enable = true; local.enable = true;
proxy = {
url = mkDefault nginx.virtualHosts.vouch'local.locations."/".proxyPass;
host = mkDefault xvars.get.host;
};
locations = mkMerge [ locations = mkMerge [
locations locations
{
"/".proxyPass = mkDefault nginx.virtualHosts.vouch'local.locations."/".proxyPass;
}
(localLocations "sso.tail.${networking.domain}") (localLocations "sso.tail.${networking.domain}")
]; ];
}; };

View file

@ -11,26 +11,25 @@ in {
EXTERNAL_GROCY_URL = "https://grocy.${config.networking.domain}"; EXTERNAL_GROCY_URL = "https://grocy.${config.networking.domain}";
DISABLE_AUTHENTICATION = true; DISABLE_AUTHENTICATION = true;
}; };
nginxConfig = mkMerge [ nginxConfig = let
xvars = nginx.virtualHosts.barcodebuddy'php.xvars.lib;
in mkMerge [
'' ''
include ${config.sops.secrets.barcodebuddy-fastcgi-params.path}; include ${config.sops.secrets.barcodebuddy-fastcgi-params.path};
'' ''
(mkIf cfg.reverseProxy.enable (mkAfter '' (mkIf cfg.reverseProxy.enable (mkAfter ''
set $bbuddy_https ""; set $bbuddy_https "";
if ($x_scheme = https) { if (${xvars.get.scheme} = https) {
set $bbuddy_https 1; set $bbuddy_https 1;
} }
fastcgi_param HTTPS $bbuddy_https if_not_empty; fastcgi_param HTTPS $bbuddy_https if_not_empty;
fastcgi_param REQUEST_SCHEME $x_scheme; fastcgi_param REQUEST_SCHEME ${xvars.get.scheme};
fastcgi_param HTTP_HOST $x_forwarded_host; fastcgi_param HTTP_HOST ${xvars.get.host};
'')) ''))
]; ];
}; };
config.services.nginx.virtualHosts.barcodebuddy'php = mkIf cfg.enable { config.services.nginx.virtualHosts.barcodebuddy'php = mkIf cfg.enable {
proxied = { proxied.enable = cfg.reverseProxy.enable;
enable = cfg.reverseProxy.enable;
xvars.enable = true;
};
name.shortServer = mkDefault "bbuddy"; name.shortServer = mkDefault "bbuddy";
}; };
config.users.users.barcodebuddy = mkIf cfg.enable { config.users.users.barcodebuddy = mkIf cfg.enable {

View file

@ -11,7 +11,8 @@ in {
currency = mkDefault "CAD"; currency = mkDefault "CAD";
}; };
}; };
services.nginx = let services.nginx.virtualHosts = {
grocy'php = mkIf cfg.enable ({config, xvars, ...}: let
extraConfig = mkAfter '' extraConfig = mkAfter ''
set $grocy_user guest; set $grocy_user guest;
set $grocy_middleware Grocy\Middleware\ReverseProxyAuthMiddleware; set $grocy_middleware Grocy\Middleware\ReverseProxyAuthMiddleware;
@ -42,28 +43,23 @@ in {
fastcgi_param GENSO_GROCY_USER $grocy_user; fastcgi_param GENSO_GROCY_USER $grocy_user;
set $grocy_https ""; set $grocy_https "";
if ($x_scheme = https) { if (${xvars.get.scheme} = https) {
set $grocy_https 1; set $grocy_https 1;
} }
fastcgi_param HTTP_HOST $x_forwarded_host; fastcgi_param HTTP_HOST ${xvars.get.host};
fastcgi_param REQUEST_SCHEME $x_scheme; fastcgi_param REQUEST_SCHEME ${xvars.get.scheme};
fastcgi_param HTTPS $grocy_https if_not_empty; fastcgi_param HTTPS $grocy_https if_not_empty;
''; '';
in { in {
virtualHosts = {
grocy'php = mkIf cfg.enable ({config, ...}: {
name.shortServer = mkDefault "grocy"; name.shortServer = mkDefault "grocy";
proxied = { proxied.enable = true;
enable = true;
xvars.enable = true; xvars.enable = true;
};
local.denyGlobal = true; local.denyGlobal = true;
locations."~ \\.php$" = { locations."~ \\.php$" = {
inherit extraConfig; inherit extraConfig;
}; };
}); });
}; };
};
users.users.grocy = mkIf cfg.enable { users.users.grocy = mkIf cfg.enable {
uid = 911; uid = 911;
}; };

View file

@ -72,7 +72,7 @@ in {
services.vouch-proxy = { services.vouch-proxy = {
authUrl = "https://${virtualHosts.keycloak'local.serverName}/realms/${config.networking.domain}"; authUrl = "https://${virtualHosts.keycloak'local.serverName}/realms/${config.networking.domain}";
domain = "login.local.${config.networking.domain}"; domain = "login.local.${config.networking.domain}";
#cookie.domain = "local.${config.networking.domain}"; settings.cookie.domain = "local.${config.networking.domain}";
}; };
security.acme.certs = { security.acme.certs = {
@ -257,13 +257,13 @@ in {
# not the real grocy record-holder, so don't respond globally.. # not the real grocy record-holder, so don't respond globally..
local.denyGlobal = true; local.denyGlobal = true;
ssl.cert.enable = true; ssl.cert.enable = true;
locations."/".proxyPass = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; proxy.url = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}";
}; };
barcodebuddy = { barcodebuddy = {
# not the real bbuddy record-holder, so don't respond globally.. # not the real bbuddy record-holder, so don't respond globally..
local.denyGlobal = true; local.denyGlobal = true;
ssl.cert.enable = true; ssl.cert.enable = true;
locations."/".proxyPass = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; proxy.url = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}";
}; };
freepbx = { freepbx = {
ssl.cert.enable = true; ssl.cert.enable = true;