fix(nginx): proxied listen

This commit is contained in:
arcnmx 2024-04-29 12:01:35 -07:00
parent f2c7178486
commit f9b02a03a4
18 changed files with 185 additions and 90 deletions

View file

@ -111,7 +111,7 @@
}; };
conditions = mkOption { conditions = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = "iifname ${name}"; default = [ "iifname ${name}" ];
}; };
}; };
}; };

View file

@ -1,11 +1,4 @@
{ let
lib,
inputs,
...
}: let
inherit (inputs.self.lib.lib) mkJustBefore mkAlmostOptionDefault orderJustBefore;
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkMerge mkOrder mkDefault mkOptionDefault;
xHeadersProxied = { xvars }: '' xHeadersProxied = { xvars }: ''
${xvars.init "forwarded_for" "$proxy_add_x_forwarded_for"} ${xvars.init "forwarded_for" "$proxy_add_x_forwarded_for"}
if ($http_x_forwarded_proto) { if ($http_x_forwarded_proto) {
@ -25,7 +18,10 @@
${xvars.init "forwarded_server" "$http_x_forwarded_server"} ${xvars.init "forwarded_server" "$http_x_forwarded_server"}
} }
''; '';
locationModule = { config, virtualHost, xvars, ... }: let locationModule = { config, virtualHost, xvars, gensokyo-zone, lib, ... }: let
inherit (gensokyo-zone.lib) mkJustBefore mkAlmostOptionDefault;
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
cfg = config.proxied; cfg = config.proxied;
in { in {
options = with lib.types; { options = with lib.types; {
@ -69,7 +65,11 @@
]; ];
}; };
}; };
hostModule = { config, xvars, ... }: let hostModule = { config, nixosConfig, xvars, gensokyo-zone, lib, ... }: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault orderJustBefore unmerged;
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkOrder mkDefault;
inherit (nixosConfig.services) nginx;
cfg = config.proxied; cfg = config.proxied;
in { in {
options = with lib.types; { options = with lib.types; {
@ -82,6 +82,14 @@
type = bool; type = bool;
default = cfg.enable != false; default = cfg.enable != false;
}; };
cloudflared = {
ingressSettings = mkOption {
type = unmerged.types.attrs;
};
getIngress = mkOption {
type = functionTo unspecified;
};
};
}; };
locations = mkOption { locations = mkOption {
type = attrsOf (submoduleWith { type = attrsOf (submoduleWith {
@ -91,18 +99,83 @@
}; };
}; };
config = { config = let
listenProxied = cfg.enabled;
in {
proxied = {
cloudflared = let
listen = config.listen'.proxied;
scheme = if listen.ssl then "https" else "http";
in mkIf (cfg.enable == "cloudflared") {
ingressSettings.${config.serverName} = {
service = "${scheme}://localhost:${toString listen.port}";
originRequest.${if scheme == "https" then "noTLSVerify" else null} = true;
};
getIngress = {}: unmerged.mergeAttrs cfg.cloudflared.ingressSettings;
};
};
xvars.enable = mkIf cfg.enabled true; xvars.enable = mkIf cfg.enabled true;
local.denyGlobal = mkIf (cfg.enable == "cloudflared") (mkDefault true); local.denyGlobal = mkIf listenProxied (mkDefault true);
listen' = mkIf listenProxied {
proxied = {
addr = "[::]";
port = mkAlmostOptionDefault nginx.proxied.listenPort;
};
};
extraConfig = mkIf (cfg.enabled && config.xvars.enable) ( extraConfig = mkIf (cfg.enabled && config.xvars.enable) (
mkOrder (orderJustBefore + 25) (xHeadersProxied { inherit xvars; }) mkOrder (orderJustBefore + 25) (xHeadersProxied { inherit xvars; })
); );
}; };
}; };
in { in {
options = with lib.types; { config,
services.nginx.virtualHosts = mkOption { system,
lib,
...
}: let
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkOptionDefault;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) any;
inherit (config.services) nginx;
cfg = nginx.proxied;
in {
options.services.nginx = with lib.types; {
proxied = {
enabled = mkOption {
type = bool;
};
listenPort = mkOption {
type = port;
default = 9080;
};
};
virtualHosts = mkOption {
type = attrsOf (submodule [hostModule]); type = attrsOf (submodule [hostModule]);
}; };
}; };
config = {
services.nginx = let
hasProxiedHosts = any (virtualHost: virtualHost.enable && virtualHost.proxied.enabled) (attrValues nginx.virtualHosts);
in {
proxied = {
enabled = mkOptionDefault hasProxiedHosts;
};
upstreams' = {
nginx'proxied = mkIf cfg.enabled {
servers.local = {
accessService = {
system = system.name;
name = "nginx";
port = "proxied";
};
};
};
};
# TODO: virtualHosts.fallback'proxied.reuseport = true;
};
networking.firewall.interfaces.lan = mkIf nginx.enable {
allowedTCPPorts = mkIf cfg.enabled [ cfg.listenPort ];
};
};
} }

