Certificates and encryption
kcore uses mutual TLS (mTLS) for all management-plane communication. Disk encryption with LUKS2 protects data at rest.
PKI hierarchy
- Root CA — created by
kctl create cluster - Sub-CA — lives on the controller, signs node leaf certificates
- Controller cert — identifies the controller to nodes and clients
- kctl client cert — authenticates CLI operations
- Per-node certs — generated during
kctl node install
mTLS bootstrap
kctl create cluster generates the full PKI (root CA, sub-CA, controller cert, kctl client cert). kctl node install pushes the node certificate, private key, and CA chain to the target node. Controller nodes also receive the sub-CA key pair.
Automated certificate rotation
The controller checks its certificate inventory every hour and rotates any node certificate that has entered its renewal window — by default the last 30 days, or the last quarter of the certificate's lifetime, whichever comes first. Certificates are signed for 365 days.
Rotation is driven by the controller but the key material is not. The controller calls RotateNodeCert on the node, the node generates a fresh keypair and sends a CSR to SignNodeCsr, and the sub-CA returns a signed chain. Private keys never leave the node.
The node writes the new certificate and key atomically, then reloads TLS without restarting the process. Established gRPC connections drop and callers reconnect; in-memory state is kept. If the new certificate fails verification, the node rolls back and carries on serving with its old, still-valid certificate.
Manual rotation
# Rotate the sub-CA
kctl rotate sub-ca --certs-dir ~/.kcore/<context>/
# Re-sign the controller cert and trigger TLS reload
kctl rotate certs --controller <host:port> --certs-dir ~/.kcore/<context>/
# Rotate one node, or every approved node, ahead of schedule
kctl rotate node-certs --node <node-id>
kctl rotate node-certs --all
Rotation and revocation stay kctl commands. Provider 0.5.0 can issue an operator certificate.
resource "kcore_operator" "ci" {
name = "ci"
roles = ["vm-admin"]
issue_cert = true
}
rotate sub-ca generates a new sub-CA and pushes it to the controller. rotate certs re-signs the controller certificate and triggers ReloadTls on the controller. rotate node-certs runs the CSR flow above immediately instead of waiting for the renewal window.
Revocation: CRL and OCSP
Every certificate the controller signs is recorded in an inventory: serial, subject, node, validity window, SHA-256 fingerprint, and status. Revoking one marks it in that inventory and publishes it through both a CRL and an OCSP responder.
# Revoke by serial, subject CN, or node
kctl revoke cert --serial <hex> --reason key-compromise
kctl revoke cert --node <node-id>
# Fetch the current CRL
kctl get crl -o crl.der
kctl node delete <node-id> revokes every certificate issued to that node before the node row is removed. The reason code is RFC 5280 reason 5, cessation of operation. Delete refuses while the node still hosts a VM, a workload, or a network. Revoking with kctl revoke cert --node leaves the node in the cluster.
The CRL is signed by the sub-CA and carries thisUpdate and nextUpdate; it is valid for 24 hours and regenerated when the revocation set changes or nextUpdate gets close. --reason accepts the RFC 5280 reason codes, such as key-compromise, superseded, or privilege-withdrawn.
Revocation is enforced above TLS, not inside the handshake: a revoked peer completes the TLS handshake and is then rejected with PermissionDenied before any RPC handler runs. No call succeeds with a revoked certificate. Checks run against a snapshot that refreshes every 60 seconds. When that snapshot goes stale the controller soft-fails by default, so a revocation outage cannot lock every node out of the cluster; set revocation.failMode to hard-fail if you would rather reject peers than serve on stale data.
OCSP stapling is not supported. The controller runs an OCSP responder and nodes query it directly, as a fallback when CRL data is stale — no stapled response is attached to a kcore handshake.
PKI endpoints
The controller serves the CRL and OCSP over plain HTTP on pki.httpListenAddr, by default 0.0.0.0:9092. These objects are signed, so they do not need transport security, and a node whose certificate has just expired can still fetch what it needs to recover.
GET /pki/crl.derandGET /pki/crl.pem— the current CRLPOST /pki/ocspandGET /pki/ocsp/<base64>— OCSP responder, answering good, revoked, or unknownGET /pki/healthz— responder health
Set pki.httpEnabled: false to turn the listener off. Nodes fall back to the gRPC GetCrl call either way.
Inspecting certificates
kctl get nodes shows cert_expiry_days per node. kctl get certificates lists the controller's inventory, filterable with --node, --status, and --expiring-within-days. kctl get pki-status summarises rotation and revocation health. The compliance report includes a Certificate Lifecycle section with expiring and unknown certificate counts.
LUKS2 disk encryption
LUKS2 encryption is mandatory for the OS disk on every node.
- TPM2 path —
systemd-cryptenrollseals the LUKS key to the TPM2 device. - Key-file path — passphrase-based unlock using a key file.
- Recovery key — stored at
/etc/kcore/recovery/luks-recovery-key.txt(root-only, mode 0400).
LUKS reporting
The node-agent detects the LUKS unlock method at registration: tpm2 if /dev/mapper/cryptroot exists and a TPM device is present, key-file otherwise. kctl get compliance-report shows the cluster-wide LUKS breakdown by method.
TLS configuration
- Production — mTLS required (default). All gRPC and API traffic is encrypted and mutually authenticated.
- Development —
kctl --insecuredisables TLS for local testing over plain HTTP.
Never use --insecure in production. Disabling mTLS removes authentication and encryption from the management plane.