Daevix Docs

Controlplane data minimization

View as Markdown Edit on Tangled
This page is the engineering document, published as-is from the repository. It is reconciled with the code on every change, so it is denser than a written guide and its section references point at the source rather than at this site. docs/controlplane-data-minimization.md · 1bb2a55b

Status: review complete — feeds tier-2 controlplane at-rest encryption (bd_2_0-k5t.7) and the BYO-controlplane question (bd_2_0-k5t.10).

Why this exists

Architecture invariant 2 keeps sovereign data out of the controlplane. This review applies the same instinct one level down, to sensitivity: of the platform metadata the controlplane legitimately holds, how much is org-sensitive, and for each such column what is the least-privilege way to hold it — encrypt it at rest, keep only a reference/hash, or relocate it enclave-ward so the controlplane never holds it at all.

Minimizing what the controlplane stores is the strongest lever on the controlplane BYOK problem: the less org-sensitive content lives centrally, the smaller the “but can I bring my own key for the management plane?” surface becomes. The governing rule (see architecture.md, “Data at rest & key custody”) is:

the controlplane may apply a customer-held key only to fields it stores and returns but never interprets.

Everything the controlplane must read to function (route a publish, resolve a grant, scope a snapshot) cannot be under a customer-only key without inverting availability and re-coupling the plane to a customer KMS — so for those the ceiling is operator-CMK encryption at rest (tier 2), not customer BYOK.

Classification tiers

  • Public — carries no confidentiality weight (surrogate ids, timestamps, status/boolean enums). No special handling.
  • Operational — the controlplane must interpret it to do its job, and it reveals tenant structure (opaque coordinates: which orgs/groups/agents/enclaves exist and how they map). Sensitive to correlation, but not customer content. Ceiling: operator-CMK at rest; cannot be customer-BYOK (interpreted) and cannot relocate (scoping is the controlplane’s function).
  • Org-sensitive — customer content or identity: policy rule bodies, display names, principals, auth bindings. Each gets an explicit recommendation below.

Recommendation vocabulary: encrypt-at-rest (operator CMK, tier 2) · reference/hash-only (hold an opaque handle, not the content) · relocate (push enclave-ward or to Zitadel; the controlplane stops holding it) · BYOK-eligible (stored+returned but never interpreted → a customer key could apply).

Per-table classification

Sourced from migrations 00001–00005 and sqlc/controlplane/models.go. Columns not listed are surrogate id/created_at/updated_at/status enums → public.

policy_setting, policy_agent_override

ColumnClassNote
value (JSONB)Org-sensitivethe policy rule body — customer governance content
org_id, group_id, agent_id, keyOperationalscope coordinates + setting key; interpreted when assembling a snapshot

Recommendation — value: encrypt-at-rest (operator CMK). The controlplane authors these centrally (platform/org/group scopes with locks) and serializes them into a policy.Snapshot it publishes controlplane→enclave. It stores and forwards the body but does not evaluate it (the resolver runs in the enclave gateway), so it is close to BYOK-eligible — but the snapshot must be readable to assemble and publish, so the safe ceiling is CMK-at-rest, not a customer-only key. Longer-term relocation lever: policy authoring could move enclave-ward for BYO-enclave tenants, but that is a redesign, not a column change. Near term: encrypt at rest, and never log/index the plaintext value.

enclave (+ subject from 00005)

ColumnClassNote
subjectOrg-sensitiveservice-account subject the enclave authenticates as (auth binding)
owning_org_idOperationalthe single org that owns the enclave — customer→infra placement (bd_2_0-j9r: one enclave, one org; NOT NULL)

Recommendation — subject: keep as an opaque reference; encrypt-at-rest. It is an authn identifier the controlplane must interpret to bind an enclave, so it is not BYOK-eligible and cannot relocate. Ensure it stays an opaque subject/reference (not a secret/token); with that, CMK-at-rest is defense-in-depth against disk/backup exposure. owning_org_id is an operational placement coordinate — opaque, interpreted, kept; correlation risk is inherent to running a management plane and is mitigated by using non-descriptive ids. (The enclave_org join table and the shared column were removed with shared enclaves, bd_2_0-j9r.)

