Add wireguard-nat-nftables python script
This commit is contained in:
parent
667b1c256b
commit
299d04142f
|
@ -4,5 +4,6 @@
|
|||
./configuration.nix
|
||||
./nginx.nix
|
||||
./containers/uptime-kuma
|
||||
./services.nix
|
||||
];
|
||||
}
|
||||
|
|
30
config/hosts/valkyrie/services.nix
Normal file
30
config/hosts/valkyrie/services.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
wireguard-nat-nftables = import ../../../pkgs/wireguard-nat-nftables pkgs;
|
||||
config = pkgs.writeText "wireguard-nat-nftables-config" (builtins.toJSON {
|
||||
interface = "ens3";
|
||||
wg_interface = "wg0";
|
||||
pubkey_port_mapping = {
|
||||
"SJ8xCRb4hWm5EnXoV4FnwgbiaxmY2wI+xzfk+3HXERg=" = [ 51827 51829 ];
|
||||
"BbNeBTe6HwQuHPK+ZQXWYRZJJMPdS0h81n07omYyRl4=" = [ 51828 51830 ];
|
||||
"u9h+D8XZ62ABnetBRKnf6tjs+tJwM8fQ4d6ipOCLFyE=" = [ 51821 51824 ];
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
systemd.services.wireguard-nat-nftables = {
|
||||
description = "A python script to update nftable dnat rules based on WireGuard peer IPs";
|
||||
requires = [ "wireguard-wg0.service" ];
|
||||
after = [ "wireguard-wg0.service" ];
|
||||
|
||||
script = ''
|
||||
${wireguard-nat-nftables}/bin/wireguard-nat-nftables.py ${config}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "root";
|
||||
Group = "root";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -9,7 +9,8 @@
|
|||
simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-23.05";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-generators, simple-nixos-mailserver, ... }@inputs: let
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, nixos-generators, simple-nixos-mailserver, ... }@inputs:
|
||||
let
|
||||
hosts = import ./hosts.nix inputs;
|
||||
helper = import ./helper.nix inputs;
|
||||
in {
|
||||
|
@ -32,9 +33,9 @@
|
|||
} // builtins.mapAttrs (helper.generateColmenaHost) hosts;
|
||||
|
||||
hydraJobs = {
|
||||
nixConfigurations = builtins.mapAttrs (
|
||||
host: helper.generateNixConfiguration host { inherit nixpkgs-unstable hosts simple-nixos-mailserver; }
|
||||
) hosts;
|
||||
nixConfigurations = builtins.mapAttrs (host: helper.generateNixConfiguration host {
|
||||
inherit nixpkgs-unstable hosts simple-nixos-mailserver;
|
||||
}) hosts;
|
||||
};
|
||||
|
||||
# Generate a base VM image for Proxmox with `nix build .#base-proxmox`
|
||||
|
|
17
pkgs/wireguard-nat-nftables/default.nix
Normal file
17
pkgs/wireguard-nat-nftables/default.nix
Normal file
|
@ -0,0 +1,17 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
nftablesWithPythonOverlay = final: prev: {
|
||||
nftables = (prev.nftables.override { withPython = true; });
|
||||
};
|
||||
pkgs-overlay = pkgs.extend nftablesWithPythonOverlay;
|
||||
in
|
||||
pkgs-overlay.python310Packages.buildPythonApplication {
|
||||
pname = "wireguard-nat-nftables";
|
||||
version = "0.0.1";
|
||||
|
||||
propagatedBuildInputs = with pkgs-overlay; [
|
||||
python310Packages.nftables
|
||||
];
|
||||
|
||||
src = ./src;
|
||||
}
|
7
pkgs/wireguard-nat-nftables/src/setup.py
Normal file
7
pkgs/wireguard-nat-nftables/src/setup.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name='wireguard-nat-nftables',
|
||||
version='0.0.1',
|
||||
scripts=['wireguard-nat-nftables.py']
|
||||
)
|
92
pkgs/wireguard-nat-nftables/src/wireguard-nat-nftables.py
Normal file
92
pkgs/wireguard-nat-nftables/src/wireguard-nat-nftables.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import nftables
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
|
||||
def main():
|
||||
f = open(sys.argv[1], "r")
|
||||
config = json.loads(f.read())
|
||||
f.close()
|
||||
|
||||
interface = config["interface"]
|
||||
wg_interface = config["wg_interface"]
|
||||
pubkey_port_mapping = config["pubkey_port_mapping"]
|
||||
|
||||
nft = nftables.Nftables()
|
||||
nft.set_json_output(True)
|
||||
nft.set_handle_output(True)
|
||||
|
||||
# add nat table rules for dnat and snat masquerade
|
||||
nft.cmd("add table nat")
|
||||
nft.cmd("add chain nat prerouting { type nat hook prerouting priority -100; }")
|
||||
nft.cmd("add chain nat postrouting { type nat hook postrouting priority 100; }")
|
||||
|
||||
# load current nftables rules
|
||||
rc, output, error = nft.cmd("list ruleset")
|
||||
if error:
|
||||
print(error, file=sys.stderr)
|
||||
nftables_output = json.loads(output)
|
||||
|
||||
add_masquerade = True
|
||||
for item in nftables_output["nftables"]:
|
||||
if ("rule" in item
|
||||
and item["rule"]["family"] == "ip"
|
||||
and item["rule"]["table"] == "nat"
|
||||
and item["rule"]["chain"] == "postrouting"
|
||||
and "masquerade" in item["rule"]["expr"][0]
|
||||
):
|
||||
add_masquerade = False
|
||||
break
|
||||
if add_masquerade:
|
||||
nft.cmd("add rule nat postrouting masquerade")
|
||||
|
||||
while True:
|
||||
# list WireGuard peer endpoint addresses of WireGuard VPN connection
|
||||
process = subprocess.Popen(["wg", "show", wg_interface, "endpoints"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = process.communicate()
|
||||
lines = stdout.decode().split("\n")[:-1]
|
||||
if stderr:
|
||||
print("{}: {}".format(wg_interface, stderr.decode()), file=sys.stderr)
|
||||
else:
|
||||
# map destination port to IP
|
||||
port_ip_mapping = {}
|
||||
for line in lines:
|
||||
pubkey = line.split("\t")[0]
|
||||
ip = line.split("\t")[1].split(":")[0] # probably only works for IPv4
|
||||
for port in pubkey_port_mapping[pubkey]:
|
||||
port_ip_mapping[port] = ip
|
||||
|
||||
# load current nftables rules
|
||||
rc, output, error = nft.cmd("list ruleset")
|
||||
if error:
|
||||
print(error, file=sys.stderr)
|
||||
nftables_output = json.loads(output)
|
||||
|
||||
# update existing nftable dnat rules, if the remote IP mismatches
|
||||
for item in nftables_output["nftables"]:
|
||||
if "rule" in item and item["rule"]["family"] == "ip" and item["rule"]["table"] == "nat" and item["rule"]["chain"] == "prerouting":
|
||||
handle = item["rule"]["handle"]
|
||||
ip = item["rule"]["expr"][2]["dnat"]["addr"]
|
||||
port = item["rule"]["expr"][1]["match"]["right"]
|
||||
if not ip == port_ip_mapping[port]:
|
||||
rc, output, error = nft.cmd("replace rule nat prerouting handle {} iif {} udp dport {} dnat to {}".format(handle, interface, port, port_ip_mapping[port]))
|
||||
if error:
|
||||
eprint(error)
|
||||
else:
|
||||
print("Changed dnat address from {} to {} for UDP port {}".format(ip, port_ip_mapping[port], port))
|
||||
port_ip_mapping.pop(port)
|
||||
|
||||
# loop through all remaining ports and add needed dnat rules
|
||||
for port in port_ip_mapping:
|
||||
rc, output, error = nft.cmd("add rule nat prerouting iif {} udp dport {} dnat to {}".format(interface, port, port_ip_mapping[port]))
|
||||
if error:
|
||||
print(error, file=sys.stderr)
|
||||
else:
|
||||
print("Added dnat rule from UDP port {} to address {}".format(port, port_ip_mapping[port]))
|
||||
time.sleep(10)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue