You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
6.3 KiB

5 years ago
import logging
import subprocess
5 years ago
import tempfile
import typing
5 years ago
import const
import models
5 years ago
import schemas
import os
import re
5 years ago
import util
5 years ago
_LOGGER = logging.getLogger(__name__)
class WGAlreadyStartedError(Exception):
pass
class WGAlreadyStoppedError(Exception):
pass
class WGPermissionsError(Exception):
pass
5 years ago
class TempServerFile():
def __init__(self, server: schemas.WGServer):
self.server = server
self.td = tempfile.TemporaryDirectory(prefix="wg_man_")
self.server_file = os.path.join(self.td.name, f"{server.interface}.conf")
def __enter__(self):
with open(self.server_file, "w+") as f:
f.write(self.server.configuration)
return self.server_file
def __exit__(self, type, value, traceback):
self.td.cleanup()
5 years ago
def _run_wg(server: schemas.WGServer, command):
try:
output = subprocess.check_output(const.CMD_WG_COMMAND + command, stderr=subprocess.STDOUT)
return output
except Exception as e:
if b'Operation not permitted' in e.output:
raise WGPermissionsError("The user has insufficientt permissions for interface %s" % server.interface)
5 years ago
5 years ago
def is_installed():
output = subprocess.check_output(const.CMD_WG_COMMAND)
return output == b'' or b'interface' in output
5 years ago
def generate_keys() -> typing.Dict[str, str]:
5 years ago
private_key = subprocess.check_output(const.CMD_WG_COMMAND + ["genkey"])
public_key = subprocess.check_output(
const.CMD_WG_COMMAND + ["pubkey"],
input=private_key
)
5 years ago
private_key = private_key.decode("utf-8").strip()
public_key = public_key.decode("utf-8").strip()
return dict(
private_key=private_key,
public_key=public_key
)
5 years ago
def generate_psk():
return subprocess.check_output(const.CMD_WG_COMMAND + ["genpsk"]).decode("utf-8").strip()
def start_interface(server: schemas.WGServer):
5 years ago
with TempServerFile(server) as server_file:
try:
#print(*const.CMD_WG_QUICK, "up", server_file)
output = subprocess.check_output(const.CMD_WG_QUICK + ["up", server_file], stderr=subprocess.STDOUT)
return output
except Exception as e:
if b'already exists' in e.output:
raise WGAlreadyStartedError("The wireguard device %s is already started." % server.interface)
5 years ago
def stop_interface(server: schemas.WGServer):
5 years ago
with TempServerFile(server) as server_file:
try:
output = subprocess.check_output(const.CMD_WG_QUICK + ["down", server_file], stderr=subprocess.STDOUT)
return output
except Exception as e:
5 years ago
5 years ago
if b'is not a WireGuard interface' in e.output:
raise WGAlreadyStoppedError("The wireguard device %s is already stopped." % server.interface)
5 years ago
def restart_interface(server: schemas.WGServer):
try:
stop_interface(server)
except WGAlreadyStoppedError:
pass
start_interface(server)
def is_running(server: schemas.WGServer):
try:
output = _run_wg(server, ["show", server.interface])
if output is None or b'Unable to access interface: No such device' in output:
5 years ago
return False
except Exception as e:
if b'No such device' in e.output:
return False
return True
def add_peer(server: schemas.WGServer, peer: schemas.WGPeer):
try:
output = _run_wg(server, ["set", server.interface, "peer", peer.public_key, "allowed-ips", peer.address])
return output == b''
except Exception as e:
_LOGGER.exception(e)
return False
def remove_peer(server: schemas.WGServer, peer: schemas.WGPeer):
try:
output = _run_wg(server, ["set", server.interface, "peer", peer.public_key, "remove"])
return output == b''
except Exception as e:
_LOGGER.exception(e)
return False
def get_stats(server: schemas.WGServer):
try:
output = _run_wg(server, ["show", server.interface])
if not output:
return []
5 years ago
regex = r"peer:.*?^\n"
test_str = output.decode("utf-8") + "\n"
peers = []
peers_raw = re.findall(regex, test_str, re.MULTILINE | re.DOTALL)
for peer in peers_raw:
peer = peer.strip()
lines = [x.split(": ")[1] for x in peer.split("\n")]
if len(lines) == 2:
public_key, allowed_ips = lines
peers.append(dict(
public_key=public_key,
client_endpoint=None,
allowed_ips=allowed_ips,
handshake=None,
rx=None,
tx=None
))
5 years ago
elif len(lines) == 5 or len(lines) == 6:
public_key = lines[0]
client_endpoint, allowed_ips, handshake, rx_tx = lines[-4:] # [1] is sometimes psk
5 years ago
rx = re.match(r"^(.*) received", rx_tx).group(1)
tx = re.match(r"^.*, (.*)sent", rx_tx).group(1)
peers.append(dict(
public_key=public_key,
client_endpoint=client_endpoint,
allowed_ips=allowed_ips,
handshake=handshake,
rx=rx,
tx=tx
))
5 years ago
else:
ValueError("We have not handled peers with line number of %s" % str(len(lines)))
return peers
except Exception as e:
_LOGGER.exception(e)
return []
5 years ago
def move_server_dir(interface, interface1):
old_server_dir = const.SERVER_DIR(interface)
old_server_file = const.SERVER_FILE(interface)
new_server_dir = const.SERVER_DIR(interface1)
new_server_file = old_server_file.replace(f"{interface}.conf", f"{interface1}.conf")
os.rename(old_server_file, new_server_file)
os.rename(old_server_dir, new_server_dir)
def generate_config(obj: typing.Union[typing.Dict[schemas.WGPeer, schemas.WGServer], schemas.WGServer]):
if isinstance(obj, dict) and "server" in obj and "peer" in obj:
template = "peer.j2"
elif isinstance(obj, schemas.WGServer) or isinstance(obj, models.WGServer):
5 years ago
template = "server.j2"
else:
raise ValueError("Incorrect input type. Should be WGPeer or WGServer")
result = util.jinja_env.get_template(template).render(
data=obj
)
return result