refactor(reverse-proxy): use route registry
This commit is contained in:
77
flake.nix
77
flake.nix
@@ -202,7 +202,7 @@
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes."budget.alexion.dev" = {
|
||||
routes.test."budget.alexion.dev" = {
|
||||
path = "/";
|
||||
backend = "http://10.23.20.42:5006";
|
||||
};
|
||||
@@ -224,7 +224,7 @@
|
||||
reverse-proxy-actualbudget-guest-route =
|
||||
let
|
||||
testConfig = self.nixosConfigurations.pikachu.config;
|
||||
route = testConfig.modules.reverse-proxy.routes.actualbudget;
|
||||
route = testConfig.registry.route.pikachu.actualbudget;
|
||||
vhost = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts."budget.alexion.dev";
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-actualbudget-guest-route-check" { } ''
|
||||
@@ -233,7 +233,6 @@
|
||||
!(
|
||||
route.host == "budget.alexion.dev"
|
||||
&& route.backend == null
|
||||
&& route.endpoint == "pikachu-actualbudget"
|
||||
&& route.port == 5006
|
||||
&& route.tls
|
||||
&& route.aliases == [ ]
|
||||
@@ -361,25 +360,25 @@
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
self.lib.registry.module
|
||||
{
|
||||
modules.reverse-proxy.routes.app = {
|
||||
registry.route.routeHost.app = {
|
||||
host = "worker.alexion.dev";
|
||||
backend = "http://10.23.20.126:3923";
|
||||
};
|
||||
system.stateVersion = "26.11";
|
||||
}
|
||||
];
|
||||
};
|
||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
registry = self.lib.registry.collect {
|
||||
hosts = {
|
||||
inherit routeHost;
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
};
|
||||
};
|
||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
||||
inherit routeHost;
|
||||
pikachu = self.nixosConfigurations.pikachu;
|
||||
} endpoints;
|
||||
testConfig =
|
||||
(self.nixosConfigurations.pikachu.extendModules {
|
||||
specialArgs.reverseProxyFleetRoutes = fleetRoutes;
|
||||
specialArgs.registry = registry;
|
||||
}).config;
|
||||
vhosts = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts;
|
||||
in
|
||||
@@ -387,8 +386,9 @@
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
fleetRoutes ? routeHost-app
|
||||
&& fleetRoutes ? pikachu-actualbudget
|
||||
registry.route ? routeHost
|
||||
&& registry.route.routeHost ? app
|
||||
&& registry.route.pikachu ? actualbudget
|
||||
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
&& vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006"
|
||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
@@ -398,9 +398,9 @@
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-endpoint-backends =
|
||||
reverse-proxy-address-backends =
|
||||
let
|
||||
endpointHost =
|
||||
workerHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
@@ -408,22 +408,11 @@
|
||||
self.lib.registry.module
|
||||
{
|
||||
registry.address.worker.self = "10.23.50.146";
|
||||
system.stateVersion = "26.11";
|
||||
}
|
||||
];
|
||||
};
|
||||
routeHost =
|
||||
lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy.routes.app = {
|
||||
registry.route.worker.self = {
|
||||
host = "worker.alexion.dev";
|
||||
endpoint = "worker";
|
||||
port = 8080;
|
||||
};
|
||||
system.stateVersion = "26.11";
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -454,36 +443,40 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
||||
worker = endpointHost;
|
||||
pikachu = guestRouteHost;
|
||||
registry = self.lib.registry.collect {
|
||||
hosts = {
|
||||
worker = workerHost;
|
||||
pikachu = guestRouteHost;
|
||||
};
|
||||
};
|
||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
||||
inherit routeHost;
|
||||
pikachu = guestRouteHost;
|
||||
} endpoints;
|
||||
testConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
specialArgs.my = self.lib;
|
||||
specialArgs = {
|
||||
my = self.lib;
|
||||
inherit registry;
|
||||
};
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes = fleetRoutes;
|
||||
routes = registry.route;
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
vhosts = testConfig.services.nginx.virtualHosts;
|
||||
workerFacts = self.lib.registry.lookup registry "worker" "self";
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-endpoint-backends-check" { } ''
|
||||
pkgs.runCommand "reverse-proxy-address-backends-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(
|
||||
vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080"
|
||||
&& fleetRoutes."pikachu-files".endpoint == "pikachu-files"
|
||||
workerFacts.address == "10.23.50.146"
|
||||
&& workerFacts.route.host == "worker.alexion.dev"
|
||||
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080"
|
||||
&& registry.route.pikachu.files.port == 3923
|
||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||
)
|
||||
) "exit 1"
|
||||
@@ -502,7 +495,7 @@
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes.raichu-files = {
|
||||
routes.raichu.files = {
|
||||
host = "files.alexion.dev";
|
||||
path = "/raichu";
|
||||
backend = "http://10.23.20.10:3923";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
config,
|
||||
lib,
|
||||
my,
|
||||
reverseProxyFleetRoutes ? config.modules.reverse-proxy.routes,
|
||||
registry,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -35,7 +35,7 @@ in
|
||||
interior = {
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes = reverseProxyFleetRoutes // legacyRoutes;
|
||||
routes = registry.route // { legacy = legacyRoutes; };
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 2022 ];
|
||||
|
||||
@@ -16,7 +16,6 @@ let
|
||||
serviceIdentityRegistry = import ./service-identities.nix { inherit lib; };
|
||||
registry = import ./registry.nix { inherit lib self; };
|
||||
networkEndpoints = import ./network-endpoints.nix { inherit lib; };
|
||||
reverseProxyRoutes = import ./reverse-proxy-routes.nix { inherit lib; };
|
||||
|
||||
collectNixFiles =
|
||||
dir:
|
||||
@@ -35,16 +34,11 @@ let
|
||||
) (builtins.readDir dir)
|
||||
);
|
||||
|
||||
specialArgs =
|
||||
let
|
||||
networkFleetEndpoints = self.lib.networkEndpoints.collectFleetEndpoints self.nixosConfigurations;
|
||||
in
|
||||
{
|
||||
inherit inputs networkFleetEndpoints self;
|
||||
my = self.lib;
|
||||
registry = self.lib.registry.collect { hosts = self.nixosConfigurations; };
|
||||
reverseProxyFleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes self.nixosConfigurations networkFleetEndpoints;
|
||||
};
|
||||
specialArgs = {
|
||||
inherit inputs self;
|
||||
my = self.lib;
|
||||
registry = self.lib.registry.collect { hosts = self.nixosConfigurations; };
|
||||
};
|
||||
|
||||
bridgeName = id: "br-vlan${toString id}";
|
||||
|
||||
@@ -54,7 +48,6 @@ let
|
||||
self
|
||||
specialArgs
|
||||
bridgeName
|
||||
reverseProxyRoutes
|
||||
;
|
||||
};
|
||||
|
||||
@@ -95,7 +88,6 @@ in
|
||||
bridgeName
|
||||
registry
|
||||
networkEndpoints
|
||||
reverseProxyRoutes
|
||||
;
|
||||
|
||||
inherit (guests) deriveMac guest;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
self,
|
||||
specialArgs,
|
||||
bridgeName,
|
||||
reverseProxyRoutes,
|
||||
}:
|
||||
let
|
||||
inherit (lib) escapeShellArg mapAttrsToList;
|
||||
@@ -130,9 +129,19 @@ let
|
||||
Left null, this guest contributes no address to the address registry.
|
||||
'';
|
||||
};
|
||||
reverseProxy = reverseProxyRoutes.mkRouteOptions {
|
||||
subject = "guest route";
|
||||
enableDefault = false;
|
||||
reverseProxy = {
|
||||
enable = lib.mkEnableOption "a reverse-proxy route for this guest";
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Primary hostname served by this guest's reverse-proxy route.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.ints.between 1 65535;
|
||||
example = 3923;
|
||||
description = "Backend TCP port served by this guest.";
|
||||
};
|
||||
};
|
||||
mounts = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
@@ -279,14 +288,15 @@ let
|
||||
|
||||
registry.address.${config.networking.hostName}.${machineName} = lib.mkIf (cfg.address != null) cfg.address;
|
||||
|
||||
modules.reverse-proxy.routes.${machineName} = lib.mkIf cfg.reverseProxy.enable (
|
||||
reverseProxyRoutes.routeFields (
|
||||
cfg.reverseProxy
|
||||
// lib.optionalAttrs (cfg.reverseProxy.backend == null && cfg.reverseProxy.endpoint == null) {
|
||||
endpoint = "${config.networking.hostName}-${machineName}";
|
||||
}
|
||||
)
|
||||
);
|
||||
registry.route.${config.networking.hostName}.${machineName} = lib.mkIf cfg.reverseProxy.enable {
|
||||
inherit (cfg.reverseProxy) host port;
|
||||
aliases = [ ];
|
||||
path = "/";
|
||||
backend = null;
|
||||
scheme = "http";
|
||||
tls = true;
|
||||
websockets = false;
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
|
||||
@@ -32,6 +32,11 @@ in
|
||||
{
|
||||
inherit definitions;
|
||||
|
||||
lookup = registry: hostName: entryName:
|
||||
filterAttrs (_name: value: value != null) (
|
||||
mapAttrs (_name: hostEntriesByName: (hostEntriesByName.${hostName} or { }).${entryName} or null) registry
|
||||
);
|
||||
|
||||
module = {
|
||||
options.registry = mapAttrs (_name: registry: registryOption registry) definitions;
|
||||
};
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
{ lib }:
|
||||
let
|
||||
mkRouteOptions =
|
||||
{
|
||||
name ? null,
|
||||
subject ? "route",
|
||||
enableDefault ? true,
|
||||
}:
|
||||
{
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = enableDefault;
|
||||
description = "Whether this reverse-proxy ${subject} is active.";
|
||||
};
|
||||
|
||||
host = lib.mkOption (
|
||||
{
|
||||
type = lib.types.str;
|
||||
description = "Primary hostname served by this reverse-proxy ${subject}.";
|
||||
}
|
||||
// lib.optionalAttrs (name != null) {
|
||||
default = name;
|
||||
defaultText = lib.literalMD "the route attribute name";
|
||||
}
|
||||
);
|
||||
|
||||
aliases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional hostnames served by this reverse-proxy ${subject}.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/";
|
||||
description = "URL path prefix proxied by this reverse-proxy ${subject}.";
|
||||
};
|
||||
|
||||
backend = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
||||
};
|
||||
|
||||
endpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Fleet network endpoint key used to derive the backend URL.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 1 65535);
|
||||
default = null;
|
||||
example = 3923;
|
||||
description = "Backend TCP port used with the fleet network endpoint.";
|
||||
};
|
||||
|
||||
scheme = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http";
|
||||
description = "Backend URL scheme used with the fleet network endpoint.";
|
||||
};
|
||||
|
||||
tls = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to serve this reverse-proxy ${subject} with ACME-backed TLS.";
|
||||
};
|
||||
|
||||
websockets = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable websocket proxy headers for this reverse-proxy ${subject}.";
|
||||
};
|
||||
};
|
||||
|
||||
routeFields = route: {
|
||||
inherit (route)
|
||||
host
|
||||
aliases
|
||||
path
|
||||
backend
|
||||
endpoint
|
||||
port
|
||||
scheme
|
||||
tls
|
||||
websockets
|
||||
;
|
||||
};
|
||||
|
||||
endpointBackend = endpoints: route:
|
||||
let
|
||||
endpoint = endpoints.${route.endpoint} or (
|
||||
throw "Unknown reverse-proxy endpoint `${route.endpoint}`. Known endpoints: ${lib.concatStringsSep ", " (lib.attrNames endpoints)}"
|
||||
);
|
||||
in
|
||||
"${route.scheme}://${endpoint.address}:${toString route.port}";
|
||||
|
||||
resolvedRouteFields = endpoints: route:
|
||||
let
|
||||
routeWithBackend =
|
||||
if route.backend != null then
|
||||
route
|
||||
else if route.endpoint != null && route.port != null then
|
||||
route // { backend = endpointBackend endpoints route; }
|
||||
else
|
||||
throw "Reverse-proxy route `${route.host}${route.path}` needs either backend or endpoint plus port.";
|
||||
in
|
||||
routeFields routeWithBackend;
|
||||
|
||||
collectFleetRoutes =
|
||||
hosts: endpoints:
|
||||
lib.concatMapAttrs (
|
||||
hostName: host:
|
||||
lib.mapAttrs' (
|
||||
routeName: route: lib.nameValuePair "${hostName}-${routeName}" (resolvedRouteFields endpoints route)
|
||||
) host.config.modules.reverse-proxy.routes
|
||||
) hosts;
|
||||
in
|
||||
{
|
||||
inherit collectFleetRoutes mkRouteOptions resolvedRouteFields routeFields;
|
||||
|
||||
routeType = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = mkRouteOptions { inherit name; };
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,33 @@
|
||||
{ config, lib, my, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
my,
|
||||
registry ? config.registry,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.modules.reverse-proxy;
|
||||
inherit (my) reverseProxyRoutes;
|
||||
|
||||
enabledRoutes = lib.filterAttrs (_: route: route.enable) cfg.routes;
|
||||
routesByHost = lib.groupBy (route: route.host) (lib.attrValues enabledRoutes);
|
||||
routeWithBackend = hostName: routeName: route:
|
||||
let
|
||||
facts = my.registry.lookup registry hostName routeName;
|
||||
in
|
||||
if route.backend != null then
|
||||
route
|
||||
else if facts ? address && route.port != null then
|
||||
route // { backend = "${route.scheme}://${facts.address}:${toString route.port}"; }
|
||||
else
|
||||
route;
|
||||
|
||||
allRoutes = lib.concatLists (
|
||||
lib.mapAttrsToList (
|
||||
hostName: routes:
|
||||
lib.mapAttrsToList (routeName: route: routeWithBackend hostName routeName route) routes
|
||||
) cfg.routes
|
||||
);
|
||||
|
||||
enabledRoutes = builtins.filter (route: route.enable) allRoutes;
|
||||
routesByHost = lib.groupBy (route: route.host) enabledRoutes;
|
||||
|
||||
rootRoutesByHost = lib.mapAttrs (
|
||||
host: routes:
|
||||
@@ -47,9 +70,9 @@ in
|
||||
enable = lib.mkEnableOption "the Nginx reverse proxy route realizer";
|
||||
|
||||
routes = lib.mkOption {
|
||||
type = lib.types.attrsOf reverseProxyRoutes.routeType;
|
||||
type = lib.types.attrsOf (lib.types.attrsOf my.registry.definitions.route.type);
|
||||
default = { };
|
||||
description = "Reverse-proxy route declarations keyed by stable route name.";
|
||||
description = "Reverse-proxy route declarations keyed by source host and stable route name.";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -66,7 +89,7 @@ in
|
||||
++ map (route: {
|
||||
assertion = !hasUnresolvedBackend route;
|
||||
message = "modules.reverse-proxy.routes for ${route.host}${route.path} must resolve to a backend URL before actualization.";
|
||||
}) (builtins.filter hasUnresolvedBackend (lib.attrValues enabledRoutes));
|
||||
}) (builtins.filter hasUnresolvedBackend enabledRoutes);
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
|
||||
65
registry/route.nix
Normal file
65
registry/route.nix
Normal file
@@ -0,0 +1,65 @@
|
||||
{ lib }:
|
||||
{
|
||||
type = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether this reverse-proxy route entry is active.";
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
defaultText = lib.literalMD "the route attribute name";
|
||||
description = "Primary hostname served by this reverse-proxy route entry.";
|
||||
};
|
||||
|
||||
aliases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional hostnames served by this reverse-proxy route entry.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/";
|
||||
description = "URL path prefix proxied by this reverse-proxy route entry.";
|
||||
};
|
||||
|
||||
backend = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.ints.between 1 65535);
|
||||
default = null;
|
||||
example = 3923;
|
||||
description = "Backend TCP port used with the matching address registry entry.";
|
||||
};
|
||||
|
||||
scheme = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http";
|
||||
description = "Backend URL scheme used with the matching address registry entry.";
|
||||
};
|
||||
|
||||
tls = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to serve this reverse-proxy route entry with ACME-backed TLS.";
|
||||
};
|
||||
|
||||
websockets = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable websocket proxy headers for this reverse-proxy route entry.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user