Hangar

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 provenance governance gate verifies a gitsign signature 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.