org, "group"

ColumnClassNote
nameOrg-sensitivecustomer org/group display name — business data
id, org_idOperationaltenant coordinate (= policy/grant/enclave scope keys)
zitadel_org_id, zitadel_group_idOperationalexternal Zitadel identifiers (already references)

Recommendation — name: relocate / reference-only (strongest lever). The controlplane never interprets the display name — it keys everything off id. And Zitadel already owns org/group identity (00003 comment: “this schema deliberately does NOT model users, passwords, sessions or external identities (Zitadel owns them)”). So the cleanest minimization is not to store name centrally at all — resolve it from Zitadel on demand via zitadel_org_id/zitadel_group_id. If a denormalized name must stay for UX, it is the one clearly BYOK-eligible column (stored, returned, never interpreted) and can take a customer key. The zitadel_* ids are opaque external references → keep, low sensitivity.

grant_

ColumnClassNote
principalOrg-sensitiveuser:<zitadel-sub> or group:<org>:<group> — identity reference
role, capability, scope_kind, org_id, group_idOperationalthe authorization decision (who-can-do-what-where)

Recommendation — principal: already a reference; encrypt-at-rest. It is a pseudonymous Zitadel subject (not raw PII), and the controlplane interprets it to make authorization decisions — so it is neither BYOK-eligible nor hashable (hashing would break subject matching/enumeration against Zitadel). Keep it as the pseudonymous reference it already is and encrypt at rest. The rest of the grant row is operational authz structure the controlplane owns and must interpret: CMK-at-rest only, no relocate/BYOK.

policy_pack, policy_pack_install

ColumnClassNote
policy_pack.value (JSONB)Operational (platform)allow/deny rule bundle in a platform-owned catalog (00004)
policy_pack.name, install.(org_id, group_id, agent_id, pack_name)Operationalcatalog key + install coordinates

Recommendation — no special handling beyond at-rest hygiene. By doctrine a pack is a platform-owned reusable catalog, not org content, so value is not org-sensitive. If org-authored packs are ever introduced, re-classify their value as org-sensitive and fold into the policy_setting.value recommendation. Install rows are operational scope coordinates.

Summary — what feeds k5t.7 / k5t.10

Encrypt-at-rest under the operator CMK (tier 2, k5t.7) — org-sensitive content the controlplane must interpret, so BYOK is out but at-rest protects disk/backups:

  • policy_setting.value, policy_agent_override.value
  • grant_.principal (+ the interpreted grant structure, as a table-level choice)
  • enclave.subject

Relocate / reference-only (shrink what the controlplane holds — do this first):

  • org.name, "group".name → resolve from Zitadel via zitadel_* ids rather than storing centrally. This is the single biggest reduction of central org-sensitive content and the strongest answer to the controlplane-BYOK question.

BYOK-eligible (stored, returned, never interpreted) — only if kept at all:

  • org.name, "group".name (the sole columns meeting the never-interpreted bar).

Operational, accept as-is (opaque, interpreted coordinates):

  • all org_id/group_id/agent_id/enclave_id/owning_org_id scope keys, zitadel_* ids, role/capability/scope_kind, status/enum columns. Mitigation is non-descriptive ids, not encryption.

Net: after relocating the two name columns, the residual org-sensitive surface on the controlplane is policy rule bodies, grant principals/structure, and the enclave auth subject — all interpreted, all bounded by operator-CMK-at-rest. There is no residual column that both (a) is org-sensitive and (b) the controlplane stores-but-never-interprets and must keep, which is why the honest BYOK answer for sovereign data remains BYO-enclave, with tier-2 CMK-at-rest as the controlplane’s confidentiality floor.