"""ACME certificate management API blueprint. Exposed at /api/certs/* and delegates to vacuum-walld. """ from typing import Any from flask import Blueprint from daemon.client import delete, get, post # noqa: F401 (resolved via module globals) from daemon.iface import ( DELETE_ACME_ACCOUNT_DEACTIVATE, DELETE_ACME_REMOVE, GET_ACME_ACCOUNT, GET_ACME_INFO, GET_ACME_ISSUE_STATUS, GET_ACME_LIST, GET_ACME_RENEW_STATUS, POST_ACME_ACCOUNT_REGISTER, POST_ACME_EMAIL, POST_ACME_ISSUE, POST_ACME_RENEW, POST_ACME_VALIDATE, ) from webui.api.common import NO_BODY, daemon_route, void_transform bp = Blueprint("certs", __name__) def _validate_body(request: Any, _va: Any) -> dict[str, Any]: domain = ((request.get_json(silent=True) or {}).get("domain") or "").strip() if not domain: raise ValueError("'domain' is required") return {"domain": domain} def _issue_body(request: Any, _va: Any) -> dict[str, Any]: body = request.get_json(silent=True) or {} domain = (body.get("domain") or "").strip() if not domain: raise ValueError("'domain' is required") email = (body.get("email") or "").strip() or None return {"domain": domain, "webroot": body.get("webroot"), "email": email} def _email_body(request: Any, _va: Any) -> dict[str, Any]: email = ((request.get_json(silent=True) or {}).get("email") or "").strip() if not email: raise ValueError("'email' is required") return {"email": email} def _register_body(request: Any, _va: Any) -> dict[str, Any]: body = request.get_json(silent=True) or {} email = (body.get("email") or "").strip() if not email: raise ValueError("'email' is required") return {"email": email, "server": (body.get("server") or "").strip()} def _email_echo(_data: Any, _va: Any, sent: Any) -> Any: return {"email": sent["email"]} @daemon_route(GET_ACME_LIST, bp) def list_certs_bp(): """GET /api/certs/list — List all managed ACME certificates.""" @daemon_route(GET_ACME_INFO, bp, rule="/") def cert_details(): """GET /api/certs/ — Get details for a specific certificate.""" @daemon_route(POST_ACME_VALIDATE, bp, body=_validate_body) def validate(): """POST /api/certs/validate — Run pre-flight checks for issuance.""" @daemon_route(POST_ACME_ISSUE, bp, rule="/issue/start", body=_issue_body) def issue_start(): """POST /api/certs/issue/start — Create a new certificate issuance request.""" @daemon_route( GET_ACME_ISSUE_STATUS, bp, rule="/issue/", params={"id": "request_id"} ) def issue_status(): """GET /api/certs/issue/ — Poll status of an issuance request.""" @daemon_route(POST_ACME_RENEW, bp, rule="//renew") def renew_bp(): """POST /api/certs//renew — Start an (async) certificate renewal.""" @daemon_route( GET_ACME_RENEW_STATUS, bp, rule="/renew/", params={"id": "request_id"} ) def renew_status(): """GET /api/certs/renew/ — Poll status of a certificate renewal.""" @daemon_route(DELETE_ACME_REMOVE, bp, rule="/", transform=void_transform) def remove_bp(): """DELETE /api/certs/ — Remove a certificate from ACME management.""" @daemon_route(POST_ACME_EMAIL, bp, body=_email_body, transform=_email_echo) def set_email_bp(): """POST /api/certs/email — Set the ACME account email address.""" @daemon_route(GET_ACME_ACCOUNT, bp) def account(): """GET /api/certs/account — Return ACME account information.""" @daemon_route(POST_ACME_ACCOUNT_REGISTER, bp, body=_register_body) def register_account(): """POST /api/certs/account/register — Register a new ACME account.""" @daemon_route(DELETE_ACME_ACCOUNT_DEACTIVATE, bp, rule="/account", body=NO_BODY) def deactivate_account(): """DELETE /api/certs/account — Deactivate the ACME account."""