View file

@ -297,22 +297,23 @@ in {
}; };
vouch'proxy = { vouch'proxy = {
enable = vouch.enable && vouch.doubleProxy.enable; enable = vouch.enable && vouch.doubleProxy.enable;
# TODO: need exported hosts options for this to detect the correct host/port/etc
servers = { servers = {
lan = { upstream, ... }: { lan = { upstream, ... }: {
enable = mkAlmostOptionDefault (!upstream.servers.int.enable); enable = mkAlmostOptionDefault (!upstream.servers.int.enable);
addr = mkAlmostOptionDefault "login.local.${networking.domain}"; addr = mkAlmostOptionDefault "login.local.${networking.domain}";
port = mkOptionDefault null; port = mkOptionDefault 9080;
ssl.enable = mkAlmostOptionDefault true; ssl.enable = mkAlmostOptionDefault true;
}; };
int = { upstream, ... }: { int = { upstream, ... }: {
enable = mkAlmostOptionDefault system.network.networks.int.enable or false; enable = mkAlmostOptionDefault system.network.networks.int.enable or false;
addr = mkAlmostOptionDefault "login.int.${networking.domain}"; addr = mkAlmostOptionDefault "login.int.${networking.domain}";
port = mkOptionDefault null; port = mkOptionDefault 9080;
}; };
tail = { upstream, ... }: { tail = { upstream, ... }: {
enable = mkAlmostOptionDefault (tailscale.enable && !upstream.servers.lan.enable && !upstream.servers.int.enable); enable = mkAlmostOptionDefault (tailscale.enable && !upstream.servers.lan.enable && !upstream.servers.int.enable);
addr = mkAlmostOptionDefault "login.tail.${networking.domain}"; addr = mkAlmostOptionDefault "login.tail.${networking.domain}";
port = mkOptionDefault null; port = mkOptionDefault 9080;
}; };
}; };
}; };

View file

@ -93,7 +93,7 @@ in {
local = mkIf cfg.enable { local = mkIf cfg.enable {
allowedTCPPorts = mkIf (any (user: user.authentication.local.allow) cfg.ensureUsers) [cfg.settings.port]; allowedTCPPorts = mkIf (any (user: user.authentication.local.allow) cfg.ensureUsers) [cfg.settings.port];
}; };
int = mkIf cfg.enable { lan = mkIf cfg.enable {
allowedTCPPorts = mkIf (any (user: user.authentication.int.allow) cfg.ensureUsers) [cfg.settings.port]; allowedTCPPorts = mkIf (any (user: user.authentication.int.allow) cfg.ensureUsers) [cfg.settings.port];
}; };
}; };

View file

@ -11,12 +11,22 @@ in {
assertion = config.ports.http.port == cfg.defaultHTTPListenPort && config.ports.https.port == cfg.defaultSSLListenPort; assertion = config.ports.http.port == cfg.defaultHTTPListenPort && config.ports.https.port == cfg.defaultSSLListenPort;
message = "ports mismatch"; message = "ports mismatch";
}; };
assertProxied = nixosConfig: cfg: {
assertion = config.ports.proxied.enable == cfg.proxied.enabled;
message = "proxied mismatch";
};
assertProxiedPort = nixosConfig: cfg: {
assertion = !config.ports.proxied.enable || config.ports.proxied.port == cfg.proxied.listenPort;
message = "proxied.port mismatch";
};
in { in {
nixos = { nixos = {
serviceAttr = "nginx"; serviceAttr = "nginx";
assertions = mkIf config.enable [ assertions = mkIf config.enable (map mkAssertion [
(mkAssertion assertPorts) assertPorts
]; assertProxied
assertProxiedPort
]);
}; };
defaults.port.listen = mkAlmostOptionDefault "lan"; defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) { ports = mapAttrs (_: mapAlmostOptionDefaults) {
@ -29,6 +39,12 @@ in {
port = 443; port = 443;
protocol = "https"; protocol = "https";
}; };
proxied = {
enable = false;
port = 9080;
protocol = "http";
listen = "lan";
};
}; };
}; };
} }

