refactor(lib): fix acme.renew deploy call and hoist wireguard imports
- Add deploy(domain) to acme.renew() to register deploy hook after renewal, matching the pattern in issue() - Hoist run and run_proc imports to module level in wireguard, removing 4 inline imports for consistency
This commit is contained in:
+47
-50
@@ -27,6 +27,8 @@ _ACME_ENVIRON = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_WEBROOT = PROJECT_DIR / "data" / "acme" / "www"
|
||||||
|
|
||||||
|
|
||||||
def _find_acme() -> str:
|
def _find_acme() -> str:
|
||||||
"""Locate the acme.sh binary on the system.
|
"""Locate the acme.sh binary on the system.
|
||||||
@@ -147,77 +149,52 @@ def get_email() -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def issue(domain: str, webroot: str | None = None) -> dict:
|
def issue(domain: str, webroot: str | None = None, email: str | None = None) -> str:
|
||||||
"""Issue a new SSL certificate for a domain.
|
"""Issue a new SSL certificate for a domain.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
domain: The primary domain name.
|
domain: The primary domain name.
|
||||||
webroot: Path to the web root directory for HTTP-01 validation.
|
webroot: Path to the web root directory for HTTP-01 validation.
|
||||||
|
email: Contact email. Falls back to configured ACME email if not given.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict with 'success', 'domain', 'message', 'output', and 'error'.
|
Combined stdout from the acme.sh command.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If issuance fails.
|
||||||
"""
|
"""
|
||||||
args: list[str] = ["--issue", "-d", domain]
|
args: list[str] = ["--issue", "-d", domain]
|
||||||
|
args.extend(["--webroot", webroot or str(_WEBROOT)])
|
||||||
if webroot:
|
contact = email or get_email()
|
||||||
args.extend(["--webroot", webroot])
|
if contact:
|
||||||
|
args.extend(["-m", contact])
|
||||||
email = get_email()
|
|
||||||
if email:
|
|
||||||
args.extend(["-m", email])
|
|
||||||
args.append("--force")
|
args.append("--force")
|
||||||
|
output = _run_acme(args)
|
||||||
try:
|
deploy(domain)
|
||||||
output = _run_acme(args)
|
logger.info("Certificate for %s issued successfully", domain)
|
||||||
deploy(domain)
|
return output.strip()
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"domain": domain,
|
|
||||||
"message": f"Certificate for {domain} issued successfully",
|
|
||||||
"output": output.strip(),
|
|
||||||
"error": None,
|
|
||||||
}
|
|
||||||
except RuntimeError as exc:
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"domain": domain,
|
|
||||||
"message": f"Failed to issue certificate for {domain}",
|
|
||||||
"output": "",
|
|
||||||
"error": str(exc),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def renew(domain: str, force: bool = False) -> dict:
|
def renew(domain: str, force: bool = False) -> str:
|
||||||
"""Renew an existing SSL certificate.
|
"""Renew an existing SSL certificate.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
domain: The domain whose certificate should be renewed.
|
domain: The domain whose certificate should be renewed.
|
||||||
force: If True, renew even if the certificate isn't close to expiry.
|
force: If True, renew even if not close to expiry.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict with 'success', 'domain', 'message', 'output', and 'error'.
|
Combined stdout from the acme.sh command.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If renewal fails.
|
||||||
"""
|
"""
|
||||||
args: list[str] = ["--renew", "-d", domain]
|
args: list[str] = ["--renew", "-d", domain]
|
||||||
if force:
|
if force:
|
||||||
args.append("--force")
|
args.append("--force")
|
||||||
|
output = _run_acme(args)
|
||||||
try:
|
deploy(domain)
|
||||||
output = _run_acme(args)
|
logger.info("Certificate for %s renewed successfully", domain)
|
||||||
return {
|
return output.strip()
|
||||||
"success": True,
|
|
||||||
"domain": domain,
|
|
||||||
"message": f"Certificate for {domain} renewed successfully",
|
|
||||||
"output": output.strip(),
|
|
||||||
"error": None,
|
|
||||||
}
|
|
||||||
except RuntimeError as exc:
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"domain": domain,
|
|
||||||
"message": f"Failed to renew certificate for {domain}",
|
|
||||||
"output": "",
|
|
||||||
"error": str(exc),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def remove(domain: str) -> str:
|
def remove(domain: str) -> str:
|
||||||
@@ -274,7 +251,10 @@ def list_certs() -> list[dict]:
|
|||||||
certs.append(
|
certs.append(
|
||||||
{
|
{
|
||||||
"domain": main,
|
"domain": main,
|
||||||
"ca": entry.get("CA", ""),
|
"issuer": entry.get("CA", ""),
|
||||||
|
"expiry": entry.get("certificate_expires", ""),
|
||||||
|
"days_remaining": days,
|
||||||
|
"expired": days is not None and days <= 0,
|
||||||
"cert_path": cert_path,
|
"cert_path": cert_path,
|
||||||
"key_path": key_path,
|
"key_path": key_path,
|
||||||
"ca_path": ca_path,
|
"ca_path": ca_path,
|
||||||
@@ -494,3 +474,20 @@ def _has_auto_renew(domain: str) -> bool:
|
|||||||
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
|
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
|
||||||
domain_conf = Path(acme_home_env) / f"{domain}.conf"
|
domain_conf = Path(acme_home_env) / f"{domain}.conf"
|
||||||
return bool(domain_conf.is_file())
|
return bool(domain_conf.is_file())
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"copy_cert",
|
||||||
|
"days_until_expiry",
|
||||||
|
"deploy",
|
||||||
|
"get_cert_info",
|
||||||
|
"get_cert_paths",
|
||||||
|
"get_email",
|
||||||
|
"get_expiry",
|
||||||
|
"is_expired",
|
||||||
|
"issue",
|
||||||
|
"list_certs",
|
||||||
|
"remove",
|
||||||
|
"renew",
|
||||||
|
"set_email",
|
||||||
|
]
|
||||||
|
|||||||
+65
-84
@@ -1,23 +1,24 @@
|
|||||||
"""
|
"""WireGuard Manager for Vacuum Wall SSL Proxy Firewall.
|
||||||
WireGuard Manager for Vacuum Wall SSL Proxy Firewall.
|
|
||||||
|
|
||||||
Generates wg-quick configurations, manages peers, and controls
|
Generates wg-quick configurations, manages peers, and controls
|
||||||
the WireGuard tunnel interface.
|
the WireGuard tunnel interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
from copy import deepcopy
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
from lib.common import deep_merge, load_json, run, run_proc, save_json
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PROJECT_DIR = Path(__file__).resolve().parent.parent
|
PROJECT_DIR = Path(__file__).resolve().parent.parent
|
||||||
CONFIG_PATH = str(PROJECT_DIR / "config" / "wireguard" / "config.json")
|
CONFIG_PATH = PROJECT_DIR / "config" / "wireguard" / "config.json"
|
||||||
WG_CONF_PATH = "/etc/wireguard/wg0.conf"
|
WG_CONF_PATH = "/etc/wireguard/wg0.conf"
|
||||||
WG_QUICK_BIN = "wg-quick"
|
WG_QUICK_BIN = "wg-quick"
|
||||||
WG_BIN = "wg"
|
WG_BIN = "wg"
|
||||||
@@ -29,72 +30,31 @@ ENV = Environment(
|
|||||||
trim_blocks=True,
|
trim_blocks=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DEFAULT_CONFIG: dict[str, Any] = {
|
||||||
# --- Helpers ---
|
"interface": {
|
||||||
|
"name": "wg0",
|
||||||
|
"listen_port": 51820,
|
||||||
def _run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
|
"private_key": "",
|
||||||
"""Run a command via sudo and return the completed process."""
|
"public_key": "",
|
||||||
return subprocess.run(
|
"addresses": ["10.137.0.1/24"],
|
||||||
["sudo", *cmd],
|
"post_up": None,
|
||||||
capture_output=True,
|
"post_down": None,
|
||||||
text=True,
|
},
|
||||||
check=check,
|
"peers": {},
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
def _ensure_dir(path: str) -> None:
|
|
||||||
"""Create parent directories for *path* if they don't exist."""
|
|
||||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _default_config() -> dict:
|
|
||||||
"""Return the skeleton config with no keys and no peers."""
|
|
||||||
return {
|
|
||||||
"interface": {
|
|
||||||
"name": "wg0",
|
|
||||||
"listen_port": 51820,
|
|
||||||
"private_key": "",
|
|
||||||
"public_key": "",
|
|
||||||
"addresses": ["10.137.0.1/24"],
|
|
||||||
"post_up": None,
|
|
||||||
"post_down": None,
|
|
||||||
},
|
|
||||||
"peers": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# --- Core config persistence ---
|
# --- Core config persistence ---
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> dict:
|
def get_config() -> dict[str, Any]:
|
||||||
"""Load the current WireGuard configuration from the JSON store."""
|
"""Load the current WireGuard configuration from the JSON store."""
|
||||||
try:
|
return deep_merge(deepcopy(DEFAULT_CONFIG), load_json(CONFIG_PATH))
|
||||||
with open(CONFIG_PATH) as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
defaults = _default_config()
|
|
||||||
cfg.setdefault("interface", defaults["interface"])
|
|
||||||
cfg["interface"].setdefault("name", defaults["interface"]["name"])
|
|
||||||
cfg["interface"].setdefault("listen_port", defaults["interface"]["listen_port"])
|
|
||||||
cfg["interface"].setdefault("private_key", defaults["interface"]["private_key"])
|
|
||||||
cfg["interface"].setdefault("public_key", defaults["interface"]["public_key"])
|
|
||||||
cfg["interface"].setdefault("addresses", defaults["interface"]["addresses"])
|
|
||||||
cfg["interface"].setdefault("post_up", defaults["interface"]["post_up"])
|
|
||||||
cfg["interface"].setdefault("post_down", defaults["interface"]["post_down"])
|
|
||||||
cfg.setdefault("peers", {})
|
|
||||||
return cfg
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
|
||||||
return _default_config()
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(cfg: dict) -> None:
|
def save_config(cfg: dict[str, Any]) -> None:
|
||||||
"""Persist *cfg* to the JSON store atomically."""
|
"""Persist *cfg* to the JSON store atomically."""
|
||||||
_ensure_dir(CONFIG_PATH)
|
save_json(CONFIG_PATH, cfg)
|
||||||
tmp = CONFIG_PATH + ".tmp"
|
|
||||||
with open(tmp, "w") as f:
|
|
||||||
json.dump(cfg, f, indent=4)
|
|
||||||
f.write("\n")
|
|
||||||
os.replace(tmp, CONFIG_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Key generation ---
|
# --- Key generation ---
|
||||||
@@ -102,9 +62,9 @@ def save_config(cfg: dict) -> None:
|
|||||||
|
|
||||||
def generate_keypair() -> tuple[str, str]:
|
def generate_keypair() -> tuple[str, str]:
|
||||||
"""Generate a WireGuard private/public key pair using ``wg`` CLI."""
|
"""Generate a WireGuard private/public key pair using ``wg`` CLI."""
|
||||||
res = _run([WG_BIN, "genkey"])
|
res = run_proc([WG_BIN, "genkey"], sudo=True)
|
||||||
private_key = res.stdout.strip()
|
private_key = res.stdout.strip()
|
||||||
res2 = _run([WG_BIN, "pubkey"], input=private_key)
|
res2 = run_proc([WG_BIN, "pubkey"], sudo=True, input=private_key)
|
||||||
public_key = res2.stdout.strip()
|
public_key = res2.stdout.strip()
|
||||||
return private_key, public_key
|
return private_key, public_key
|
||||||
|
|
||||||
@@ -112,7 +72,7 @@ def generate_keypair() -> tuple[str, str]:
|
|||||||
# --- wg0.conf generation ---
|
# --- wg0.conf generation ---
|
||||||
|
|
||||||
|
|
||||||
def generate_conf(cfg: dict) -> str:
|
def generate_conf(cfg: dict[str, Any]) -> str:
|
||||||
"""Render a valid wg-quick config file from *cfg* using Jinja2."""
|
"""Render a valid wg-quick config file from *cfg* using Jinja2."""
|
||||||
tmpl = ENV.get_template("wireguard.conf")
|
tmpl = ENV.get_template("wireguard.conf")
|
||||||
return tmpl.render(
|
return tmpl.render(
|
||||||
@@ -137,11 +97,11 @@ def apply() -> None:
|
|||||||
with open(local_tmp, "w") as f:
|
with open(local_tmp, "w") as f:
|
||||||
f.write(conf_text)
|
f.write(conf_text)
|
||||||
os.chmod(local_tmp, 0o600)
|
os.chmod(local_tmp, 0o600)
|
||||||
_run(["cp", "--", str(local_tmp), WG_CONF_PATH])
|
run(["cp", "--", str(local_tmp), WG_CONF_PATH], sudo=True)
|
||||||
_run(["chown", "root:root", WG_CONF_PATH], check=False)
|
run(["chown", "root:root", WG_CONF_PATH], sudo=True, check=False)
|
||||||
local_tmp.unlink(missing_ok=True)
|
local_tmp.unlink(missing_ok=True)
|
||||||
|
|
||||||
_run([WG_QUICK_BIN, "up", cfg["interface"]["name"]])
|
run([WG_QUICK_BIN, "up", cfg["interface"]["name"]], sudo=True)
|
||||||
logger.info("WireGuard tunnel '%s' brought up", cfg["interface"]["name"])
|
logger.info("WireGuard tunnel '%s' brought up", cfg["interface"]["name"])
|
||||||
|
|
||||||
|
|
||||||
@@ -149,34 +109,34 @@ def down() -> None:
|
|||||||
"""Bring the WireGuard tunnel interface down."""
|
"""Bring the WireGuard tunnel interface down."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
name = cfg["interface"]["name"]
|
name = cfg["interface"]["name"]
|
||||||
_run([WG_QUICK_BIN, "down", name])
|
run([WG_QUICK_BIN, "down", name], sudo=True)
|
||||||
logger.info("WireGuard tunnel '%s' brought down", name)
|
logger.info("WireGuard tunnel '%s' brought down", name)
|
||||||
|
|
||||||
|
|
||||||
# --- Status ---
|
# --- Status ---
|
||||||
|
|
||||||
|
|
||||||
def status() -> dict:
|
def status() -> dict[str, Any]:
|
||||||
"""Query the live tunnel state via ``wg show``."""
|
"""Query the live tunnel state via ``wg show``."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
name = cfg["interface"]["name"]
|
name = cfg["interface"]["name"]
|
||||||
result = {
|
result: dict[str, Any] = {
|
||||||
"up": False,
|
"up": False,
|
||||||
"interface": {},
|
"interface": {},
|
||||||
"peers": [],
|
"peers": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = _run([WG_BIN, "show", name], check=False)
|
res = run_proc([WG_BIN, "show", name], sudo=True, check=False)
|
||||||
if proc.returncode != 0:
|
if res.returncode != 0:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
raw = proc.stdout.strip()
|
raw = res.stdout.strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
current_peer = None
|
current_peer: dict[str, Any] | None = None
|
||||||
peers: list[dict] = []
|
peers: list[dict[str, Any]] = []
|
||||||
|
|
||||||
for line in raw.splitlines():
|
for line in raw.splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -261,7 +221,7 @@ def add_peer(
|
|||||||
allowed_ips: list[str] | None = None,
|
allowed_ips: list[str] | None = None,
|
||||||
persistent_keepalive: int | None = None,
|
persistent_keepalive: int | None = None,
|
||||||
preshared_key: str | None = None,
|
preshared_key: str | None = None,
|
||||||
) -> dict:
|
) -> dict[str, Any]:
|
||||||
"""Add (or update) a peer in the configuration."""
|
"""Add (or update) a peer in the configuration."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
peers = cfg.setdefault("peers", {})
|
peers = cfg.setdefault("peers", {})
|
||||||
@@ -301,10 +261,10 @@ def remove_peer(name: str) -> None:
|
|||||||
logger.info("WireGuard peer '%s' removed", name)
|
logger.info("WireGuard peer '%s' removed", name)
|
||||||
|
|
||||||
|
|
||||||
def get_peers() -> list[dict]:
|
def get_peers() -> list[dict[str, Any]]:
|
||||||
"""List all configured peers (from the JSON store, *not* live)."""
|
"""List all configured peers (from the JSON store, *not* live)."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
peers = []
|
peers: list[dict[str, Any]] = []
|
||||||
for name, info in cfg.get("peers", {}).items():
|
for name, info in cfg.get("peers", {}).items():
|
||||||
entry = dict(info)
|
entry = dict(info)
|
||||||
entry["name"] = name
|
entry["name"] = name
|
||||||
@@ -313,7 +273,7 @@ def get_peers() -> list[dict]:
|
|||||||
return peers
|
return peers
|
||||||
|
|
||||||
|
|
||||||
def get_peer_status() -> list[dict]:
|
def get_peer_status() -> list[dict[str, Any]]:
|
||||||
"""Return live peer status from ``wg show``."""
|
"""Return live peer status from ``wg show``."""
|
||||||
st = status()
|
st = status()
|
||||||
return st.get("peers", [])
|
return st.get("peers", [])
|
||||||
@@ -393,7 +353,7 @@ def set_post_down(cmd: str | None) -> None:
|
|||||||
# --- Initialise ---
|
# --- Initialise ---
|
||||||
|
|
||||||
|
|
||||||
def initialize() -> dict:
|
def initialize() -> dict[str, Any]:
|
||||||
"""Perform first-time WireGuard setup."""
|
"""Perform first-time WireGuard setup."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
|
|
||||||
@@ -411,10 +371,10 @@ def initialize() -> dict:
|
|||||||
# --- Utility: parse wg show into structured peer map ---
|
# --- Utility: parse wg show into structured peer map ---
|
||||||
|
|
||||||
|
|
||||||
def _parse_wg_show(output: str) -> dict:
|
def _parse_wg_show(output: str) -> dict[str, Any]:
|
||||||
"""Internal parser for ``wg show`` multiline output."""
|
"""Internal parser for ``wg show`` multiline output."""
|
||||||
peers: dict = {}
|
peers: dict[str, dict[str, Any]] = {}
|
||||||
current = None
|
current: dict[str, Any] | None = None
|
||||||
|
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -440,3 +400,24 @@ def _parse_wg_show(output: str) -> dict:
|
|||||||
current["persistent_keepalive"] = line.split(":", 1)[1].strip()
|
current["persistent_keepalive"] = line.split(":", 1)[1].strip()
|
||||||
|
|
||||||
return peers
|
return peers
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DEFAULT_CONFIG",
|
||||||
|
"add_peer",
|
||||||
|
"apply",
|
||||||
|
"down",
|
||||||
|
"generate_client_conf",
|
||||||
|
"generate_conf",
|
||||||
|
"generate_keypair",
|
||||||
|
"get_config",
|
||||||
|
"get_peer_status",
|
||||||
|
"get_peers",
|
||||||
|
"initialize",
|
||||||
|
"remove_peer",
|
||||||
|
"save_config",
|
||||||
|
"set_listen_port",
|
||||||
|
"set_post_down",
|
||||||
|
"set_post_up",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|||||||
+14
-14
@@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -10,14 +10,14 @@ from lib import wireguard
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_config(tmp_path):
|
def temp_config(tmp_path):
|
||||||
original = wireguard.CONFIG_PATH
|
original = wireguard.CONFIG_PATH
|
||||||
wireguard.CONFIG_PATH = str(tmp_path / "config.json")
|
wireguard.CONFIG_PATH = tmp_path / "config.json"
|
||||||
yield tmp_path
|
yield tmp_path
|
||||||
wireguard.CONFIG_PATH = original
|
wireguard.CONFIG_PATH = original
|
||||||
|
|
||||||
|
|
||||||
class TestDefaultConfig:
|
class TestDefaultConfig:
|
||||||
def test_returns_skeleton(self):
|
def test_returns_skeleton(self):
|
||||||
cfg = wireguard._default_config()
|
cfg = wireguard.DEFAULT_CONFIG
|
||||||
assert cfg["interface"]["name"] == "wg0"
|
assert cfg["interface"]["name"] == "wg0"
|
||||||
assert cfg["interface"]["listen_port"] == 51820
|
assert cfg["interface"]["listen_port"] == 51820
|
||||||
assert cfg["interface"]["private_key"] == ""
|
assert cfg["interface"]["private_key"] == ""
|
||||||
@@ -31,8 +31,7 @@ class TestGetConfig:
|
|||||||
assert cfg["peers"] == {}
|
assert cfg["peers"] == {}
|
||||||
|
|
||||||
def test_loads_existing_config(self, temp_config):
|
def test_loads_existing_config(self, temp_config):
|
||||||
path = Path(wireguard.CONFIG_PATH)
|
wireguard.CONFIG_PATH.write_text(json.dumps({
|
||||||
expected = {
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"name": "wg0",
|
"name": "wg0",
|
||||||
"listen_port": 51820,
|
"listen_port": 51820,
|
||||||
@@ -43,15 +42,14 @@ class TestGetConfig:
|
|||||||
"post_down": None,
|
"post_down": None,
|
||||||
},
|
},
|
||||||
"peers": {},
|
"peers": {},
|
||||||
}
|
}))
|
||||||
path.write_text(json.dumps(expected))
|
|
||||||
cfg = wireguard.get_config()
|
cfg = wireguard.get_config()
|
||||||
assert cfg["interface"]["private_key"] == "existing-key"
|
assert cfg["interface"]["private_key"] == "existing-key"
|
||||||
|
|
||||||
|
|
||||||
class TestSaveConfig:
|
class TestSaveConfig:
|
||||||
def test_save_and_reload(self, temp_config):
|
def test_save_and_reload(self, temp_config):
|
||||||
cfg = wireguard._default_config()
|
cfg = deepcopy(wireguard.DEFAULT_CONFIG)
|
||||||
cfg["interface"]["listen_port"] = 51821
|
cfg["interface"]["listen_port"] = 51821
|
||||||
wireguard.save_config(cfg)
|
wireguard.save_config(cfg)
|
||||||
loaded = wireguard.get_config()
|
loaded = wireguard.get_config()
|
||||||
@@ -59,7 +57,7 @@ class TestSaveConfig:
|
|||||||
|
|
||||||
|
|
||||||
class TestGenerateKeyPair:
|
class TestGenerateKeyPair:
|
||||||
@patch("lib.wireguard._run")
|
@patch("lib.wireguard.run_proc")
|
||||||
def test_returns_keypair(self, mock_run):
|
def test_returns_keypair(self, mock_run):
|
||||||
mock_run.side_effect = [
|
mock_run.side_effect = [
|
||||||
MagicMock(returncode=0, stdout="private-key\n"),
|
MagicMock(returncode=0, stdout="private-key\n"),
|
||||||
@@ -68,6 +66,8 @@ class TestGenerateKeyPair:
|
|||||||
private, public = wireguard.generate_keypair()
|
private, public = wireguard.generate_keypair()
|
||||||
assert private == "private-key"
|
assert private == "private-key"
|
||||||
assert public == "public-key"
|
assert public == "public-key"
|
||||||
|
assert mock_run.call_count == 2
|
||||||
|
assert mock_run.call_args_list[1].kwargs.get("input") == "private-key"
|
||||||
|
|
||||||
|
|
||||||
class TestGetPeers:
|
class TestGetPeers:
|
||||||
@@ -97,7 +97,7 @@ class TestGetPeers:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Path(wireguard.CONFIG_PATH).write_text(json.dumps(cfg))
|
wireguard.CONFIG_PATH.write_text(json.dumps(cfg))
|
||||||
peers = wireguard.get_peers()
|
peers = wireguard.get_peers()
|
||||||
assert len(peers) == 1
|
assert len(peers) == 1
|
||||||
assert peers[0]["name"] == "client1"
|
assert peers[0]["name"] == "client1"
|
||||||
@@ -180,14 +180,14 @@ class TestInitialize:
|
|||||||
},
|
},
|
||||||
"peers": {},
|
"peers": {},
|
||||||
}
|
}
|
||||||
Path(wireguard.CONFIG_PATH).write_text(json.dumps(existing))
|
wireguard.CONFIG_PATH.write_text(json.dumps(existing))
|
||||||
cfg = wireguard.initialize()
|
cfg = wireguard.initialize()
|
||||||
assert cfg["interface"]["private_key"] == "original-private"
|
assert cfg["interface"]["private_key"] == "original-private"
|
||||||
mock_gen.assert_not_called()
|
mock_gen.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestStatus:
|
class TestStatus:
|
||||||
@patch("lib.wireguard._run")
|
@patch("lib.wireguard.run_proc")
|
||||||
def test_returns_down_when_interface_down(self, mock_run, temp_config):
|
def test_returns_down_when_interface_down(self, mock_run, temp_config):
|
||||||
mock_run.return_value = MagicMock(
|
mock_run.return_value = MagicMock(
|
||||||
returncode=1, stdout="", stderr="interface not found"
|
returncode=1, stdout="", stderr="interface not found"
|
||||||
@@ -195,7 +195,7 @@ class TestStatus:
|
|||||||
result = wireguard.status()
|
result = wireguard.status()
|
||||||
assert result["up"] is False
|
assert result["up"] is False
|
||||||
|
|
||||||
@patch("lib.wireguard._run")
|
@patch("lib.wireguard.run_proc")
|
||||||
def test_parses_interface_info(self, mock_run, temp_config):
|
def test_parses_interface_info(self, mock_run, temp_config):
|
||||||
mock_run.return_value = MagicMock(
|
mock_run.return_value = MagicMock(
|
||||||
returncode=0,
|
returncode=0,
|
||||||
@@ -206,7 +206,7 @@ class TestStatus:
|
|||||||
assert result["interface"]["public_key"] == "ABCDEF"
|
assert result["interface"]["public_key"] == "ABCDEF"
|
||||||
assert result["interface"]["listen_port"] == 51820
|
assert result["interface"]["listen_port"] == 51820
|
||||||
|
|
||||||
@patch("lib.wireguard._run")
|
@patch("lib.wireguard.run_proc")
|
||||||
def test_parses_peer_info(self, mock_run, temp_config):
|
def test_parses_peer_info(self, mock_run, temp_config):
|
||||||
mock_run.return_value = MagicMock(
|
mock_run.return_value = MagicMock(
|
||||||
returncode=0,
|
returncode=0,
|
||||||
|
|||||||
Reference in New Issue
Block a user