🚧 Add CA and server cert management

This commit is contained in:
Jonas Bengtsson
2026-09-19 13:35:49 +02:00
parent 45de68f0b7
commit 111d6cc390
20 changed files with 1317 additions and 34 deletions
+37 -15
View File
@@ -12,7 +12,7 @@ from dataclasses import dataclass, field
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models import BrokerSettings, ListenerConfig
from app.models import BrokerSettings, Certificate, ListenerConfig
_BOOL = {True: "true", False: "false"}
@@ -34,7 +34,9 @@ def _global_section(db: Session, warnings: list[str]) -> list[str]:
return lines
def _listener_block(listener: ListenerConfig, warnings: list[str]) -> list[str]:
def _listener_block(
listener: ListenerConfig, db: Session, warnings: list[str]
) -> list[str]:
lines: list[str] = []
if not listener.enabled:
return lines
@@ -51,18 +53,38 @@ def _listener_block(listener: ListenerConfig, warnings: list[str]) -> list[str]:
if listener.max_connections != -1:
lines.append(f"max_connections {listener.max_connections}")
if listener.tls_enabled:
# TLS blocks only become valid once PKI ships (Release 2); we
# generate them now so the model is forward-compatible, but warn.
warnings.append(
f"listener '{listener.name}': TLS requested but PKI is not "
"available in Release 1; certfile/keyfile not set"
)
if listener.tls_min_version:
lines.append(f"tls_version {listener.tls_min_version}")
if listener.require_certificate:
lines.append("require_certificate true")
if listener.use_identity_as_username:
lines.append("use_identity_as_username true")
# Real PKI material (spec section 30): when a certificate is bound,
# the deploy pipeline publishes cert/key/CA files under
# /mosquitto/certs/<id>.crt|key BEFORE the config referencing
# them is activated, so validation sees real files.
cert = db.get(Certificate, listener.cert_id) if listener.cert_id else None
ca = db.get(Certificate, cert.parent_id) if cert and cert.parent_id else None
if cert is not None:
cert_dir = "/mosquitto/certs"
lines.append(f"certfile {cert_dir}/{cert.id}.crt")
lines.append(f"keyfile {cert_dir}/{cert.id}.key")
if ca is not None:
lines.append(f"cafile {cert_dir}/{ca.id}.crt")
if listener.tls_min_version:
lines.append(f"tls_version {listener.tls_min_version}")
if listener.require_certificate:
lines.append("require_certificate true")
if listener.use_identity_as_username:
lines.append("use_identity_as_username true")
else:
# TLS requested but no certificate bound: forward-compatible
# block without certfile/keyfile, with an actionable warning.
warnings.append(
f"listener '{listener.name}': TLS requested but no certificate "
"is bound; certfile/keyfile not set. Issue a server certificate "
"and bind it to this listener."
)
if listener.tls_min_version:
lines.append(f"tls_version {listener.tls_min_version}")
if listener.require_certificate:
lines.append("require_certificate true")
if listener.use_identity_as_username:
lines.append("use_identity_as_username true")
return lines
@@ -101,7 +123,7 @@ def generate_config(db: Session) -> GenerationResult:
if enabled:
lines.append("# --- Listeners ---")
for listener in enabled:
block = _listener_block(listener, warnings)
block = _listener_block(listener, db, warnings)
if block:
lines.append(f"# listener: {listener.name}")
lines.extend(block)