View file

@ -22,8 +22,8 @@ in {
requireAuth = false; requireAuth = false;
}; };
proxy = { proxy = {
url = mkIf barcodebuddy.enable (mkDefault upstream = mkIf barcodebuddy.enable (mkDefault
"http://localhost:${toString nginx.defaultHTTPListenPort}" "nginx'proxied"
); );
host = mkDefault serverName; host = mkDefault serverName;
}; };
@ -42,7 +42,7 @@ in {
ssl.cert.copyFromVhost = "barcodebuddy"; ssl.cert.copyFromVhost = "barcodebuddy";
local.enable = mkDefault true; local.enable = mkDefault true;
proxy = { proxy = {
url = mkDefault nginx.virtualHosts.barcodebuddy.proxy.url; upstream = mkDefault nginx.virtualHosts.barcodebuddy.proxy.upstream;
host = mkDefault nginx.virtualHosts.barcodebuddy.proxy.host; host = mkDefault nginx.virtualHosts.barcodebuddy.proxy.host;
}; };
locations."/" = { config, ... }: { locations."/" = { config, ... }: {

View file

@ -59,8 +59,8 @@ in {
inherit name extraConfig locations; inherit name extraConfig locations;
vouch.enable = true; vouch.enable = true;
proxy = { proxy = {
url = mkIf grocy.enable (mkDefault upstream = mkIf grocy.enable (mkDefault
"http://localhost:${toString nginx.defaultHTTPListenPort}" "nginx'proxied"
); );
host = mkDefault serverName; host = mkDefault serverName;
}; };
@ -70,7 +70,7 @@ in {
local.enable = mkDefault true; local.enable = mkDefault true;
ssl.cert.copyFromVhost = "grocy"; ssl.cert.copyFromVhost = "grocy";
proxy = { proxy = {
url = mkDefault "http://localhost:${toString nginx.defaultHTTPListenPort}"; upstream = mkDefault "nginx'proxied";
host = nginx.virtualHosts.grocy'local'int.serverName; host = nginx.virtualHosts.grocy'local'int.serverName;
}; };
locations."/" = { locations."/" = {
@ -82,7 +82,7 @@ in {
serverName = serverName'local; serverName = serverName'local;
inherit name extraConfig locations; inherit name extraConfig locations;
proxy = { proxy = {
url = mkDefault nginx.virtualHosts.grocy.proxy.url; upstream = mkDefault nginx.virtualHosts.grocy.proxy.upstream;
host = mkDefault nginx.virtualHosts.grocy.proxy.host; host = mkDefault nginx.virtualHosts.grocy.proxy.host;
}; };
proxied.enable = true; proxied.enable = true;

View file

@ -1,5 +1,6 @@
{ {
config, config,
system,
lib, lib,
... ...
}: let }: let
@ -29,8 +30,9 @@ in {
enable = mkDefault nginx.virtualHosts.invidious'int.enable; enable = mkDefault nginx.virtualHosts.invidious'int.enable;
host = mkDefault nginx.virtualHosts.invidious'int.serverName; host = mkDefault nginx.virtualHosts.invidious'int.serverName;
servers.local = { servers.local = {
addr = mkDefault "localhost"; accessService = {
port = nginx.defaultHTTPListenPort; inherit (nginx.upstreams'.nginx'proxied.servers.local.accessService) system name id port;
};
}; };
}; };
}; };

View file

@ -16,7 +16,7 @@ in {
invidious_hmac_key = commonSecret; invidious_hmac_key = commonSecret;
}; };
networking.firewall.interfaces.int.allowedTCPPorts = [cfg.port]; networking.firewall.interfaces.lan.allowedTCPPorts = [cfg.port];
users.groups.invidious = {}; users.groups.invidious = {};
users.users.invidious = { users.users.invidious = {
isSystemUser = true; isSystemUser = true;

View file

@ -1,9 +1,8 @@
{inputs, system, config, lib, ...}: let {inputs, system, access, config, lib, ...}: let
inherit (lib.modules) mkIf mkForce mkDefault; inherit (lib.modules) mkIf mkForce mkDefault;
inherit (lib.lists) optional; inherit (lib.lists) optional;
inherit (config.lib.access) mkSnakeOil;
cfg = config.services.keycloak; cfg = config.services.keycloak;
cert = mkSnakeOil { cert = access.mkSnakeOil {
name = "keycloak-selfsigned"; name = "keycloak-selfsigned";
domain = hostname; domain = hostname;
}; };
@ -33,7 +32,7 @@ in {
}; };
}; };
networking.firewall.interfaces.int.allowedTCPPorts = mkIf cfg.enable [ networking.firewall.interfaces.lan.allowedTCPPorts = mkIf cfg.enable [
cfg.port cfg.port
]; ];
systemd.services.keycloak = mkIf cfg.enable { systemd.services.keycloak = mkIf cfg.enable {
@ -43,11 +42,15 @@ in {
services.keycloak = { services.keycloak = {
enable = true; enable = true;
database = { database = let
host = "postgresql.int.${config.networking.domain}"; system = access.systemForService "postgresql";
inherit (system.exports.services) postgresql;
in {
host = access.getAddressFor system.name "lan";
port = postgresql.ports.default.port;
passwordFile = config.sops.secrets.keycloak_db_password.path; passwordFile = config.sops.secrets.keycloak_db_password.path;
createLocally = false; createLocally = false;
useSSL = false; useSSL = postgresql.ports.default.ssl;
}; };
settings = { settings = {

View file

@ -1,13 +1,15 @@
{ {
lib, config,
inputs,
modulesPath,
system, system,
gensokyo-zone,
lib,
modulesPath,
... ...
}: let }: let
inherit (inputs.self.lib.lib) unmerged; inherit (gensokyo-zone.lib) unmerged coalesce;
inherit (lib.modules) mkIf mkMerge mkDefault; inherit (lib.modules) mkIf mkMerge mkDefault;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit (lib.trivial) mapNullable;
inherit (system) proxmox; inherit (system) proxmox;
in { in {
imports = [ imports = [
@ -28,11 +30,15 @@ in {
networks.${interface.networkd.name} = unmerged.mergeAttrs interface.networkd.networkSettings; networks.${interface.networkd.name} = unmerged.mergeAttrs interface.networkd.networkSettings;
}) proxmox.network.interfaces)); }) proxmox.network.interfaces));
networking.firewall.interfaces.int = let networking.firewall.interfaces.lan = let
inherit (proxmox.network.internal) interface; inherit (proxmox.network) internal local;
in mkIf (interface != null) { conditions = coalesce [
nftables.conditions = [ (mapNullable (interface: [ "iifname ${interface.name}" ]) internal.interface)
"iifname ${interface.name}" (mapNullable (interface: config.networking.interfaces.local.nftables.conditions) local.interface)
]; ];
in mkIf (conditions != null) {
nftables = {
inherit conditions;
};
}; };
} }

View file

@ -25,7 +25,7 @@ in {
}; };
networking.firewall = mkIf cfg.enable { networking.firewall = mkIf cfg.enable {
interfaces.int = { interfaces.lan = {
allowedTCPPorts = [ allowedTCPPorts = [
8443 # remote login 8443 # remote login
]; ];

View file

@ -40,6 +40,7 @@
listen = mkIf (!preread) "wan"; listen = mkIf (!preread) "wan";
}; };
http.listen = "wan"; http.listen = "wan";
proxied.enable = true;
}; };
}; };
sshd = { sshd = {

View file

@ -53,18 +53,16 @@ in {
}; };
services.cloudflared = let services.cloudflared = let
inherit (nginx) defaultHTTPListenPort;
tunnelId = "964121e3-b3a9-4cc1-8480-954c4728b604"; tunnelId = "964121e3-b3a9-4cc1-8480-954c4728b604";
localNginx = "http://localhost:${toString defaultHTTPListenPort}";
in { in {
tunnels.${tunnelId} = { tunnels.${tunnelId} = {
default = "http_status:404"; default = "http_status:404";
credentialsFile = config.sops.secrets.cloudflared-tunnel-hakurei.path; credentialsFile = config.sops.secrets.cloudflared-tunnel-hakurei.path;
ingress = { ingress = mkMerge [
${virtualHosts.prox.serverName}.service = localNginx; (virtualHosts.freeipa'web.proxied.cloudflared.getIngress {})
${virtualHosts.gensokyoZone.serverName}.service = localNginx; (virtualHosts.prox.proxied.cloudflared.getIngress {})
${virtualHosts.freeipa'web.serverName}.service = localNginx; (virtualHosts.gensokyoZone.proxied.cloudflared.getIngress {})
}; ];
}; };
}; };
@ -219,6 +217,12 @@ in {
upstreams' = { upstreams' = {
vouch'auth.servers.local.enable = false; vouch'auth.servers.local.enable = false;
vouch'auth'local.servers.local.enable = true; vouch'auth'local.servers.local.enable = true;
tei'nginx'proxied.servers.nginx.accessService = {
# TODO: host exports
system = "tei";
name = "nginx";
port = "proxied";
};
}; };
stream.servers = { stream.servers = {
mosquitto.ssl.cert.name = "mosquitto"; mosquitto.ssl.cert.name = "mosquitto";
@ -261,13 +265,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;
proxy.url = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; proxy.upstream = "tei'nginx'proxied";
}; };
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;
proxy.url = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; proxy.upstream = "tei'nginx'proxied";
}; };
freepbx = { freepbx = {
ssl.cert.enable = true; ssl.cert.enable = true;

View file

@ -4,11 +4,10 @@
access, access,
... ...
}: let }: let
inherit (lib.modules) mkIf; inherit (lib.modules) mkMerge;
inherit (config.services) home-assistant nginx; inherit (config.services) home-assistant nginx;
cfg = config.services.cloudflared; cfg = config.services.cloudflared;
apartment = "5e85d878-c6b2-4b15-b803-9aeb63d63543"; apartment = "5e85d878-c6b2-4b15-b803-9aeb63d63543";
localNginx = "http://localhost:${toString nginx.defaultHTTPListenPort}";
in { in {
sops.secrets.cloudflared-tunnel-apartment.owner = cfg.user; sops.secrets.cloudflared-tunnel-apartment.owner = cfg.user;
services.cloudflared = { services.cloudflared = {
@ -16,28 +15,17 @@ in {
${apartment} = { ${apartment} = {
credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path; credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path;
default = "http_status:404"; default = "http_status:404";
ingress = { ingress = mkMerge [
${nginx.virtualHosts.zigbee2mqtt.serverName} = { (nginx.virtualHosts.zigbee2mqtt.proxied.cloudflared.getIngress {})
service = localNginx; (nginx.virtualHosts.grocy.proxied.cloudflared.getIngress {})
}; (nginx.virtualHosts.barcodebuddy.proxied.cloudflared.getIngress {})
${nginx.virtualHosts.grocy.serverName} = { {
service = localNginx;
};
${nginx.virtualHosts.barcodebuddy.serverName} = {
service = localNginx;
};
${home-assistant.domain} = assert home-assistant.enable; { ${home-assistant.domain} = assert home-assistant.enable; {
service = access.proxyUrlFor { serviceName = "home-assistant"; }; service = access.proxyUrlFor { serviceName = "home-assistant"; };
}; };
}; }
};
};
};
systemd.services."cloudflared-tunnel-${apartment}" = rec {
wants = mkIf config.services.tailscale.enable [
"tailscaled.service"
]; ];
after = wants; };
};
}; };
} }

View file

@ -10,7 +10,10 @@ _: {
exports = { exports = {
services = { services = {
sshd.enable = true; sshd.enable = true;
nginx.enable = true; nginx = {
enable = true;
ports.proxied.enable = true;
};
tailscale.enable = true; tailscale.enable = true;
home-assistant.enable = true; home-assistant.enable = true;
zigbee2mqtt.enable = true; zigbee2mqtt.enable = true;

View file

@ -10,7 +10,10 @@ _: {
exports = { exports = {
services = { services = {
sshd.enable = true; sshd.enable = true;
nginx.enable = true; nginx = {
enable = true;
ports.proxied.enable = true;
};
unifi.enable = true; unifi.enable = true;
mosquitto.enable = true; mosquitto.enable = true;
dnsmasq.enable = true; dnsmasq.enable = true;

View file

@ -18,18 +18,13 @@ in {
]; ];
services.cloudflared = let services.cloudflared = let
inherit (nginx) virtualHosts defaultHTTPListenPort; inherit (nginx) virtualHosts;
tunnelId = "28bcd3fc-3467-4997-806b-546ba9995028"; tunnelId = "28bcd3fc-3467-4997-806b-546ba9995028";
localNginx = "http://localhost:${toString defaultHTTPListenPort}";
in { in {
tunnels.${tunnelId} = { tunnels.${tunnelId} = {
default = "http_status:404"; default = "http_status:404";
credentialsFile = config.sops.secrets.cloudflared-tunnel-utsuho.path; credentialsFile = config.sops.secrets.cloudflared-tunnel-utsuho.path;
ingress = { ingress = virtualHosts.unifi.proxied.cloudflared.getIngress {};
${virtualHosts.unifi.serverName} = {
service = localNginx;
};
};
}; };
}; };