Internal / Gossip API
These endpoints carry inter-node CRDT replication traffic. They are served on the same port as the public OAuth2/OIDC endpoints and protected at the application layer — port-level firewall rules cannot isolate them without also blocking public clients.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/gossip/sync | Accept an incoming CRDT gossip push from a peer node. The body is a CMS SignedData(EnvelopedData) blob (ECDSA P-256 outer signature, ML-KEM-768 inner encryption). The receiver verifies the signature, decrypts, applies admission filters, merges the CRDT, and replies with its own state (delta or full, based on the request_delta_since field in the inbound envelope) in the same CMS format. |
GET | /api/gossip/kem-info | Return this node’s ML-KEM-768 public key (base64url SPKI DER) and node_id. Unauthenticated. Returns 404 Not Found with {"registered": false} when this node has no CRDT entry yet (i.e. the node has not yet completed its first gossip round); callers should treat 404 as “not yet registered”. |
GET | /api/gossip/wrapping-key | Return the cluster AEAD wrapping key sealed to the requester’s ML-KEM-768 public key (SignedData(EnvelopedData) DER, application/pkcs7-mime). The requester identifies itself via the X-Ahdapa-Node-Id header and must have a KEM key already in the CRDT. Unauthenticated at the HTTP level; confidentiality is ensured by the ML-KEM-768 encryption — the blob is useless without the requester’s private key. |
POST | /api/gossip/register-kem | Kerberos-authenticated self-registration of both the ML-KEM-768 public key and the ECDSA P-256 gossip signing public key. An IPA-enrolled peer presents Authorization: Negotiate <kerberos-token> (Kerberos AP-REQ for the local HTTP service) and a JSON body {"node_id":"<hostname>","kem_public_key_der":"<base64url-SPKI>","gossip_signing_pub_key_der":"<base64url-SPKI>"}. All three fields are required; missing or empty fields return 400. The server validates that the authenticated principal is HTTP/<hostname>@<REALM>, that node_id matches <hostname>, and (when gossip.kerberos_realm is set) that the principal’s realm matches. Both keys are stored in the CRDT in a three-case match: insert-fresh, upsert-signing-key-only, or no-op (both already present). Returns 503 when the GSSAPI server credential is unavailable or when the DB persist fails; 401 with WWW-Authenticate: Negotiate when no token is presented or invalid; 400 when required fields are missing; 403 on principal or allowlist rejection; and 200 OK on success. |
GET | /api/gossip/stats | Return runtime gossip statistics for this node. Unauthenticated. Returns a JSON object with node_id, crdt_generation, per-collection live counts under counts, configured and topology-discovered peers, active_signing_kid, kem_enrolled, gossip_signing_enrolled, and a gossip sub-object with started_at, rounds_completed, last_round_at, peer_last_sync (map of peer node_id → last inbound sync unix timestamp), persist_errors, and wrapping_key_pull_errors. See Gossip Protocol — Node statistics endpoint for the full response schema. |
Access control
Gossip endpoints are protected by two independent mechanisms at the application layer. A rogue client that can reach the server port gains nothing from these endpoints without the corresponding cryptographic keys.
CMS authentication and encryption (/api/gossip/sync, /api/gossip/wrapping-key)
Every gossip sync payload is a CMS SignedData(EnvelopedData) structure:
- The outer ECDSA P-256 signature is verified against the sender’s pinned
gossip signing key stored in the CRDT. A node whose key is not pinned
receives
401 Unauthorizedregardless of the payload contents. - The inner ML-KEM-768 encryption is addressed to the receiving node’s public key. The encrypted payload is opaque to any party that does not hold the private key.
For /api/gossip/wrapping-key, the requester’s X-Ahdapa-Node-Id must match
a node_id in the admission allowlist (see below); the response is itself an
EnvelopedData blob encrypted to the requester’s ML-KEM-768 key, so the
cluster wrapping key is never transmitted in the clear.
Node admission allowlist (allowed_node_ids)
The [gossip] section of the configuration controls which node_ids are
permitted to exchange CRDT state:
[gossip]
# Static allowlist. Only node_ids listed here may participate.
allowed_node_ids = ["ipa1.example.com", "ipa2.example.com"]
When ipa_topology = true, the allowlist is extended automatically with all
replica hostnames discovered from the IPA replication topology, and updated
every ipa_topology_interval_secs seconds. Entries from the static list and
the topology-derived list are merged; the union fails closed — a node_id absent
from both is denied.
Kerberos authentication (/api/gossip/register-kem)
Self-registration requires a valid Kerberos AP-REQ for the local HTTP
service. Only HTTP/<hostname>@<REALM> service principals are accepted; user
principals are rejected. When gossip.kerberos_realm is set, cross-realm
principals are also rejected. The authenticated hostname is validated against
the submitted node_id and against the allowlist before any key material is
stored.
See Multi-node Cluster and Gossip Protocol for operational details.