Certificate store¶
LOOPZE keeps a central, file-backed certificate catalogue that every
network node can reference by ID instead of embedding PEM inline. The
store is part of the same encrypted file that holds node credentials
(data/credentials.json, AES-256-GCM, master key in data/loopze.key),
so there is one secret to back up and one master key to rotate.
This page is the operator-facing reference: what you can put in the store, how to keep it healthy, and how to rotate certificates without editing flows by hand.
When to use the store¶
| Scenario | Recommendation |
|---|---|
| One flow, throwaway dev cert | Inline PEM in the node's tls block. |
| Many flows hitting the same broker / API | Stored reference — one entry, many consumers. |
| Cert is rotated by an external tool (cert-manager, Let's Encrypt, K8s secret), shared across configs | Stored entry, Source: file — one named entry to manage; many configs reference it. |
| Cert is rotated externally, only one config uses it | *File path directly in the tls block — no store entry needed; same hot-reload semantics. |
| Cert lives only inside LOOPZE | Inline source — PEM is encrypted at rest. |
The store and inline mode coexist; you can pick differently per node.
Both file-path approaches re-read on every connection init. A stored
Source: fileentry and a directcaBundleFile/clientCertFile/clientKeyFileon thetlsblock both work the same at runtime. The difference is cataloguing — stored entries get a name, fingerprint and expiry in the cert manager UI, plus they can be referenced by ID from many configs at once.
Entry shape¶
Each entry has:
- ID — a slug matching
^[a-z0-9][a-z0-9_-]{0,63}$. Immutable after creation; pick a stable name (internal-root-ca,edge-client-2026). - Type — one of
ca-bundle,client-pair,server-pair. Nodes type-check on resolution: atls.caBundleRefpointing at aclient-pairis a hard deploy error. - Source —
inlineorfile. - Material — for
inline, PEM strings; forfile, absolute paths on disk. - Notes — free-form, surfaces in the UI list.
Fingerprint, subject, issuer and notAfter are derived from the
leaf certificate when the entry is saved and shown in the UI.
Adding a certificate¶
Through the UI¶
- Open Certificates from the header (Editor role or higher).
- Click + New certificate.
- Pick the type, choose a source, fill in the material.
- Click Validate to preview the parsed metadata before saving.
- Create.
Through the API¶
# Inline CA bundle
curl -X POST https://loopze.example.com/api/v1/certs \
-H 'Content-Type: application/json' \
--cookie loopze_session=… \
-d '{
"id": "internal-root-ca",
"name": "Internal Root CA",
"type": "ca-bundle",
"source": "inline",
"certPem": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----"
}'
# File-sourced client pair
curl -X POST https://loopze.example.com/api/v1/certs \
-H 'Content-Type: application/json' \
--cookie loopze_session=… \
-d '{
"id": "edge-client-2026",
"name": "Edge gateway client",
"type": "client-pair",
"source": "file",
"certPath": "/etc/loopze/certs/edge.crt",
"keyPath": "/etc/loopze/certs/edge.key"
}'
The full route table:
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/certs |
Viewer+ |
| GET | /api/v1/certs/{id} |
Viewer+ |
| POST | /api/v1/certs |
Editor+ |
| PUT | /api/v1/certs/{id} |
Editor+ |
| DELETE | /api/v1/certs/{id} |
Editor+ (409 when referenced) |
| POST | /api/v1/certs/validate |
Editor+ (probe only — does not persist) |
Responses for GET never include PEM material or private keys —
only file paths and parsed metadata. If you need the original PEM
back, you have to re-upload it.
Rotating certificates¶
File source¶
The cleanest path. The store only persists the file path; the
contents are read fresh on every BuildTLSConfig call (which
happens at every node deploy / connection init). External tools
that swap the file in place — cert-manager, Let's Encrypt, mounted
Kubernetes secrets — pick up automatically:
# Drop the new cert in place atomically; long-lived TLS connections
# stay open until they redial, then use the new material.
mv /etc/loopze/certs/edge.crt.new /etc/loopze/certs/edge.crt
mv /etc/loopze/certs/edge.key.new /etc/loopze/certs/edge.key
No restart, no API call, no flow edit.
Long-lived connections survive a rotation
The cert file is re-read at connection init, not on every write. A connection that was opened before the rotation keeps the old TLS material until it closes (or the flow is re-deployed). For aggressive rotation, schedule a periodic redeploy or close the connection from the peer side.
Inline source¶
Inline material lives encrypted inside credentials.json. Rotate by
issuing a PUT against the entry with the new PEM, or use the UI's
Edit button. Connections established before the update keep the
old cert until they redial.
Tooling examples¶
Mount the certificate as a Secret and point the LOOPZE entry
at the projected paths. cert-manager rotates the Secret
contents in place.
Point the entry at the live symlinks, not at the rotating
archive files:
certPath: /etc/letsencrypt/live/example.com/fullchain.pem
keyPath: /etc/letsencrypt/live/example.com/privkey.pem
Certbot keeps the symlinks pointing at the latest version.
Use any deployment tool to swap the files atomically (e.g. write
a .new, chmod 0600, then mv). LOOPZE does not enforce a
base directory — the only requirement is that the path is
absolute.
Deletion safety net¶
DELETE /api/v1/certs/{id} is rejected with 409 Conflict when the
saved workspace still references the cert. The response body lists
each reference so you can untangle:
{
"error": "Conflict",
"message": "cert is still referenced by deployed nodes",
"references": [
{
"flowId": "ingest-line-1",
"nodeId": "tcp-out-3",
"nodeType": "tcp-out",
"field": "tls.caBundleRef"
}
]
}
The UI surfaces the same list before letting you confirm.
File contents on disk are never touched by DELETE — only the store
entry is removed.
OPC UA migration on upgrade¶
Existing opcua-server config nodes that used clientCertFile /
clientKeyFile are auto-migrated on first boot after upgrade:
- A new
client-pairentry is created with deterministic IDopcua-<configNodeID>andSource: file, pointing at the same paths. - The legacy properties are stripped from the config so the deprecation WARN does not fire on every subsequent deploy.
- Idempotent: a half-finished migration converges on the next boot.
If you prefer a different naming scheme, run the migration first,
then PUT the entries under more meaningful IDs and update the
certRef on the affected configs.
Operational notes¶
- One key, one file.
data/credentials.jsonanddata/loopze.keycarry the entire store plus node credentials. Back up both, but store them separately — the file without the key is useless and the key without the file lets nothing through. - File permissions.
0600on both. The runtime enforces this on write; check before backups too. - Logging hygiene. PEM material and private keys are never logged. File paths are; if a path is sensitive in your environment (rare), avoid putting it on the filesystem.
- Expired certs are a WARN, not a fail. Test setups
legitimately use expired certs. If you want a hard refusal, add
monitoring on
notAfterfrom the UI's listing endpoint.
Out of scope¶
The store deliberately does not:
- generate certificates (no "Create CA" button — bring your own PKI);
- speak OCSP / CRL;
- rotate the master key on a schedule;
- integrate with the OS keychain.
These are tracked as separate follow-ups.