Controlplane data minimization
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
| Column | Class | Note |
|---|---|---|
value (JSONB) | Org-sensitive | the policy rule body — customer governance content |
org_id, group_id, agent_id, key | Operational | scope 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)
| Column | Class | Note |
|---|---|---|
subject | Org-sensitive | service-account subject the enclave authenticates as (auth binding) |
owning_org_id | Operational | the 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"
| Column | Class | Note |
|---|---|---|
name | Org-sensitive | customer org/group display name — business data |
id, org_id | Operational | tenant coordinate (= policy/grant/enclave scope keys) |
zitadel_org_id, zitadel_group_id | Operational | external 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_
| Column | Class | Note |
|---|---|---|
principal | Org-sensitive | user:<zitadel-sub> or group:<org>:<group> — identity reference |
role, capability, scope_kind, org_id, group_id | Operational | the 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
| Column | Class | Note |
|---|---|---|
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) | Operational | catalog 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.valuegrant_.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 viazitadel_*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_idscope 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.