Fix list_certs to use PEM files + openssl instead of acme.sh

acme.sh --list failed in uv/systemd context. Now scans certs directory
directly and uses openssl for cert details + HAProxy show ssl cert API
for loaded status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-15 14:40:11 +09:00
parent 97af0b49e2
commit 5c0af11735

View File

@@ -110,53 +110,68 @@ def restore_certificates() -> int:
def _haproxy_list_certs_impl() -> str:
"""Implementation of haproxy_list_certs."""
try:
result = run_command([ACME_SH, "--list"], timeout=SUBPROCESS_TIMEOUT)
if result.returncode != 0:
return f"Error: {result.stderr}"
lines = result.stdout.strip().split("\n")
if len(lines) <= 1:
return "No certificates found"
# Get loaded certs from HAProxy Runtime API
try:
haproxy_certs = haproxy_cmd("show ssl cert")
except HaproxyError as e:
logger.debug("Could not get HAProxy certs: %s", e)
haproxy_certs = ""
# Scan PEM files in certs directory
pem_files = sorted(
f for f in os.listdir(CERTS_DIR)
if f.endswith(".pem")
) if os.path.isdir(CERTS_DIR) else []
if not pem_files:
return "No certificates found"
certs = []
for line in lines[1:]:
parts = line.split()
if len(parts) >= 4:
domain = parts[0]
ca = "unknown"
created = "unknown"
renew = "unknown"
for pem_file in pem_files:
domain = pem_file[:-4] # strip .pem
host_path = f"{CERTS_DIR}/{pem_file}"
_, container_path = get_pem_paths(domain)
for part in parts:
if "Google" in part or "LetsEncrypt" in part or "ZeroSSL" in part:
ca = part
elif part.endswith("Z") and "T" in part:
if created == "unknown":
created = part
else:
renew = part
# Get cert details via openssl
issuer = "unknown"
not_after = "unknown"
sans = ""
try:
result = run_command(
["openssl", "x509", "-in", host_path, "-noout",
"-issuer", "-enddate", "-ext", "subjectAltName"],
timeout=SUBPROCESS_TIMEOUT,
)
if result.returncode == 0:
for line in result.stdout.split("\n"):
line = line.strip()
if line.startswith("issuer="):
# Extract CN or O from issuer
for field in line.split(","):
field = field.strip()
if field.startswith("CN =") or field.startswith("CN="):
issuer = field.split("=", 1)[1].strip()
elif field.startswith("O =") or field.startswith("O="):
if issuer == "unknown":
issuer = field.split("=", 1)[1].strip()
elif line.startswith("notAfter="):
not_after = line.split("=", 1)[1].strip()
elif "DNS:" in line:
sans = ", ".join(
s.strip().replace("DNS:", "")
for s in line.split(",")
if "DNS:" in s
)
except (TimeoutError, OSError):
pass
host_path, container_path = get_pem_paths(domain)
if container_path in haproxy_certs:
status = "loaded"
elif _file_exists(host_path):
status = "file exists (not loaded)"
else:
status = "not deployed"
certs.append(f"{domain} ({ca})\n Created: {created}\n Renew: {renew}\n Status: {status}")
status = "loaded" if container_path in haproxy_certs else "file only"
san_info = f"\n SANs: {sans}" if sans else ""
certs.append(
f"{domain} ({issuer})\n Expires: {not_after}{san_info}\n Status: {status}"
)
return "\n\n".join(certs) if certs else "No certificates found"
except (TimeoutError, subprocess.TimeoutExpired):
return "Error: Command timed out"
except FileNotFoundError:
return "Error: acme.sh not found"
except OSError as e:
logger.error("Error listing certificates: %s", e)
return f"Error: {e}"