diff --git a/leylines/leylines/__init__.py b/leylines/leylines/__init__.py index b9b8a7c..95576d2 100644 --- a/leylines/leylines/__init__.py +++ b/leylines/leylines/__init__.py @@ -1,5 +1,7 @@ +import asyncio import binascii -from typing import Optional +import secrets +from typing import Optional, Any from pyroute2 import IPRoute, WireGuard @@ -9,9 +11,28 @@ from .database import Database, Node, SERVER_NODE_ID IFNAME = 'leyline-wg' DEFAULT_PORT = 31337 +API_PORT = 31338 db = Database("leylines.db") +async def client_connected(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + try: + while True: + line = reader.readline() + if line.strip() != db.get_token(): + raise Exception("unauthorized") + # TODO + finally: + writer.close() + await writer.wait_closed() + + +async def main() -> None: + sync_interface() + server = await asyncio.create_server(client_connected, host="127.0.0.1", port=API_PORT) + await server.serve_forever() + + def net_init() -> None: with open("/proc/sys/net/ipv4/ip_forward", "w") as f: f.write("1") @@ -62,6 +83,7 @@ def add_node_to_wg(wg: WireGuard, node: Node) -> None: def create_or_get_interface() -> int: + net_init() with IPRoute() as ipr: lookup = ipr.link_lookup(ifname=IFNAME) if len(lookup) > 0: diff --git a/leylines/leylines/__main__.py b/leylines/leylines/__main__.py index 7c0a889..e238980 100644 --- a/leylines/leylines/__main__.py +++ b/leylines/leylines/__main__.py @@ -1,5 +1,65 @@ -from leylines import net_init, create_or_get_interface, sync_interface +import argparse +import ipaddress -net_init() -create_or_get_interface() -sync_interface() +from leylines import main, db, sync_interface, seckey_to_pubkey, generate_node_config +from leylines.database import SERVER_NODE_ID + + +def nop(): + # needed because setup.py is weird + pass + + +parser = argparse.ArgumentParser(description="wireguard management system for dragons") +cmd = parser.add_subparsers(dest="cmd") +cmd.required = True + +cmd_daemon = cmd.add_parser("daemon") + +cmd_status = cmd.add_parser("status") + +cmd_init = cmd.add_parser("init") +cmd_init.add_argument("-n", "--name", type=str, help="Name of the server node", required=True) +cmd_init.add_argument("-i", "--ip", type=str, help="Public IP of the server node", required=True) + +cmd_add = cmd.add_parser("add") +cmd_add.add_argument("-n", "--name", type=str, help="Name of the node to add", required=True) +cmd_add.add_argument("-i", "--ip", type=str, help="Public IP of the node, if any", required=False) +cmd_add.add_argument("-k", "--ssh-key", type=argparse.FileType("r"), + help="SSH private keyfile for the node, if any", required=False) + +cmd_get_conf = cmd.add_parser("get-conf") +cmd_get_conf.add_argument("id", type=int, help="Node ID") + +cmd_sync = cmd.add_parser("sync") + +args = parser.parse_args() + +if args.cmd == "daemon": + asyncio.run(main()) +elif args.cmd == "status": + token = db.get_token() + subnet = db.get_subnet() + nodes = db.get_nodes() + server_node = db.get_server_node() + print("TOKEN:", token) + print("SUBNET:", subnet) + if server_node is None: + print("SERVER: ") + else: + print("SERVER:", server_node.name, server_node.ip, server_node.public_ip, + seckey_to_pubkey(server_node.seckey)) + for node in nodes: + if node.id == SERVER_NODE_ID: + continue + print(f"NODE {node.id}:", node.name, node.public_ip, node.ip, + seckey_to_pubkey(node.seckey), "" if node.ssh_key is not None else "") +elif args.cmd == "init": + db.init_server(args.name, ipaddress.IPv4Address(args.ip)) +elif args.cmd == "add": + db.add_node(args.name, args.ip, args.ssh_key.read() if args.ssh_key is not None else None) + sync_interface() +elif args.cmd == "get-conf": + print(generate_node_config(args.id)) +elif args.cmd == "sync": + sync_interface() diff --git a/leylines/leylines/database.py b/leylines/leylines/database.py index 1ae26ed..d2897b9 100644 --- a/leylines/leylines/database.py +++ b/leylines/leylines/database.py @@ -1,5 +1,6 @@ import binascii import ipaddress +import secrets import sqlite3 from typing import Iterable, List, NamedTuple, Optional, Set @@ -38,6 +39,9 @@ class Database: "CREATE TABLE IF NOT EXISTS settings (name TEXT PRIMARY KEY, value TEXT NOT NULL)") self.conn.execute( "INSERT OR IGNORE INTO settings (name, value) VALUES ('subnet', ?)", (DEFAULT_SUBNET,)) + self.conn.execute( + "INSERT OR IGNORE INTO settings (name, value) VALUES ('token', ?)", + (secrets.token_hex(32),)) self.conn.commit() @@ -51,6 +55,11 @@ class Database: cur.execute("SELECT value FROM settings WHERE name='subnet'") return ipaddress.IPv4Interface(cur.fetchone()[0]) + def get_token(self) -> str: + cur = self.conn.cursor() + cur.execute("SELECT value FROM settings WHERE name='token'") + return cur.fetchone()[0] + def _get_free_ip(self) -> ipaddress.IPv4Address: subnet = self.get_subnet().network subnets = [self.get_subnet().network] diff --git a/leylines/setup.py b/leylines/setup.py index 8f4d180..de1ba60 100644 --- a/leylines/setup.py +++ b/leylines/setup.py @@ -1,20 +1,22 @@ from setuptools import setup -setup(name='leylines', - version='0.1', - description='Sample text', - url='https://awoo.systems', - author='haskal', - author_email='haskal@awoo.systems', - license='AGPLv3', - packages=['leylines'], - install_requires=[ - # requirements - ], - include_package_data=True, - entry_points={ - 'console_scripts': [ - # bins - ] - }, - zip_safe=True) +setup( + name='leylines', + version='0.1', + description='Sample text', + url='https://awoo.systems', + author='haskal', + author_email='haskal@awoo.systems', + license='AGPLv3', + packages=['leylines'], + install_requires=[ + "leylines-monocypher", + "pyroute2" + ], + include_package_data=True, + entry_points={ + 'console_scripts': [ + "leylines=leylines.__main__:nop" + ] + }, + zip_safe=True)