diff options
| author | Mel <mel@rnrd.eu> | 2026-04-24 03:10:11 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-04-24 03:18:05 +0200 |
| commit | fa38ea010957a98e778c32b23a8f133b14afdef1 (patch) | |
| tree | 2548538141908ceafa25c5f8ac7371d054b7bd87 /modules/vpn | |
| parent | 97e935e0ff718cbec86605bf584a5660812bdce9 (diff) | |
| download | network-fa38ea010957a98e778c32b23a8f133b14afdef1.tar.zst network-fa38ea010957a98e778c32b23a8f133b14afdef1.zip | |
Give the VPN its final name 'Tunnel'
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'modules/vpn')
| -rw-r--r-- | modules/vpn/definition.nix | 75 | ||||
| -rw-r--r-- | modules/vpn/egress.nix | 133 | ||||
| -rw-r--r-- | modules/vpn/ingress.nix | 201 |
3 files changed, 0 insertions, 409 deletions
diff --git a/modules/vpn/definition.nix b/modules/vpn/definition.nix deleted file mode 100644 index 60ea5d0..0000000 --- a/modules/vpn/definition.nix +++ /dev/null @@ -1,75 +0,0 @@ -# definition of the network layout which supports our vpn -# architecture. - -{ - # these are the available paths which a user is allowed to take - # to reach a specified egress server. - # when a user connects to a port defined here via wireguard, - # the primary ingress server (us), will establish a connection with - # the user and the backend egress server (this time, not via wireguard, - # but with a specific dpi-evading protocol), and route the users packets - # through to the egress. - paths = [ - { - port = 50501; - egress = "taupe"; - - info = { - uuid = "328c90a0-20ae-4d4c-9e54-97e9ab41c053"; - short = "b20629b505f39194"; - - public = "_837k5niQBE-qmgqpZalH3cS_fAIBwv8dwMoDW1uvgk"; - keySecret = ../../secrets/vpn/egress-key-taupe.age; - }; - } - { - port = 50502; - egress = "taureau"; - - info = { - uuid = "826b8598-ed75-4782-9b7e-27e0e16e1141"; - short = "8f7e9f8a3fa46bf0"; - - public = "HvR4iP8URERpPBM4oG1Bjfw3mIfN0MoL2x6MHlt_TUM"; - keySecret = ../../secrets/vpn/egress-key-taureau.age; - }; - } - ]; - - # there are our users who are allowed to connect to any of our "paths". - # their ip is always a template, with 'X' representing the path index. - users = { - mel = { - key = "vnZoHXapCLLUhZ8A8R5W0iJ8LpWVLve29z41kkoT0BU="; - ip = "10.123.X.101"; - }; - - andrei = { - key = "qqU4uYImLfUohIwl4KBshPtTINFcs0JVALjbmwpfxRg="; - ip = "10.123.X.102"; - }; - - sergo = { - key = "qbZGMNIDZFCJC6SHtlyNIlIdGWHELceXClJCcagrj2Y="; - ip = "10.123.X.103"; - }; - - fedor = { - key = "tEO9r8+jTpu8TBRmZ+/v087IgD/QfmofLUKs249i/F0="; - ip = "10.123.X.104"; - }; - }; - - # we use a website as a "mask" for vless/reality, which will tell our peers - # to pretend as if they're a user and a well-known website communicating with - # each other, even though they know that the keys don't actually match up, - # it's not possible to see that on the outside. - mask = "microsoft.com"; - - # we don't actually need this to configure the tunnel, but this is - # the public key of the ingress interface. - # when creating wireguard vpn configurations for the users, this - # is the public key of the server peer at `tunnel.rnrd.eu`. - # the matching private key of the pair is the secret `vpn/ingress-key`. - ingress.public = "s5yyPCJiN0uqW0jzKIbYCF7I9TthymiRzpNt466XeWk="; -} diff --git a/modules/vpn/egress.nix b/modules/vpn/egress.nix deleted file mode 100644 index 7858751..0000000 --- a/modules/vpn/egress.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ - me, - config, - pkgs, - lib, - ... -}: - -let - inherit (lib) findFirst; - - # this is the https port, we use it to try to trick dpi into thinking - # we are just serving normal encrypted web traffic, nothing interesting! :) - # this does mean that our egress servers are unable to support normal www - # services which we put on machines by default, which is okay. - port = 443; - - # supposedly the current gold-standard protocol for circumventing dpi! - # both xray (egress-side) and sing-box (ingress-side) support various - # other protocols, if roskomnadzor learns to sniff out vless fully. - protocol = "vless"; - - inboundTag = "vless-in"; - outboundTag = "direct-out"; - - definition = import ./definition.nix; - inherit (definition) paths mask; - - path = findFirst ( - p: p.egress == me.name - ) (throw "no egress information found for this server!") paths; - - xrayConfig = { - inbounds = [ - { - inherit port protocol; - tag = inboundTag; - - settings = { - clients = [ - { - id = path.info.uuid; - flow = "xtls-rprx-vision"; - } - ]; - decryption = "none"; - }; - - streamSettings = { - network = "tcp"; - security = "reality"; - realitySettings = { - show = false; - dest = "www.${mask}:443"; - serverNames = [ - "www.${mask}" - mask - ]; - privateKey = "@PRIVATE_KEY@"; - shortIds = [ path.info.short ]; - }; - }; - } - ]; - - # and we're out! - outbounds = [ - { - protocol = "freedom"; - tag = outboundTag; - } - ]; - - routing = { - rules = [ - { - type = "field"; - inboundTag = [ inboundTag ]; - inherit outboundTag; - } - ]; - }; - - log = { - loglevel = "debug"; - }; - }; - - config-file = pkgs.writeText "xray.json" (builtins.toJSON xrayConfig); -in -{ - networking.firewall.allowedTCPPorts = [ port ]; - - age.secrets.egress-key = { - file = path.info.keySecret; - }; - - systemd.services = { - # we have to make an xray config on the fly because - # xray does not like reading secrets from specific files, - # it wants them in plain-text! - generate-xray-config = { - description = "Generate Xray configuration"; - wantedBy = [ "multi-user.target" ]; - before = [ "xray.service" ]; - partOf = [ "xray.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = '' - mkdir -p /run/xray-configuration - cp ${config-file} /run/xray-configuration/xray.json - - egress_key=$(cat ${config.age.secrets.egress-key.path}) - - # use sd for replacement as a fancy new tool for this - ${pkgs.sd}/bin/sd "@PRIVATE_KEY@" "$egress_key" /run/xray-configuration/xray.json - ''; - }; - - xray = { - requires = [ "generate-xray-config.service" ]; - after = [ "generate-xray-config.service" ]; - restartTriggers = [ config-file ]; - }; - }; - - services.xray = { - enable = true; - settingsFile = "/run/xray-configuration/xray.json"; - }; -} diff --git a/modules/vpn/ingress.nix b/modules/vpn/ingress.nix deleted file mode 100644 index 6c6a78e..0000000 --- a/modules/vpn/ingress.nix +++ /dev/null @@ -1,201 +0,0 @@ -{ - config, - lib, - ... -}: - -let - inherit (lib) - imap0 - attrValues - mergeAttrsList - replaceString - concatImapStringsSep - ; - - definition = import ./definition.nix; - - inherit (definition) paths users mask; - - ownAddress = "10.123.X.1"; # ip of host running the ingress vpn (per-interface) - - addressFromTemplate = - index: template: prefix: - "${replaceString "X" (toString (index + 1)) template}/${toString prefix}"; - - ingressName = index: "vpn-ingress${toString index}"; - egressName = "vpn-egress0"; - egressAddress = "10.123.255.1/16"; # /16 encompasses all possible subnet addresses - egressMTU = 1400; - - egressHost = name: "${name}.rnrd.eu"; -in -{ - boot.kernel.sysctl = { - "net.ipv4.ip_forward" = 1; # allow ipv4 forwarding - }; - - networking.firewall = { - allowedUDPPorts = map (x: x.port) paths; - allowedTCPPorts = map (x: x.port) paths; - checkReversePath = "loose"; - }; - - age.secrets.ingress-key = { - file = ../../secrets/vpn/ingress-key.age; - owner = "systemd-network"; - }; - - systemd.network = - let - mkNetdev = index: path: { - "10-${ingressName index}" = { - netdevConfig = { - Kind = "wireguard"; - Name = ingressName index; - }; - wireguardConfig = { - PrivateKeyFile = config.age.secrets.ingress-key.path; - ListenPort = path.port; - }; - wireguardPeers = map (user: { - PublicKey = user.key; - AllowedIPs = [ (addressFromTemplate index user.ip 32) ]; - }) (attrValues users); - }; - }; - - mkNetwork = index: path: { - "10-${ingressName index}" = { - name = ingressName index; - address = [ (addressFromTemplate index ownAddress 24) ]; - routingPolicyRules = [ - { - IncomingInterface = ingressName index; - Table = 100; - } - ]; - }; - }; - - ingressNetdevs = imap0 mkNetdev paths; - - ingressNetworks = imap0 mkNetwork paths; - egressNetworks = [ - { - "20-${egressName}" = { - name = egressName; - address = [ egressAddress ]; - networkConfig = { - IPv4ReversePathFilter = "loose"; - }; - linkConfig = { - ActivationPolicy = "up"; - RequiredForOnline = "no"; # does not count as online - MTUBytes = toString egressMTU; - }; - routes = [ - { - Destination = "0.0.0.0/0"; - Table = 100; - Scope = "link"; - } - ]; - }; - } - ]; - in - { - netdevs = mergeAttrsList ingressNetdevs; - networks = mergeAttrsList (ingressNetworks ++ egressNetworks); - }; - - # allow forwarding packets between egress and ingress, but avoid any snat, - # ip should always keep it's origin form, for correct egress routing. - # also adapt mss to outgoing mss value, so that we don't shatter packets. - networking.nftables.ruleset = - let - ingressInterfaces = concatImapStringsSep "\", \"" (i: _: ingressName (i - 1)) paths; - in - '' - table inet filter { - chain forward { - type filter hook forward priority 0; policy drop; - - tcp flags syn tcp option maxseg size set rt mtu - - iifname { "${ingressInterfaces}" } oifname "${egressName}" accept - iifname "${egressName}" oifname { "${ingressInterfaces}" } accept - } - } - ''; - - # sing-box is a vpn client supporting various protocols which will allow us - # to configure it in whichever way we want to avoid russian dpi. - # in this case, our communications crossing the borders are relying on vless. - services.sing-box = - let - inboundName = "vpn-in"; - outboundName = egress: "vpn-out-${egress}"; - in - { - enable = true; - settings = { - inbounds = [ - { - type = "tun"; - tag = inboundName; - interface_name = egressName; - address = [ egressAddress ]; - mtu = egressMTU; - stack = "gvisor"; - auto_route = false; # we route manually - strict_route = false; - endpoint_independent_nat = true; - } - ]; - - outbounds = map (path: { - type = "vless"; - flow = "xtls-rprx-vision"; - - server = egressHost path.egress; - server_port = 443; - - tag = outboundName path.egress; - uuid = path.info.uuid; - - tls = { - enabled = true; - server_name = "www.${mask}"; - - utls = { - enabled = true; - fingerprint = "chrome"; - }; - - reality = { - enabled = true; - public_key = path.info.public; - short_id = path.info.short; - }; - }; - }) paths; - - route = { - rules = imap0 (index: path: { - inbound = inboundName; - source_ip_cidr = [ (addressFromTemplate index "10.123.X.0" 24) ]; - outbound = outboundName path.egress; - }) paths; - - auto_detect_interface = true; - }; - - log = { - level = "debug"; - timestamp = true; - }; - }; - }; -} |
