ADR-0104: Security model — secrets, TLS, RBAC, and redaction¶
The consolidated threat model and security posture: how credentials, TLS trust, least-privilege RBAC, and payload redaction are handled across Kollect.
Theme: 01 · Foundations · Status: Current
Context¶
Security decisions were spread across many ADRs — TLS trust (ADR-0201), namespaced
isolation and SAR (ADR-0203), redaction (ADR-0303),
and HTTP/API auth (ADR-0404) — but there was no single model a reviewer
could read to understand Kollect's posture. This ADR records the decision; the maintained
security architecture maps it to current controls.
(SECURITY.md at the repo root remains the disclosure policy.)
Threat model (what we defend against)¶
- A compromised or misconfigured tenant reading/exporting resources outside its namespace.
- Secrets (registry creds, DB passwords, tokens, kubeconfigs) leaking into exported inventory, logs, or etcd status.
- Untrusted/MITM'd sink or cluster endpoints.
- Over-broad operator RBAC enabling privilege escalation.
Decision¶
Secret handling¶
- Credentials are referenced by
secretRef, never inlined in CRD specs (ADR-0201). - Reconcilers resolve secrets and pass material via
BuildContext(SecretData,DatabaseSecretData,CAPEM) — backends never read Kubernetes secrets themselves (ADR-0406). - Secret values are not intentionally logged or written to CR
statusor Events. Known credentials are sanitized in Git errors, but generic third-party backend error strings do not yet have a centralized sanitizer (ADR-0602).
TLS trust¶
- Sinks and cluster connections trust a configurable CA (
caPEM) resolved from a secret/configmap. insecureSkipVerifyexists only where unavoidable (HTTP-ish backends), is off by default, and is an explicit compatibility opt-in. It weakens server identity and is independent of private-address reachability. When set, reconcilers surface status conditionTLSInsecure(ConditionTLSInsecure) so operators can see the opt-in without reading the spec.- Git HTTPS/SSH retains server-name or host-key verification while using the resolved-address guard.
RBAC (least privilege)¶
- The operator's
ClusterRolegrants only the verbs needed (watch/list/get on selected GVKs; CRUD on Kollect CRDs; leader-election on a single lease). - Tenant mode (ADR-0203) narrows watches to configured namespaces;
chart emits
Role/RoleBindinginstead of cluster-wide bindings (ADR-0704). - Cross-namespace reads in namespaced inventories are SubjectAccessReview-gated: missing permission degrades gracefully (skip + condition) rather than escalating.
Redaction and data minimization¶
- Profiles redact via
scrubKeys/redaction before items enter the store (ADR-0303), reducing what can reach the export data contract (ADR-0405). - Helm release values, Secret data, and known-sensitive keys are scrubbed at extraction time, not at export time. Key-based scrubbing cannot classify every business-sensitive field.
API / HTTP exposure¶
- The read API is feature-gated and off by default. Its default mode uses Kubernetes bearer TokenReview + SubjectAccessReview and is namespace-scoped (ADR-0404); a disabled-auth mode exists for local development.
- The built-in listener is plain HTTP. Deployers provide transport confidentiality through ingress, a service mesh, a local port-forward, or an equivalent network boundary.
Outbound network policy¶
- Sink endpoints are checked at admission when default-on webhooks are enabled and again after DNS resolution at dial time (resolved-address policy).
- Private RFC1918/ULA sinks are denied by default. The cluster-admin Helm value
allowPrivateSinksrenders--allow-private-sinks; it is not a CRD or tenant field. - Loopback, link-local/cloud metadata, unspecified, multicast, carrier-grade NAT, benchmark ranges,
and known metadata hostnames remain denied when the private-sink opt-in is enabled. Default-on
admission also rejects
file://; disabling webhooks removes that check, and runtime Git accepts valid local bare-repository paths. - The chart does not ship a sink-aware egress
NetworkPolicy; deployers constrain egress to the Kubernetes API, DNS, and approved sinks.
Multi-cluster¶
- Each cluster runs its own manager and exports to shared sinks partitioned by
spec.cluster(ADR-0501). There is no privileged central hub transport.
Webhook TLS (serving)¶
- Validating webhooks are enabled by default and the chart provisions their serving certificate through cert-manager. Disabling chart-managed certificate resources requires operator-provided serving certificates and CA injection; the chart does not generate TLS material on that path (ADR-0105).
Supply chain¶
- Operator, pipeline, and UI images are scanned, signed, SBOM'd, and published with provenance; see ADR-0705.
Consequences¶
- A reviewer has one page for the posture; individual ADRs hold the detail.
- Redaction-at-extraction prevents sinks from exporting fields removed before they enter the store; profile authors must still classify and scrub business-sensitive values.
- SAR-gated degradation favors availability + safety over hard failure on partial permissions.
- Private-sink reachability is an explicit cluster-operator trade-off rather than tenant-controlled configuration.
Open questions¶
- DECIDED : Encryption-at-rest for sinks (Postgres/object-store) is recommended and documented, not enforced by the operator (it's a backend/infra responsibility).
- DECIDED : Add a formal RBAC audit gate in CI (
kubeaudit-style) as a maturity signal (ADR-0705). - OPEN: A built-in secret-leak scanner over outgoing payloads as defense-in-depth beyond
scrubKeys?