semi-spread example: serve internal vhosts on the VPN addresses only, over IPv4 and IPv6
This commit is contained in:
parent
538da5a13a
commit
7800f92425
9 changed files with 356 additions and 89 deletions
|
|
@ -4,6 +4,7 @@
|
|||
{
|
||||
imports = [
|
||||
./wireguard.nix
|
||||
./gateway.nix
|
||||
./dns.nix
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Names inside the VPN. Every swarm name that is not public resolves to the
|
||||
# VPN address of the machine serving it, through a static table written on
|
||||
# each host. Identical on every host.
|
||||
# Names inside the VPN. Every swarm name, the public ones included, resolves to
|
||||
# the VPN addresses (IPv4 and IPv6) of the machine serving it, through a static
|
||||
# table written on each host. Identical on every host.
|
||||
#
|
||||
# Two readers need the table. Processes on the host read /etc/hosts
|
||||
# (`networking.hosts`). Containers (agents, swarm services) ask the hive's
|
||||
|
|
@ -49,17 +49,20 @@ let
|
|||
};
|
||||
|
||||
elsewhere = removeAttrs servedBy [ self ];
|
||||
|
||||
# Each machine's names, once per address family: A and AAAA answers.
|
||||
addressesOf = machine: [
|
||||
mesh.${machine}.address
|
||||
mesh.${machine}.address6
|
||||
];
|
||||
table = lib.concatMap (
|
||||
machine: map (address: lib.nameValuePair address elsewhere.${machine}) (addressesOf machine)
|
||||
) (builtins.attrNames elsewhere);
|
||||
in
|
||||
{
|
||||
networking.hosts = lib.mapAttrs' (
|
||||
machine: names: lib.nameValuePair mesh.${machine}.address names
|
||||
) elsewhere;
|
||||
networking.hosts = lib.listToAttrs table;
|
||||
|
||||
services.dnsmasq.settings.addn-hosts = "${builtins.toFile "swarm-vpn-hosts" (
|
||||
lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
machine: names: "${mesh.${machine}.address} ${lib.concatStringsSep " " names}"
|
||||
) elsewhere
|
||||
)
|
||||
lib.concatMapStrings (entry: "${entry.name} ${lib.concatStringsSep " " entry.value}\n") table
|
||||
)}";
|
||||
}
|
||||
|
|
|
|||
20
docs/examples/semi-spread/gateway.nix
Normal file
20
docs/examples/semi-spread/gateway.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Which addresses the gateway's private vhosts answer on: this machine's VPN
|
||||
# addresses (IPv4 and IPv6) and the hive bridge. Identical on every host.
|
||||
# Each machine's own file lists its private vhosts (`public = false`); a vhost
|
||||
# that stays `public` also answers on these addresses.
|
||||
#
|
||||
# The bridge is how this machine's own containers (agents, swarm services)
|
||||
# reach a name it serves: the hive's resolver answers it with the bridge
|
||||
# address.
|
||||
{ config, ... }:
|
||||
let
|
||||
mesh = import ./mesh.nix;
|
||||
me = mesh.${config.networking.hostName};
|
||||
in
|
||||
{
|
||||
services.hyperhive.gateway.privateListenAddresses = [
|
||||
me.address
|
||||
me.address6
|
||||
config.services.hyperhive.network.bridgeIp
|
||||
];
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
# hive `<name>`.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
hyperhive = config.services.hyperhive;
|
||||
name = lib.removePrefix "hive-" config.networking.hostName;
|
||||
|
||||
# The leaves for this hive, copied from swarm-central, where the secret store
|
||||
# runs: `client.pem` and `client-key.pem` from
|
||||
# `/var/lib/swarm-bao-pki/hives/<name>/`, along with that directory's
|
||||
|
|
@ -10,19 +13,30 @@ let
|
|||
pki = "/var/lib/swarm-pki";
|
||||
in
|
||||
{
|
||||
services.hyperhive.deploy = {
|
||||
hive-controller = {
|
||||
enable = true;
|
||||
hiveName = lib.removePrefix "hive-" config.networking.hostName;
|
||||
};
|
||||
bao = {
|
||||
clientCertFile = "${pki}/client.pem";
|
||||
clientKeyFile = "${pki}/client-key.pem";
|
||||
services.hyperhive = {
|
||||
deploy = {
|
||||
hive-controller = {
|
||||
enable = true;
|
||||
hiveName = name;
|
||||
};
|
||||
bao = {
|
||||
clientCertFile = "${pki}/client.pem";
|
||||
clientKeyFile = "${pki}/client-key.pem";
|
||||
};
|
||||
};
|
||||
|
||||
# The hive's dashboard and its domain answer on the private addresses only.
|
||||
gateway.vhosts =
|
||||
lib.genAttrs
|
||||
[
|
||||
hyperhive.swarm.dashboardDomains.${name}
|
||||
hyperhive.swarm.hives.${name}.domain
|
||||
]
|
||||
(_: {
|
||||
public = false;
|
||||
});
|
||||
};
|
||||
|
||||
# The dashboard is reachable over the VPN only.
|
||||
networking.firewall.interfaces.wg0.allowedTCPPorts = [
|
||||
config.services.hyperhive.gateway.httpsPort
|
||||
];
|
||||
networking.firewall.interfaces.wg0.allowedTCPPorts = [ hyperhive.gateway.httpsPort ];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,40 @@
|
|||
# The VPN's facts: one entry per machine, read by every host (wireguard.nix,
|
||||
# dns.nix) and nothing else. Keyed by `networking.hostName`.
|
||||
# gateway.nix, dns.nix) and nothing else. Keyed by `networking.hostName`.
|
||||
#
|
||||
# `address` is the machine's address inside the VPN; `endpoint` is where the
|
||||
# other machines dial it; `publicKey` is the output of `wg pubkey` on that
|
||||
# machine. The keys below are placeholders: replace each with the real one.
|
||||
# `address` and `address6` are the machine's addresses inside the VPN (IPv4
|
||||
# 10.77.0.0/24 and the unique-local IPv6 prefix fd77:77a1:5e::/64);
|
||||
# `endpoint` is where the other machines dial it; `publicKey` is the output of
|
||||
# `wg pubkey` on that machine. The keys below are placeholders: replace each
|
||||
# with the real one. Generate your own IPv6 prefix (RFC 4193) rather than
|
||||
# keeping this one.
|
||||
{
|
||||
swarm-central = {
|
||||
address = "10.77.0.1";
|
||||
address6 = "fd77:77a1:5e::1";
|
||||
endpoint = "203.0.113.10:51820";
|
||||
publicKey = "REPLACE-with-swarm-central-wg-pubkey";
|
||||
};
|
||||
swarm-services = {
|
||||
address = "10.77.0.2";
|
||||
address6 = "fd77:77a1:5e::2";
|
||||
endpoint = "203.0.113.11:51820";
|
||||
publicKey = "REPLACE-with-swarm-services-wg-pubkey";
|
||||
};
|
||||
swarm-heavyduty = {
|
||||
address = "10.77.0.3";
|
||||
address6 = "fd77:77a1:5e::3";
|
||||
endpoint = "203.0.113.12:51820";
|
||||
publicKey = "REPLACE-with-swarm-heavyduty-wg-pubkey";
|
||||
};
|
||||
hive-one = {
|
||||
address = "10.77.0.11";
|
||||
address6 = "fd77:77a1:5e::11";
|
||||
endpoint = "198.51.100.11:51820";
|
||||
publicKey = "REPLACE-with-hive-one-wg-pubkey";
|
||||
};
|
||||
hive-two = {
|
||||
address = "10.77.0.12";
|
||||
address6 = "fd77:77a1:5e::12";
|
||||
endpoint = "198.51.100.12:51820";
|
||||
publicKey = "REPLACE-with-hive-two-wg-pubkey";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# swarm-central: the swarm controller (and the swarm UI it serves) and the
|
||||
# secret store.
|
||||
{ config, ... }:
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
mesh = import ./mesh.nix;
|
||||
hyperhive = config.services.hyperhive;
|
||||
|
|
@ -14,8 +14,13 @@ in
|
|||
# and hives on other machines.
|
||||
deploy.bao.extraListenAddresses = [ mesh.swarm-central.address ];
|
||||
|
||||
# The swarm UI is public.
|
||||
# The gateway's ports are open to everyone. The swarm UI is the one vhost
|
||||
# that answers there; the store's browser UI answers on the private
|
||||
# addresses only.
|
||||
gateway.openFirewall = true;
|
||||
gateway.vhosts = lib.genAttrs [ hyperhive.swarm.bao.ui.domain ] (_: {
|
||||
public = false;
|
||||
});
|
||||
};
|
||||
|
||||
# The store is reachable over the VPN only.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ in
|
|||
deploy.ci-runner.enable = true;
|
||||
deploy.attic.enable = true;
|
||||
|
||||
# The cache answers on the private addresses only.
|
||||
gateway.vhosts = lib.genAttrs [ hyperhive.swarm.attic.ownDomain ] (_: {
|
||||
public = false;
|
||||
});
|
||||
|
||||
deploy.bao = lib.mergeAttrsList [
|
||||
(pair "builder" "builder")
|
||||
(pair "ciRunner" "ci-runner")
|
||||
|
|
|
|||
|
|
@ -19,9 +19,22 @@ in
|
|||
# Everything once-per-swarm that is not placed elsewhere below.
|
||||
deploy.allSwarmServices = true;
|
||||
|
||||
# The forge and the matrix homeserver are public, and so is the SSO login
|
||||
# they send browsers to.
|
||||
# The gateway's ports are open to everyone. The forge and the matrix
|
||||
# homeserver answer there, and so does the SSO login they send browsers
|
||||
# to. Every other vhost here answers on the private addresses only.
|
||||
gateway.openFirewall = true;
|
||||
gateway.vhosts =
|
||||
lib.genAttrs
|
||||
(with hyperhive.swarm; [
|
||||
nats.domain
|
||||
otel.domain
|
||||
grafana.domain
|
||||
victoriametrics.domain
|
||||
victorialogs.domain
|
||||
])
|
||||
(_: {
|
||||
public = false;
|
||||
});
|
||||
|
||||
deploy.bao = lib.mergeAttrsList [
|
||||
{ enable = false; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# The VPN: plain NixOS WireGuard, one interface `wg0`, a full mesh built from
|
||||
# ./mesh.nix. Identical on every host; the host's own entry is picked by
|
||||
# `networking.hostName`.
|
||||
# ./mesh.nix, carrying IPv4 and IPv6. Identical on every host; the host's own
|
||||
# entry is picked by `networking.hostName`.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
mesh = import ./mesh.nix;
|
||||
|
|
@ -8,13 +8,19 @@ let
|
|||
in
|
||||
{
|
||||
networking.wireguard.interfaces.wg0 = {
|
||||
ips = [ "${mesh.${self}.address}/24" ];
|
||||
ips = [
|
||||
"${mesh.${self}.address}/24"
|
||||
"${mesh.${self}.address6}/64"
|
||||
];
|
||||
listenPort = 51820;
|
||||
# Created on the host by `wg genkey`, never in the store.
|
||||
privateKeyFile = "/etc/wireguard/wg0.key";
|
||||
peers = lib.mapAttrsToList (_: peer: {
|
||||
inherit (peer) publicKey endpoint;
|
||||
allowedIPs = [ "${peer.address}/32" ];
|
||||
allowedIPs = [
|
||||
"${peer.address}/32"
|
||||
"${peer.address6}/128"
|
||||
];
|
||||
persistentKeepalive = 25;
|
||||
}) (removeAttrs mesh [ self ]);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue