Configure the TLS certificates
This page sets up the certificates of the Navin Platform: the public certificate of the API and the portal, and the
internal certificates of the data and security services. In production the API refuses a database URL without TLS,
sends HSTS and requires rediss:// for Redis (Production rules).
Kubernetes with cert-manager
Issuers
deploy/cert-manager/cluster-issuers.yaml declares the issuers, applied by the infra layer:
| ClusterIssuer | Role |
|---|---|
eai-selfsigned-bootstrap | Signs the internal root CA once |
eai-internal-ca | Internal CA (secret eai-root-ca, ECDSA P-384, 10 years): certificates of the data and security services |
eai-openbao-pki | PKI engine of OpenBao (https://openbao.openbao.svc:8200) |
letsencrypt-prod | ACME issuer for the public certificate |
Steps
-
Choose the issuer of the public certificate.
deploy/cert-manager/certificates.yamlissuesapi-gateway-tls(namespaceapisix, names<control-plane-host>and<portal-host>) withletsencrypt-prod. On a site without ACME, pointissuerRefto the issuer of your company PKI, and set the real host names.Expected result:
kubectl -n apisix get certificate api-gateway-tlsisReady=True.If it fails:
kubectl -n apisix describe certificate api-gateway-tlsthenkubectl get certificaterequest -Ashow the refusal of the issuer. -
Check the internal certificates.
deploy/cert-manager/platform-services-certificates.yamlissues, fromeai-internal-ca,nats-tls,qdrant-tls,redis-tls,temporal-tls,keycloak-tls,opa-tls,openfga-tls,clickhouse-tls,redis-opik-tls,opensearch-tls,opensearch-admin-tls,jaeger-tls,otel-collector-tlsandopenbao-tls. PostgreSQL uses the CA of CloudNativePG (secreteai-postgres-ca).kubectl get certificate -AExpected result: every certificate
Ready=True. -
Distribute the CA. External Secrets copies
ca.crt(nevertls.key) ofeai-root-cato the namespaces that need it (deploy/external-secrets/secrets/ca-stores.yaml). The Control Plane database URL points to the mounted CA:sslmode=verify-full&sslrootcert=/run/secrets/postgres-ca/ca.crt. -
Portal ingress.
portal.ingress.tlsSecretName(defaultportal-tls) is issued byportal.ingress.clusterIssuer(defaulteai-internal-ca); set your public issuer for a portal reached from the workstations. The ingress forces HTTPS, TLS 1.2 and 1.3, and HSTS.
The hops between chart workloads (APISIX to Control Plane, Control Plane to LiteLLM and Opik) carry no application certificate: Cilium encrypts them node to node with WireGuard and authenticates the Control Plane and LiteLLM hops with SPIFFE mutual authentication.
Servers installed from packages
The services of the navin-platform package listen in plain HTTP on 8080 and 3300: put a TLS reverse proxy of the
bank in front of them.
-
Issue a certificate for the two public names (for example
<control-plane-host>and<navin-host>) from the PKI of the bank. -
Configure the proxy to forward
<control-plane-url>tohttp://<host>:8080andhttps://<navin-host>tohttp://<host>:3300, with the WebSocket upgrade allowed on/v1/realtime/. -
Set the public URLs in
/etc/navin-platform/platform.env:NAVIN_PORTAL_API_URL='<control-plane-url>'EAI_CORS_ORIGINS='https://<navin-host>'EAI_ALLOWED_HOSTS='<control-plane-host>'EAI_TRUSTED_PROXIES='<proxy-address>/32'EAI_HSTS_ENABLED='true'<proxy-address>: address of the reverse proxy, the only peer allowed to setX-Forwarded-For. -
Use TLS towards the dependencies:
sslmode=verify-fullinEAI_DATABASE_URL,rediss://withEAI_REDIS_TLS_CA_FILE,https://for OpenBao, Keycloak and the file scanning engines (EAI_FILE_SCAN_CA_FILE). -
Restart:
sudo systemctl restart navin-platform.target.
Verify
curl -fsSv <control-plane-url>/healthz 2>&1 | grep -E 'SSL connection|strict-transport-security'
Expected output: a TLS 1.2 or 1.3 connection and the header strict-transport-security.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
EAI_DATABASE_URL must use sslmode=require, verify-ca or verify-full | Database URL without TLS in production | Add sslmode=verify-full and the CA |
| Browser rejects the portal certificate | Issued by the internal CA | Use the public issuer, or deploy the internal CA on the workstations |
400 Bad Request from the API behind the proxy | Host not in EAI_ALLOWED_HOSTS | Add the public host name |
| Client IP of every request is the proxy | Proxy not in EAI_TRUSTED_PROXIES | Add its address |