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:
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user