Skip to content

[compliance] Backlog — Cluster clock-skew checks (bootstrap + cert-load) #77

Description

@perki

Tracked in the compliance matrix as CLOCK-SKEW-CLUSTER-CHECKS.


Backlog — Cluster clock-skew checks (bootstrap + cert-load)

Filed: 2026-05-20 (from the compliance-matrix implementer-
perspective gap-probing session — Q11 on time synchronization
across cores).

Problem

Pryv core processes use machine wall-clock for every timestamp
(audit row time, access expiry, LE cert renewal trigger, TLS
handshake validity-window checks). Clock synchronization between
machines is the operator's job — chronyd / ntpd on the host —
and iso-27001.A.8.17 (Clock synchronization) is correctly
classified out-of-scope in the matrix because Pryv does no
synchronization itself.

But two failure modes today are silent:

  1. A core boots / joins a cluster with a badly skewed clock.
    The bootstrap bundle flow (bin/bootstrap.js init
    bin/master.js --bootstrap) brings up a new core that joins the
    Raft cluster + immediately starts serving traffic with its
    inherited config. If the joining machine's clock is wrong (NTP
    not yet converged, hardware-clock-only boot, container drift),
    nothing notices. Audit rows from the new core land with skewed
    timestamps + the new core's TLS stack may reject the materialized
    cert (forward skew past notAfter) or refuse to load a freshly
    issued one (backward skew before notBefore).

  2. A core hot-swaps a freshly-rotated cert without validating
    its own clock against the cert's window.
    The acme:rotate
    IPC fanout pushes the new cert to every worker; each calls
    https.Server.setSecureContext. If the local clock is backward-
    skewed before the new cert's notBefore, the TLS stack starts
    serving a cert it considers not-yet-valid → every handshake on
    that core fails. If forward-skewed past notAfter of the
    previous cert, the previous cert was already broken anyway
    (this is the worse pre-existing state). Either way, an explicit
    pre-load validity check would surface the misalignment in logs

    • skip the swap rather than break in production.

Direction

Two small additions, both in components/business/src/:

1. Bootstrap-join clock-skew check

In applyBundle.ts (or a separate step in bin/bootstrap.js --bootstrap), after the joining core has Raft connectivity but
before announcing readiness to the orchestrator (ack to
POST /system/admin/cores/ack):

GET <issuer-core>/system/admin/now            # returns { serverTime }
                                              # Note: requires a new admin endpoint
                                              # or reuse of the existing /service/info
                                              # which already carries serverTime.
compare to Date.now()
if |delta| > skewThresholdSec (default 30s):
  log error + refuse to ack + exit 1

Operator runs the boot only after fixing the clock. Threshold is
operator-configurable: tight (10s) for strict-audit deployments,
loose (300s) for development.

2. Pre-cert-load validity check

In the worker-side acme:rotate IPC handler (or in
CertRenewer's materialization path on non-renewer cores) — wrap
the setSecureContext call with:

const cert = x509.X509Certificate(newPem);
const now = Date.now();
if (cert.validFromDate.getTime() > now + skewThresholdMs) {
  logger.error(`[acme:rotate] refusing to load cert: notBefore is ${cert.validFromDate} > now ${new Date(now)}; clock probably backward-skewed`);
  return;   // keep previous cert loaded
}
if (cert.validToDate.getTime() < now) {
  logger.error(`[acme:rotate] refusing to load cert: notAfter is ${cert.validToDate} < now ${new Date(now)}; clock probably forward-skewed past renewal`);
  return;
}
setSecureContext(...);

Same skewThresholdMs operator config (default 30s).

Constraints

  • Per-core / intra-core only. No cross-core clock agreement
    required (Q11 confirmed: cores are independent — user is
    core-affine; PlatformDB is index, not router; cores never need
    to agree on cert validity).
  • Operator setting. Both checks gated by
    cluster.clockSkewThresholdSec (default 30). Setting to 0
    disables.
  • No NTP enforcement. Pryv does not run an NTP client. The
    checks detect misalignment; the operator fixes it.
  • Composable with serverTime API helper. Pryv already
    returns meta.serverTime in every API response (Unix timestamp
    seconds; see
    components/api-server/src/methods/helpers/setCommonMeta.ts)
    for client-side skew detection. The bootstrap check uses the
    same primitive on the server side, peer-to-peer.

Status

Not started. Backlog.

Matrix impact when shipped

Scope Row Today After shipping
iso-27001 A.8.17 Clock synchronization Out of scope F: Awareness | Low (Pryv contributes bootstrap-time + cert-load skew detection + serverTime client helper; operator still runs NTP)
hipaa-security 164.312(c)(1) Integrity F: Primitive | Med unchanged tier; cert-load check tightens the integrity story
iso-27001 A.8.15 Logging Implemented | High unchanged; audit timestamps still depend on operator NTP

Related

  • Q11 in compliance-matrix/docs/implementer-faq.md.
  • Sibling backlog: AUDIT-LOG-CHAINING.md — the chained-audit-log
    proposal explicitly depends on per-core monotonic time
    (cross-core ordering is not meaningful per Q11). Notes added
    to that backlog + matrix proposal to surface the dependency.
  • pryv-primitives.md audit + account-backup-tool entries cite
    serverTime as the client-side skew-detection helper.
  • Architecture note:
    compliance-matrix/context/core-affinity-architecture.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    compliance-matrixTracked from compliance-matrix (pryv/compliance-matrix)

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions