Fix NPM SSL: preserve existing cert on update, find cert by domain
Three fixes: 1. When updating existing proxy host, preserve its certificate_id and SSL settings instead of resetting to 0 2. Search NPM certificates by domain if proxy host has no cert assigned (handles manually created certs) 3. Remove invalid 'nice_name' and 'dns_challenge' from LE cert request payload (caused 400 error on newer NPM versions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,32 @@ async def test_npm_connection(api_url: str, email: str, password: str) -> dict[s
|
|||||||
return {"ok": False, "message": f"Unexpected error: {exc}"}
|
return {"ok": False, "message": f"Unexpected error: {exc}"}
|
||||||
|
|
||||||
|
|
||||||
|
async def _find_cert_by_domain(
|
||||||
|
client: httpx.AsyncClient, api_url: str, headers: dict, domain: str
|
||||||
|
) -> int | None:
|
||||||
|
"""Find an existing SSL certificate by domain name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: httpx client (already authenticated).
|
||||||
|
api_url: NPM API base URL.
|
||||||
|
headers: Auth headers with Bearer token.
|
||||||
|
domain: Domain to search for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Certificate ID if found, None otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = await client.get(f"{api_url}/nginx/certificates", headers=headers)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
for cert in resp.json():
|
||||||
|
cert_domains = cert.get("domain_names", [])
|
||||||
|
if domain in cert_domains:
|
||||||
|
return cert.get("id")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("Could not search certificates: %s", exc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def _find_proxy_host_by_domain(
|
async def _find_proxy_host_by_domain(
|
||||||
client: httpx.AsyncClient, api_url: str, headers: dict, domain: str
|
client: httpx.AsyncClient, api_url: str, headers: dict, domain: str
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
@@ -211,6 +237,17 @@ async def create_proxy_host(
|
|||||||
if not proxy_id:
|
if not proxy_id:
|
||||||
return {"error": f"Domain {domain} already in use but could not find existing proxy host"}
|
return {"error": f"Domain {domain} already in use but could not find existing proxy host"}
|
||||||
logger.info("Found existing NPM proxy host for %s (id=%s), updating...", domain, proxy_id)
|
logger.info("Found existing NPM proxy host for %s (id=%s), updating...", domain, proxy_id)
|
||||||
|
|
||||||
|
# Preserve existing certificate_id and ssl settings
|
||||||
|
host_resp = await client.get(f"{api_url}/nginx/proxy-hosts/{proxy_id}", headers=headers)
|
||||||
|
if host_resp.status_code == 200:
|
||||||
|
existing = host_resp.json()
|
||||||
|
existing_cert = existing.get("certificate_id", 0)
|
||||||
|
if existing_cert and existing_cert > 0:
|
||||||
|
payload["certificate_id"] = existing_cert
|
||||||
|
payload["ssl_forced"] = existing.get("ssl_forced", True)
|
||||||
|
payload["hsts_enabled"] = existing.get("hsts_enabled", True)
|
||||||
|
|
||||||
update_resp = await client.put(
|
update_resp = await client.put(
|
||||||
f"{api_url}/nginx/proxy-hosts/{proxy_id}",
|
f"{api_url}/nginx/proxy-hosts/{proxy_id}",
|
||||||
json=payload,
|
json=payload,
|
||||||
@@ -275,9 +312,14 @@ async def _request_ssl(
|
|||||||
if host_resp.status_code == 200:
|
if host_resp.status_code == 200:
|
||||||
host_data = host_resp.json()
|
host_data = host_resp.json()
|
||||||
existing_cert_id = host_data.get("certificate_id", 0)
|
existing_cert_id = host_data.get("certificate_id", 0)
|
||||||
|
|
||||||
|
# If no cert on proxy host, search NPM for existing cert matching domain
|
||||||
|
if not existing_cert_id or existing_cert_id == 0:
|
||||||
|
existing_cert_id = await _find_cert_by_domain(client, api_url, headers, domain)
|
||||||
|
|
||||||
if existing_cert_id and existing_cert_id > 0:
|
if existing_cert_id and existing_cert_id > 0:
|
||||||
logger.info("Proxy host %s already has certificate (id=%s), ensuring SSL is enabled",
|
logger.info("Found certificate (id=%s) for %s, assigning to proxy host %s",
|
||||||
proxy_id, existing_cert_id)
|
existing_cert_id, domain, proxy_id)
|
||||||
ssl_update = {
|
ssl_update = {
|
||||||
"certificate_id": existing_cert_id,
|
"certificate_id": existing_cert_id,
|
||||||
"ssl_forced": True,
|
"ssl_forced": True,
|
||||||
@@ -290,19 +332,20 @@ async def _request_ssl(
|
|||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
if update_resp.status_code in (200, 201):
|
if update_resp.status_code in (200, 201):
|
||||||
logger.info("SSL enabled on existing proxy host %s (cert_id=%s)", proxy_id, existing_cert_id)
|
logger.info("SSL enabled on proxy host %s (cert_id=%s)", proxy_id, existing_cert_id)
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning("Failed to assign cert %s to proxy host %s: HTTP %s",
|
||||||
|
existing_cert_id, proxy_id, update_resp.status_code)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Could not check existing cert for proxy host %s: %s", proxy_id, exc)
|
logger.warning("Could not check existing cert for proxy host %s: %s", proxy_id, exc)
|
||||||
|
|
||||||
ssl_payload = {
|
ssl_payload = {
|
||||||
"domain_names": [domain],
|
"domain_names": [domain],
|
||||||
"provider": "letsencrypt",
|
"provider": "letsencrypt",
|
||||||
"nice_name": domain,
|
|
||||||
"meta": {
|
"meta": {
|
||||||
"letsencrypt_agree": True,
|
"letsencrypt_agree": True,
|
||||||
"letsencrypt_email": admin_email,
|
"letsencrypt_email": admin_email,
|
||||||
"dns_challenge": False,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user