Publish CRL for the unprivileged OpenVPN user so crl_verify works

- ovpmon-helper: new publish-crl command (validated copy of pki/crl.pem to
  /etc/openvpn/crl.pem, root:root 644); crl-verify allowlisted only for
  that path; CRL is refreshed on service start/restart.
- Profiler: publish after gen-crl (init/revoke) and on server/configure;
  generator renders the published path when running unprivileged.
- doas rule for publish-crl; docs and helper copy updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
iclaoudezinandClaude Sonnet 5.5 committed 2026-09-30 12:38:30 +00:00
1 parent 6f9e800779
commit 9ffdbfa259
8 files changed
+103 -9

No files matched your search

+4
View File
@@ -25,6 +25,10 @@ def generate_server_config(db: Session, output_path: str = "server.conf"):
file_dh_path = os.path.join(PKI_DIR, "dh.pem")
file_ta_path = os.path.join(PKI_DIR, "ta.key")
file_crl_path = os.path.join(PKI_DIR, "crl.pem")
from .process import is_container
if os.geteuid() != 0 and not is_container():
# unprivileged API: OpenVPN (nobody) cannot enter the 0700 pki dir, use the copy published by the helper
file_crl_path = "/etc/openvpn/crl.pem"
# Render template
ctx = dict(
+13 -1
View File
@@ -146,7 +146,8 @@ def init_pki(db: Session):
# Gen CRL
_run_easyrsa(["gen-crl"], env)
_publish_crl()
return "PKI Initialized"
def clear_pki(db: Session):
@@ -183,9 +184,20 @@ def build_client(username: str, db: Session):
_run_easyrsa(["build-client-full", username, "nopass"], env)
return True
def _publish_crl():
"""Make the fresh CRL readable by OpenVPN when the API runs unprivileged (see ovpmon-helper)."""
from .process import publish_crl
ok, msg = publish_crl()
if not ok:
logger.error(f"CRL was generated but could not be published: {msg}")
return ok
def revoke_client(username: str, db: Session):
validate_username(username)
env = _get_easyrsa_env(db)
_run_easyrsa(["revoke", username], env)
_run_easyrsa(["gen-crl"], env)
if not _publish_crl():
raise RuntimeError("Certificate revoked, but the CRL could not be published to OpenVPN")
return True
+13
View File
@@ -48,6 +48,19 @@ def install_config():
return False, msg
def publish_crl():
"""Publish pki/crl.pem to a root-owned location readable by the OpenVPN user (nobody).
No-op when running as root/in a container (OpenVPN reads the PKI directly). Returns (ok, message)."""
if is_container() or os.geteuid() == 0:
return True, "not required"
rc, data, raw = _run_helper(["publish-crl"])
if rc == 0 and data and data.get("status") == "ok":
return True, "CRL published"
msg = (data or {}).get("error") or raw or "helper failed"
logger.error(f"[PROCESS] publish-crl failed: {msg}")
return False, msg
def control_service(action: str):
"""
Action: start, stop, restart