diff --git a/labctl/commands/devices.py b/labctl/commands/devices.py index 850059a..2bc023e 100644 --- a/labctl/commands/devices.py +++ b/labctl/commands/devices.py @@ -1,14 +1,20 @@ +from datetime import datetime from os import getcwd +from shutil import which +from subprocess import run, PIPE import typer from rich.table import Table from labctl.core import Config, APIDriver, console -from labctl.core import cli_ready, wireguard +from labctl.core import cli_ready app = typer.Typer() +def parse_datetime(date_str: str) -> str: + return datetime.fromisoformat(date_str.replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S") + @app.command(name="list") @cli_ready def list_devices(): @@ -18,54 +24,79 @@ def list_devices(): config = Config() devices = APIDriver().get("/devices/" + config.username).json() table = Table(title=":computer: Devices") - table.add_column("ID", style="cyan") - table.add_column("Name", style="magenta") + table.add_column("ID", style="bold") + table.add_column("Name", style="cyan") table.add_column("IPv4", style="green") - table.add_column("RX Bytes", style="blue") - table.add_column("TX Bytes", style="yellow") - table.add_column("Remote IP", style="red") + table.add_column("Created At", style="blue") + table.add_column("Expires At", style="red") + table.add_column("Last Seen", style="yellow") + table.add_column("Online", style="green") for device in devices: table.add_row( device["id"], - device["name"], - device["ipv4"], - device["rx_bytes"], - device["tx_bytes"], - device["remote_ip"], + device["givenName"], + ", ".join(device["ipAddresses"]), + parse_datetime(device["createdAt"]), + (device["expiry"] if device["expiry"] != "0001-01-01T00:00:00Z" else "Never"), + parse_datetime(device["lastSeen"]), + "Yes" if device["online"] else "No", ) console.print(table) -@app.command(name="create") +@app.command(name="enroll") @cli_ready -def create_device(name: str = typer.Argument(..., help="The device name")): +def enroll(): + """ + Self enroll device to vpn + """ + # Todo: Check if tailscale cli is installed and Create preauth key with api and call tailscale cli to enroll device + bin = which("tailscale") + if not bin: + console.print("[red]TailScale cli not found in path please install it[/red]") + return + config = Config() + api_driver = APIDriver() + key_rsp = api_driver.get(f"/devices/{config.username}/preauthkey") + key = key_rsp.json().get("key") + print("Running tailscale login...") + cmd = [bin, "login", "--login-server", "https://gw.laboinfra.net", "--auth-key", key, "--accept-routes", "true"] + print("Execeuting: " + " ".join(cmd)) + print("Output: " + run(cmd, stdout=PIPE).stdout.decode()) + +@app.command(name="logout") +def logout(): """ - Create a device + Self logout """ - rsp = APIDriver().post( - f"/devices/{Config().username}", - json={"name": name}, - additional_headers={"Content-Type": "application/json"} - ) - if rsp.status_code >= 200 < 300: - console.print(f"Device {name} created :tada:") - data = rsp.json() - config_path = f"/{getcwd()}/{name}.conf" - wireguard.generate_config(data["device"], data["private_key"], config_path) - console.print(f"Configuration file saved to {config_path}") + bin = which("tailscale") + if not bin: + console.print("[red]TailScale cli not found in path please install it[/red]") return - console.print(f"Error creating device {name} ({rsp.status_code})") + print("Running tailscale down...") + print("Output: " + run([bin, "down"], stdout=PIPE).stdout.decode()) + print("Running tailscale logout...") + print("Output: " + run([bin, "logout"], stdout=PIPE).stdout.decode()) + @app.command(name="delete") @cli_ready -def delete_device( - device_id: str = typer.Argument(..., help="The device ID") +def delete( + name: str = typer.Argument(None, help="The device name (found on the list command)"), ): """ - Delete a device + Self logout or logout specified device """ - rsp = APIDriver().delete(f"/devices/{Config().username}/{device_id}") - if rsp.status_code == 200: - console.print(f"Device {device_id} deleted :fire:") - return - console.print(f"Error deleting device {device_id} ({rsp.status_code})") + # Todo : Check if tailscale cli is installed logout user shutdown tailscale and call api to delete device if asked + config = Config() + api_driver = APIDriver() + + # delete http://localhost:8000/devices/admin/localhost-6uxdpldr + status = api_driver.delete(f"/devices/{config.username}/{name}").json() + + # rsp "success": true, "msg": "Device deleted" } + if status.get("success"): + console.print(f"Device {name} has ben removed from the vpn server :fire:") + else: + console.print(f"Failed to remove device {name}") + console.print(f"Reason given by the server : {status.get("msg")}") diff --git a/labctl/core/__init__.py b/labctl/core/__init__.py index 8c8ebf3..1e347fd 100644 --- a/labctl/core/__init__.py +++ b/labctl/core/__init__.py @@ -2,4 +2,3 @@ from .api import APIDriver from .console import console from .decorators import cli_ready -from . import wireguard diff --git a/labctl/core/wireguard.py b/labctl/core/wireguard.py deleted file mode 100644 index 6bab3e5..0000000 --- a/labctl/core/wireguard.py +++ /dev/null @@ -1,16 +0,0 @@ -import wgconfig - -def generate_config(device: dict, private_key: str, config_file: str): - wg = wgconfig.WGConfig(config_file) - wg.add_attr(None, "PrivateKey", private_key) - wg.add_attr(None, "Address", f"{device['ipv4']}/32, {device['ipv6']}/128") - wg.add_attr(None, "DNS", ", ".join(device["dns"])) - wg.add_attr(None, "MTU", device["mtu"]) - - wg.add_peer(device["server_public_key"], "# LaboInfra WireGuard Server Client") - wg.add_attr(device["server_public_key"], "Endpoint", device["endpoint"]) - wg.add_attr(device["server_public_key"], "AllowedIPs", ", ".join(device["allowed_ips"])) - wg.add_attr(device["server_public_key"], "PersistentKeepalive", device["persistent_keepalive"]) - wg.add_attr(device["server_public_key"], "PresharedKey", device["preshared_key"]) - - wg.write_file() diff --git a/labctl/main.py b/labctl/main.py index 874c446..4ef7aa6 100644 --- a/labctl/main.py +++ b/labctl/main.py @@ -1,9 +1,7 @@ -from typing import Annotated from time import sleep from os import environ from json import dumps -import requests import typer from rich.tree import Tree