Tidy up code and add logging
This commit is contained in:
parent
4c75251c20
commit
c6eea6a9fe
|
@ -470,9 +470,6 @@ class WireguardConfig:
|
||||||
conf.append(f"PrivateKey = {self.private_key}")
|
conf.append(f"PrivateKey = {self.private_key}")
|
||||||
if self.listen_port is not None:
|
if self.listen_port is not None:
|
||||||
conf.append(f"ListenPort = {self.listen_port}")
|
conf.append(f"ListenPort = {self.listen_port}")
|
||||||
if self.table is not None:
|
|
||||||
val = "on" if self.table else "off"
|
|
||||||
conf.append(f"Table = {val}")
|
|
||||||
if self.fwmark is not None:
|
if self.fwmark is not None:
|
||||||
conf.append(f"FwMark = {self.fwmark}")
|
conf.append(f"FwMark = {self.fwmark}")
|
||||||
if wgquick_format:
|
if wgquick_format:
|
||||||
|
@ -481,6 +478,9 @@ class WireguardConfig:
|
||||||
conf.extend([f"Address = {addr}" for addr in self.addresses])
|
conf.extend([f"Address = {addr}" for addr in self.addresses])
|
||||||
conf.extend([f"DNS = {addr}" for addr in self.dns_servers])
|
conf.extend([f"DNS = {addr}" for addr in self.dns_servers])
|
||||||
conf.extend([f"DNS = {domain}" for domain in self.search_domains])
|
conf.extend([f"DNS = {domain}" for domain in self.search_domains])
|
||||||
|
if self.table is not None:
|
||||||
|
val = "auto" if self.table else "off"
|
||||||
|
conf.append(f"Table = {val}")
|
||||||
|
|
||||||
conf.extend([f"PreUp = {cmd}" for cmd in self.preup])
|
conf.extend([f"PreUp = {cmd}" for cmd in self.preup])
|
||||||
conf.extend([f"PostUp = {cmd}" for cmd in self.postup])
|
conf.extend([f"PostUp = {cmd}" for cmd in self.postup])
|
||||||
|
|
87
woven.py
87
woven.py
|
@ -3,9 +3,11 @@
|
||||||
from fabric import Connection, Config
|
from fabric import Connection, Config
|
||||||
from invoke.exceptions import UnexpectedExit
|
from invoke.exceptions import UnexpectedExit
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sys import stderr
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from json import loads
|
from json import loads
|
||||||
|
from os import devnull
|
||||||
|
from sys import stdout, stderr, exit
|
||||||
|
from contextlib import redirect_stdout
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclass_wizard import fromdict
|
from dataclass_wizard import fromdict
|
||||||
|
@ -13,10 +15,6 @@ from ipaddress import IPv4Interface, IPv4Network, IPv6Interface, IPv6Network, Ad
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
from wireguard_tools import WireguardConfig, WireguardPeer, WireguardKey
|
from wireguard_tools import WireguardConfig, WireguardPeer, WireguardKey
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class WovenArgs:
|
|
||||||
config: str
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WovenNode:
|
class WovenNode:
|
||||||
listen_address: str
|
listen_address: str
|
||||||
|
@ -35,6 +33,16 @@ class WovenConfig:
|
||||||
ptp_ipv4_subnet: int = 30
|
ptp_ipv4_subnet: int = 30
|
||||||
ptp_ipv6_subnet: int = 64
|
ptp_ipv6_subnet: int = 64
|
||||||
|
|
||||||
|
ssh_config = Config(overrides = { "run": { "hide": True } })
|
||||||
|
id_suffix = "loop"
|
||||||
|
wg_dir = "/etc/wireguard"
|
||||||
|
wg_config_ext = ".conf"
|
||||||
|
wg_config_glob = f"{wg_dir}/*-{id_suffix}{wg_config_ext}"
|
||||||
|
|
||||||
|
table = False
|
||||||
|
allowed_ips = ["0.0.0.0/0", "::/0"]
|
||||||
|
persistent_keepalive = 20
|
||||||
|
|
||||||
def generate_wg_configs(config: WovenConfig):
|
def generate_wg_configs(config: WovenConfig):
|
||||||
try:
|
try:
|
||||||
ptp_ipv4_network = IPv4Network(config.ptp_ipv4_network)
|
ptp_ipv4_network = IPv4Network(config.ptp_ipv4_network)
|
||||||
|
@ -49,19 +57,26 @@ def generate_wg_configs(config: WovenConfig):
|
||||||
except NetmaskValueError:
|
except NetmaskValueError:
|
||||||
raise ValueError("invalid IPv6 PtP network subnet")
|
raise ValueError("invalid IPv6 PtP network subnet")
|
||||||
|
|
||||||
cs = { id: Connection(node.listen_address, user = "root", config = Config(overrides = { "run": { "hide": True } })) for id, node in config.nodes.items() }
|
|
||||||
for c in cs.values():
|
|
||||||
c.run("for f in /etc/wireguard/*-loop.conf; do systemctl stop wg-quick@$(basename $f .conf).service; done")
|
|
||||||
try:
|
|
||||||
c.run("rm /etc/wireguard/*-loop.conf")
|
|
||||||
except UnexpectedExit:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ptp_ipv4_network_iter = ptp_ipv4_network.subnets(new_prefix = config.ptp_ipv4_subnet)
|
ptp_ipv4_network_iter = ptp_ipv4_network.subnets(new_prefix = config.ptp_ipv4_subnet)
|
||||||
ptp_ipv6_network_iter = ptp_ipv6_network.subnets(new_prefix = config.ptp_ipv6_subnet)
|
ptp_ipv6_network_iter = ptp_ipv6_network.subnets(new_prefix = config.ptp_ipv6_subnet)
|
||||||
port_iter = iter(range(config.min_port, config.max_port))
|
port_iter = iter(range(config.min_port, config.max_port))
|
||||||
|
|
||||||
|
cs = { id: Connection(node.listen_address, user = "root", config = ssh_config) for id, node in config.nodes.items() }
|
||||||
|
|
||||||
|
for id, c in cs.items():
|
||||||
|
print(f"stopping services for {id}...", end = " ", flush = True)
|
||||||
|
c.run(f"for f in {wg_config_glob}; do systemctl stop wg-quick@$(basename $f {wg_config_ext}).service; done")
|
||||||
|
print("done")
|
||||||
|
print(f"removing existing configs for {id}...", end = " ", flush = True)
|
||||||
|
try:
|
||||||
|
c.run(f"rm {wg_config_glob}")
|
||||||
|
except UnexpectedExit:
|
||||||
|
pass
|
||||||
|
print("done")
|
||||||
|
|
||||||
for (id_a, node_a), (id_b, node_b) in combinations(config.nodes.items(), 2):
|
for (id_a, node_a), (id_b, node_b) in combinations(config.nodes.items(), 2):
|
||||||
|
print(f"creating configs for {id_a} <-> {id_b} tunnel...", end = " ", flush = True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ptp_ipv4_network = next(ptp_ipv4_network_iter)
|
ptp_ipv4_network = next(ptp_ipv4_network_iter)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
@ -95,19 +110,18 @@ def generate_wg_configs(config: WovenConfig):
|
||||||
key_b = WireguardKey.generate()
|
key_b = WireguardKey.generate()
|
||||||
key_b_pub = key_b.public_key()
|
key_b_pub = key_b.public_key()
|
||||||
|
|
||||||
name_a = f"{id_a}-{id_b}-loop"
|
name_a = f"{id_a}-{id_b}-{id_suffix}"
|
||||||
name_b = f"{id_b}-{id_a}-loop"
|
addresses_a = [IPv4Interface(f"{ipv4_a}/{ptp_ipv4_network.prefixlen}"), IPv6Interface(f"{ipv6_a}/{ptp_ipv6_network.prefixlen}")]
|
||||||
|
|
||||||
preup_a = [f"ip ro replace {node_b.listen_address}/32 dev {node_a.interface_name} via {node_a.listen_gateway} metric 10 src {node_a.listen_address}"]
|
preup_a = [f"ip ro replace {node_b.listen_address}/32 dev {node_a.interface_name} via {node_a.listen_gateway} metric 10 src {node_a.listen_address}"]
|
||||||
predown_a = [f"ip ro del {node_b.listen_address}/32 dev {node_a.interface_name} via {node_a.listen_gateway} metric 10 src {node_a.listen_address}"]
|
predown_a = [f"ip ro del {node_b.listen_address}/32 dev {node_a.interface_name} via {node_a.listen_gateway} metric 10 src {node_a.listen_address}"]
|
||||||
postup_a = [f"ip ro replace {sn} dev {name_a} via {ipv4_b} metric 10" for sn in node_b.routed_ipv4_subnets] + [f"ip -6 ro replace {sn} dev {name_a} via {ipv6_b} metric 10" for sn in node_b.routed_ipv6_subnets]
|
postup_a = [f"ip ro replace {sn} dev {name_a} via {ipv4_b} metric 10" for sn in node_b.routed_ipv4_subnets] + [f"ip -6 ro replace {sn} dev {name_a} via {ipv6_b} metric 10" for sn in node_b.routed_ipv6_subnets]
|
||||||
postdown_a = [f"ip ro del {sn} dev {name_a} via {ipv4_b} metric 10" for sn in node_b.routed_ipv4_subnets] + [f"ip -6 ro del {sn} dev {name_a} via {ipv6_b} metric 10" for sn in node_b.routed_ipv6_subnets]
|
postdown_a = [f"ip ro del {sn} dev {name_a} via {ipv4_b} metric 10" for sn in node_b.routed_ipv4_subnets] + [f"ip -6 ro del {sn} dev {name_a} via {ipv6_b} metric 10" for sn in node_b.routed_ipv6_subnets]
|
||||||
|
|
||||||
config_a = WireguardConfig(
|
config_a = WireguardConfig(
|
||||||
addresses = [IPv4Interface(f"{ipv4_a}/{ptp_ipv4_network.prefixlen}"), IPv6Interface(f"{ipv6_a}/{ptp_ipv6_network.prefixlen}")],
|
addresses = addresses_a,
|
||||||
listen_port = port,
|
listen_port = port,
|
||||||
private_key = key_a,
|
private_key = key_a,
|
||||||
table = False,
|
table = table,
|
||||||
preup = preup_a,
|
preup = preup_a,
|
||||||
predown = predown_a,
|
predown = predown_a,
|
||||||
postup = postup_a,
|
postup = postup_a,
|
||||||
|
@ -115,24 +129,26 @@ def generate_wg_configs(config: WovenConfig):
|
||||||
peers = {
|
peers = {
|
||||||
key_b_pub: WireguardPeer(
|
key_b_pub: WireguardPeer(
|
||||||
public_key = key_b_pub,
|
public_key = key_b_pub,
|
||||||
allowed_ips = ["0.0.0.0/0", "::/0"],
|
allowed_ips = allowed_ips,
|
||||||
endpoint_host = node_b.listen_address,
|
endpoint_host = node_b.listen_address,
|
||||||
endpoint_port = port,
|
endpoint_port = port,
|
||||||
persistent_keepalive = 20
|
persistent_keepalive = persistent_keepalive
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
name_b = f"{id_b}-{id_a}-{id_suffix}"
|
||||||
|
addresses_b = [IPv4Interface(f"{ipv4_b}/{ptp_ipv4_network.prefixlen}"), IPv6Interface(f"{ipv6_b}/{ptp_ipv6_network.prefixlen}")]
|
||||||
preup_b = [f"ip ro replace {node_a.listen_address}/32 dev {node_b.interface_name} via {node_b.listen_gateway} metric 10 src {node_b.listen_address}"]
|
preup_b = [f"ip ro replace {node_a.listen_address}/32 dev {node_b.interface_name} via {node_b.listen_gateway} metric 10 src {node_b.listen_address}"]
|
||||||
predown_b = [f"ip ro del {node_a.listen_address}/32 dev {node_b.interface_name} via {node_b.listen_gateway} metric 10 src {node_b.listen_address}"]
|
predown_b = [f"ip ro del {node_a.listen_address}/32 dev {node_b.interface_name} via {node_b.listen_gateway} metric 10 src {node_b.listen_address}"]
|
||||||
postup_b = [f"ip ro replace {sn} dev {name_b} via {ipv4_a} metric 10" for sn in node_a.routed_ipv4_subnets] + [f"ip -6 ro replace {sn} dev {name_b} via {ipv6_a} metric 10" for sn in node_a.routed_ipv6_subnets]
|
postup_b = [f"ip ro replace {sn} dev {name_b} via {ipv4_a} metric 10" for sn in node_a.routed_ipv4_subnets] + [f"ip -6 ro replace {sn} dev {name_b} via {ipv6_a} metric 10" for sn in node_a.routed_ipv6_subnets]
|
||||||
postdown_b = [f"ip ro del {sn} dev {name_b} via {ipv4_a} metric 10" for sn in node_a.routed_ipv4_subnets] + [f"ip -6 ro del {sn} dev {name_b} via {ipv6_a} metric 10" for sn in node_a.routed_ipv6_subnets]
|
postdown_b = [f"ip ro del {sn} dev {name_b} via {ipv4_a} metric 10" for sn in node_a.routed_ipv4_subnets] + [f"ip -6 ro del {sn} dev {name_b} via {ipv6_a} metric 10" for sn in node_a.routed_ipv6_subnets]
|
||||||
|
|
||||||
config_b = WireguardConfig(
|
config_b = WireguardConfig(
|
||||||
addresses = [IPv4Interface(f"{ipv4_b}/{ptp_ipv4_network.prefixlen}"), IPv6Interface(f"{ipv6_b}/{ptp_ipv6_network.prefixlen}")],
|
addresses = addresses_b,
|
||||||
listen_port = port,
|
listen_port = port,
|
||||||
private_key = key_b,
|
private_key = key_b,
|
||||||
table = False,
|
table = table,
|
||||||
preup = preup_b,
|
preup = preup_b,
|
||||||
predown = predown_b,
|
predown = predown_b,
|
||||||
postup = postup_b,
|
postup = postup_b,
|
||||||
|
@ -140,32 +156,47 @@ def generate_wg_configs(config: WovenConfig):
|
||||||
peers = {
|
peers = {
|
||||||
key_a_pub: WireguardPeer(
|
key_a_pub: WireguardPeer(
|
||||||
public_key = key_a_pub,
|
public_key = key_a_pub,
|
||||||
allowed_ips = ["0.0.0.0/0", "::/0"],
|
allowed_ips = allowed_ips,
|
||||||
endpoint_host = node_a.listen_address,
|
endpoint_host = node_a.listen_address,
|
||||||
endpoint_port = port,
|
endpoint_port = port,
|
||||||
persistent_keepalive = 20
|
persistent_keepalive = persistent_keepalive
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
print(f"saving {id_a} side of {id_a} <-> {id_b} tunnel...", end = " ", flush = True)
|
||||||
cs[id_a].put(StringIO(config_a.to_wgconfig(wgquick_format = True)), f"/etc/wireguard/{name_a}.conf")
|
cs[id_a].put(StringIO(config_a.to_wgconfig(wgquick_format = True)), f"/etc/wireguard/{name_a}.conf")
|
||||||
cs[id_a].run(f"systemctl start wg-quick@{name_a}.service")
|
print("done")
|
||||||
|
print(f"saving {id_b} side of {id_a} <-> {id_b} tunnel...", end = " ", flush = True)
|
||||||
cs[id_b].put(StringIO(config_b.to_wgconfig(wgquick_format = True)), f"/etc/wireguard/{name_b}.conf")
|
cs[id_b].put(StringIO(config_b.to_wgconfig(wgquick_format = True)), f"/etc/wireguard/{name_b}.conf")
|
||||||
cs[id_b].run(f"systemctl start wg-quick@{name_b}.service")
|
print("done")
|
||||||
|
|
||||||
|
for id, c in cs.items():
|
||||||
|
print(f"starting services for {id}...", end = " ", flush = True)
|
||||||
|
c.run(f"for f in {wg_config_glob}; do systemctl start wg-quick@$(basename $f {wg_config_ext}).service; done")
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WovenArgs:
|
||||||
|
quiet: str
|
||||||
|
config: str
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
parser = ArgumentParser("woven")
|
parser = ArgumentParser("woven")
|
||||||
|
parser.add_argument("-q", "--quiet", action = "store_true", help = "decrease output verbosity")
|
||||||
parser.add_argument("-c", "--config", default = "config.json", help = "The path to the config file")
|
parser.add_argument("-c", "--config", default = "config.json", help = "The path to the config file")
|
||||||
args = parser.parse_args(namespace = WovenArgs)
|
args = parser.parse_args(namespace = WovenArgs)
|
||||||
|
|
||||||
|
with redirect_stdout(open(devnull, "w") if args.quiet else stdout):
|
||||||
config_path = Path(args.config)
|
config_path = Path(args.config)
|
||||||
config = fromdict(WovenConfig, loads(config_path.read_bytes()))
|
config = fromdict(WovenConfig, loads(config_path.read_bytes()))
|
||||||
generate_wg_configs(config)
|
generate_wg_configs(config)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"error: {e}", file = stderr)
|
print(f"error: {e}", file = stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue