2f215793e9
Add comprehensive docstrings to firewall, DHCP, proxy, wireguard, certs, and logs API endpoints. Document parameters, return values, and error cases for the documentation system.
326 lines
9.9 KiB
Python
326 lines
9.9 KiB
Python
"""WireGuard tunnel management API blueprint.
|
|
|
|
Exposed at /api/wireguard/* and delegates to vacuum-walld.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from flask import Blueprint, request
|
|
|
|
from daemon.client import BadRequest, NotFound, delete, get, patch, post
|
|
from webui.api.common import _error, _ok
|
|
|
|
logger = logging.getLogger(__name__)
|
|
bp = Blueprint("wireguard", __name__)
|
|
|
|
|
|
@bp.route("/config", methods=["GET"])
|
|
def get_config_bp():
|
|
"""Get the current WireGuard configuration.
|
|
|
|
Endpoint: GET /api/wireguard/config
|
|
|
|
Returns:
|
|
JSON response with the WireGuard config on success, or an error
|
|
response on failure.
|
|
"""
|
|
try:
|
|
return _ok(get("/wireguard/config"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to read WireGuard config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/config", methods=["POST"])
|
|
def post_config():
|
|
"""Create or fully replace the WireGuard configuration.
|
|
|
|
Endpoint: POST /api/wireguard/config
|
|
|
|
Args:
|
|
body: JSON body with the configuration. If an ``interface`` key
|
|
is present, the private key will be stripped before forwarding.
|
|
|
|
Returns:
|
|
Success response on acceptance, 400 on validation failure, or 500
|
|
on server error.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
if not isinstance(body, dict):
|
|
return _error("Request body must be a JSON object", 400)
|
|
try:
|
|
if "interface" in body:
|
|
body = dict(body)
|
|
body["interface"] = dict(body["interface"])
|
|
body["interface"].pop("private_key", None)
|
|
post("/wireguard/config", body)
|
|
return _ok(None)
|
|
except BadRequest as exc:
|
|
logger.info("WireGuard config save rejected: %s", exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to save WireGuard config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/config", methods=["PATCH"])
|
|
def patch_config():
|
|
"""Partially update the WireGuard configuration.
|
|
|
|
Endpoint: PATCH /api/wireguard/config
|
|
|
|
Args:
|
|
body: JSON body with the fields to update. If an ``interface``
|
|
key is present, the private key will be stripped before forwarding.
|
|
|
|
Returns:
|
|
Success response on acceptance, 400 on validation failure, or 500
|
|
on server error.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
if not isinstance(body, dict):
|
|
return _error("Request body must be a JSON object", 400)
|
|
try:
|
|
if "interface" in body:
|
|
body = dict(body)
|
|
body["interface"] = dict(body["interface"])
|
|
body["interface"].pop("private_key", None)
|
|
patch("/wireguard/config", body)
|
|
logger.info("WireGuard config patched: %s", sorted(body.keys()))
|
|
return _ok(None)
|
|
except BadRequest as exc:
|
|
logger.info("WireGuard config patch rejected: %s", exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to patch WireGuard config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/apply", methods=["POST"])
|
|
def apply_bp():
|
|
"""Apply the current WireGuard configuration to the live tunnel.
|
|
|
|
Endpoint: POST /api/wireguard/apply
|
|
|
|
Returns:
|
|
Success response on acceptance, or 500 on server error.
|
|
"""
|
|
try:
|
|
post("/wireguard/apply")
|
|
logger.info("WireGuard tunnel applied via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to apply WireGuard config: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/up", methods=["POST"])
|
|
def up_bp():
|
|
"""Bring the WireGuard tunnel interface up.
|
|
|
|
Endpoint: POST /api/wireguard/up
|
|
|
|
Returns:
|
|
Success response on acceptance, or 500 on server error.
|
|
"""
|
|
try:
|
|
post("/wireguard/apply")
|
|
logger.info("WireGuard tunnel started via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to start WireGuard tunnel: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/down", methods=["POST"])
|
|
def down_bp():
|
|
"""Bring the WireGuard tunnel interface down.
|
|
|
|
Endpoint: POST /api/wireguard/down
|
|
|
|
Returns:
|
|
Success response on acceptance, or 500 on server error.
|
|
"""
|
|
try:
|
|
post("/wireguard/down")
|
|
logger.info("WireGuard tunnel brought down via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to bring down WireGuard tunnel: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/status", methods=["GET"])
|
|
def status_bp():
|
|
"""Get the current WireGuard tunnel status.
|
|
|
|
Endpoint: GET /api/wireguard/status
|
|
|
|
Returns:
|
|
JSON response with the tunnel status on success, or an error
|
|
response on failure.
|
|
"""
|
|
try:
|
|
return _ok(get("/wireguard/status"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to get WireGuard status: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/initialize", methods=["POST"])
|
|
def initialize_bp():
|
|
"""Initialize WireGuard for first-time use.
|
|
|
|
Endpoint: POST /api/wireguard/initialize
|
|
|
|
Returns:
|
|
Success response on acceptance, or 500 on server error.
|
|
"""
|
|
try:
|
|
post("/wireguard/initialize")
|
|
logger.info("WireGuard initialized via API")
|
|
return _ok(None)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to initialize WireGuard: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/peers", methods=["POST"])
|
|
def add_peer_bp():
|
|
"""Add a new peer to the WireGuard configuration.
|
|
|
|
Endpoint: POST /api/wireguard/peers
|
|
|
|
Args:
|
|
name: Peer display name (required).
|
|
endpoint: Optional peer endpoint address.
|
|
allowed_ips: Optional list of allowed IP CIDRs.
|
|
persistent_keepalive: Optional keepalive interval in seconds.
|
|
preshared_key: Optional pre-shared key in hex.
|
|
|
|
Returns:
|
|
JSON response with the created peer on success, 400 on validation
|
|
failure, or 500 on server error.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
name = body.get("name", "").strip()
|
|
if not name:
|
|
return _error("'name' is required", 400)
|
|
try:
|
|
peer = post(
|
|
"/wireguard/peers/add",
|
|
{
|
|
"name": name,
|
|
"endpoint": body.get("endpoint"),
|
|
"allowed_ips": body.get("allowed_ips", []),
|
|
"persistent_keepalive": body.get("persistent_keepalive"),
|
|
"preshared_key": body.get("preshared_key"),
|
|
},
|
|
)
|
|
logger.info("WireGuard peer '%s' added via API", name)
|
|
return _ok(peer)
|
|
except BadRequest as exc:
|
|
logger.info("Add peer '%s' rejected: %s", name, exc)
|
|
return _error(str(exc), 400)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to add peer '%s': %s", name, exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/peers/<name>", methods=["DELETE"])
|
|
def remove_peer_bp(name):
|
|
"""Remove a peer from the WireGuard configuration.
|
|
|
|
Endpoint: DELETE /api/wireguard/peers/<name>
|
|
|
|
Args:
|
|
name: Peer name to remove (from URL path).
|
|
|
|
Returns:
|
|
Success response with peer name on removal, 404 if peer not found,
|
|
or 500 on server error.
|
|
"""
|
|
try:
|
|
delete("/wireguard/peers/remove", {"name": name})
|
|
logger.info("WireGuard peer '%s' removed via API", name)
|
|
return _ok({"name": name})
|
|
except NotFound as exc:
|
|
logger.info("WireGuard peer '%s' not found: %s", name, exc)
|
|
return _error(str(exc), 404)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to remove peer '%s': %s", name, exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/peers", methods=["GET"])
|
|
def peers_bp():
|
|
"""List all configured WireGuard peers.
|
|
|
|
Endpoint: GET /api/wireguard/peers
|
|
|
|
Returns:
|
|
JSON response with the peers list on success, or an error response
|
|
on failure.
|
|
"""
|
|
try:
|
|
return _ok(get("/wireguard/peers"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to list WireGuard peers: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/peer-status", methods=["GET"])
|
|
def peer_status_bp():
|
|
"""Get real-time status information for all WireGuard peers.
|
|
|
|
Endpoint: GET /api/wireguard/peer-status
|
|
|
|
Returns:
|
|
JSON response with peer status on success, or an error response
|
|
on failure.
|
|
"""
|
|
try:
|
|
return _ok(get("/wireguard/peer-status"))
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to get WireGuard peer status: %s", exc)
|
|
return _error(str(exc), 500)
|
|
|
|
|
|
@bp.route("/generate-client", methods=["POST"])
|
|
def generate_client_bp():
|
|
"""Generate a WireGuard client configuration file for a peer.
|
|
|
|
Endpoint: POST /api/wireguard/generate-client
|
|
|
|
Args:
|
|
name: Peer name (required).
|
|
server_endpoint: Server endpoint address for the client config (required).
|
|
|
|
Returns:
|
|
JSON response with the generated config string on success, 404 if
|
|
peer not found, 400 on validation failure, or 500 on server error.
|
|
"""
|
|
body = request.get_json(silent=True) or {}
|
|
name = body.get("name", "").strip()
|
|
if not name:
|
|
return _error("Field 'name' is required", 400)
|
|
server_endpoint = body.get("server_endpoint", "")
|
|
if not server_endpoint:
|
|
return _error("Field 'server_endpoint' is required", 400)
|
|
try:
|
|
result = post(
|
|
"/wireguard/generate-client",
|
|
{
|
|
"name": name,
|
|
"server_endpoint": server_endpoint,
|
|
},
|
|
)
|
|
logger.info("Client config generated for peer '%s' via API", name)
|
|
return _ok({"config": result.get("config", "")})
|
|
except NotFound as exc:
|
|
logger.info("Peer '%s' not found for client config: %s", name, exc)
|
|
return _error(str(exc), 404)
|
|
except RuntimeError as exc:
|
|
logger.error("Failed to generate client config for '%s': %s", name, exc)
|
|
return _error(str(exc), 500)
|