Docs · Glidepath
Security model
A single place to see the platform’s security posture end to end. Each section links to the doc with the full depth - this page is the map, not the territory.
Threat model in one sentence
Many tenants (Applications) share one cluster and one control plane; a compromised or buggy component belonging to one tenant, or to the platform itself, should never be able to affect another tenant or escalate to cluster-wide control.
No cluster-admin, no broad impersonation, anywhere
Every tenant gets namespace-scoped RBAC only. The shared CDEvents broker - the one
component every tenant’s traffic passes through - holds no elevated cluster role:
kubectl auth can-i --as=system:serviceaccount:<broker-ns>:<broker-sa> '*' '*' is an
emphatic no. Where the broker needs to hand off to a tenant’s own pipeline identity, it
does so via Kubernetes’ own impersonate verb, granted per-Application as a single
namespaced Role/RoleBinding scoped to exactly that Application’s pipeline-runner
ServiceAccount - never a cluster-wide impersonation grant. See
chaining.md and ADR-0002.
Authentication without a platform-minted credential
The broker authenticates every caller via the Kubernetes TokenReview API against
that pod’s own cluster-issued, audience-bound, short-lived ServiceAccount token - never
a credential the platform itself mints, stores, or rotates. Compromising the broker
doesn’t hand out a reusable secret, since every trust decision is delegated to the
Kubernetes API server itself. See chaining.md.
Namespace isolation and its real limit
Every Application gets its own CI-control-plane namespace (<type>-<app>-cicd) and
its own per-environment namespace (<type>-<app>-<env>) - RBAC never spans them. The
CEL-filtered Trigger on the broker checks the calling namespace against the CDEvent’s
declared source before matching, which is the actual cross-tenant trust boundary - not
NetworkPolicy. NetworkPolicy (default-deny + allow-from-same-namespace) is
defense-in-depth on top of that, and only works if the cluster’s CNI enforces it
(Calico/Cilium) - a cluster running kindnet has this layer silently disabled. Know
which case your cluster is in before treating it as a security reference; see
installation.md.
Supply chain: build to release
- Image signing: Tekton Chains signs every built image keylessly via a cluster-local Fulcio instance, with an independent trust root per cluster (never shared or copied between clusters) - see image-signing.md and ADR-0006.
- Provenance/attestation: SLSA-shaped provenance is generated and verified before promotion - see provenance-policy.md.
- Commit signing: the
provenancegovernance gate verifies agitsignsignature on the actual app-repo commit being promoted (not the machine-generated gitops PR commit) - see commit-signing.md, including the merge-strategy gotcha that can silently break this. - Governance gates are real, not theater:
sast(Semgrep),imageScan(Trivy),policyCheck(gitsign),sbom(cosign attestation) all do genuine enforcement today, and any gate still awaiting a real backend is structurally marked as a stub - never silently reported as a pass. See governance-stubs.md and ADR-0003.
Release: no direct-mutation path exists
release cannot mutate a target cluster even if fully compromised - it can only open
a pull request against a gitops-<app> repo. Human review plus branch protection plus
ArgoCD’s own sync are the only path from “PR opened” to “cluster changed.” See
release.md and ADR-0004.
Secrets
Application secrets flow through a ClusterSecretStore/ExternalSecret model, never
through values files or platform-minted long-lived credentials - see
secrets-management.md and app-secrets.md.
The GitHub App private key used for git operations never leaves the pipelines-as-code
namespace: a shared, TokenReview-authenticated service mints a per-repo-scoped
installation token on request instead of copying the key into every Application’s
namespace - see release.md.
Multi-cluster: no credential ever crosses the boundary
The dev cluster never holds a credential for any staging/prod cluster, at any point - each additional cluster gets its own ArgoCD instance, and sync outcomes flow back to dev as an authenticated event, never as a push in the other direction. See multi-cluster.md and ADR-0005.
Build isolation
Image builds run under Pod Security Standards restricted via kaniko - no privileged
containers, no host namespaces or mounts, non-root by default, on any cluster this
platform runs on. See rootless-builds.md.
Known, accepted gaps
- NetworkPolicy is a no-op on a non-enforcing CNI (see above) - accepted for a shared dev cluster, not for anything beyond it.
- The broker’s TokenReview interceptor runs plain HTTP internally, not HTTPS - a deliberate tradeoff after a real TLS integration attempt hit a hard wall (see chaining.md); TokenReview-verified identity, not transport encryption, was always the actual trust boundary for this never-leaves-the-cluster call.
- Governance gates that haven’t landed yet are visible as structurally-marked stubs, not hidden - see governance-stubs.md for current status of each.