Skip to content

Add Cozystack security self-assessment snapshot - #2306

Merged
kfaseela merged 3 commits into
cncf:mainfrom
tym83:cozystack/security-assessment-snapshot-2026-09-19
Sep 23, 2026
Merged

kfaseela merged 3 commits into
cncf:mainfrom
tym83:cozystack/security-assessment-snapshot-2026-09-19

Conversation

@tym83

@tym83 tym83 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

This adds a point-in-time snapshot of Cozystack's security self-assessment to projects/cozystack/security-assessment/self-assessment.md, for the Incubation due-diligence record tracked in #1916 and requested in cozystack/community#81.

The self-assessment is maintained at docs/security/self-assessment.md in cozystack/cozystack; this is a point-in-time copy for the TOC record.

Point-in-time snapshot of Cozystack's security self-assessment for the Incubation
due-diligence record (cncf#1916), per the Security section of the DD guide and
requested in cozystack/community#81.

Signed-off-by: tym83 <6355522@gmail.com>

@JustinCappos JustinCappos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a few minor issues to fix. However, the assessment is in good shape overall.


### Background

Cozystack is delivered as a **management (root) Kubernetes cluster** onto which tenants, managed services, virtual machines, and tenant Kubernetes clusters are layered. The centre of the security model is the aggregated API server **`cozystack-api`**. Tenants never write privileged Kubernetes objects (HelmRelease, Deployment, Secret, RBAC) directly. Instead they write thin, virtual `apps.cozystack.io/*` **Application** custom resources, and `cozystack-api` translates each one into a Flux `HelmRelease` whose chart reference is fixed server-side. The set of Application kinds is registered dynamically from `ApplicationDefinition` custom resources, so the platform's catalogue of managed services is data-driven rather than hard-coded.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is helpful, but at least for me is missing a sentence or so of "why" / "what is the impact". Can you explain how writing to apps.cozystack.io/* application custom resources changes the security / usability properties?


Cozystack is delivered as a **management (root) Kubernetes cluster** onto which tenants, managed services, virtual machines, and tenant Kubernetes clusters are layered. The centre of the security model is the aggregated API server **`cozystack-api`**. Tenants never write privileged Kubernetes objects (HelmRelease, Deployment, Secret, RBAC) directly. Instead they write thin, virtual `apps.cozystack.io/*` **Application** custom resources, and `cozystack-api` translates each one into a Flux `HelmRelease` whose chart reference is fixed server-side. The set of Application kinds is registered dynamically from `ApplicationDefinition` custom resources, so the platform's catalogue of managed services is data-driven rather than hard-coded.

When OIDC is enabled (opt-in; `authentication.oidc.enabled` is off by default), authentication is centralized through Keycloak/OIDC; in the default OIDC-disabled mode the dashboard is fronted by a token-proxy. Authorization uses Kubernetes RBAC (with roles aggregated per tenant) plus ValidatingAdmissionPolicy. Networking isolation is provided by Cilium. Platform PKI (including the aggregated-API serving certificate) is issued by cert-manager. Tenant Kubernetes clusters run on dedicated Kamaji-hosted control planes with Talos worker nodes running as KubeVirt virtual machines.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also be useful here to indicate which of these is therefore trusted and what users really need to understand. E.g., is it that you're doing all the Cilium / Cert-Manager work for them or does the user need to do these parts correctly?

| Delete a platform-critical object | Any actor → core kube-apiserver | DELETE request | `cozystack-no-delete-guardrail` denies DELETE on `platform.cozystack.io/no-delete=true` objects. Operational guidance, not adversarial defense: an actor able to update the object can remove the label first (tenants cannot reach these roots; only T0/T1). | Accidental teardown of platform roots blocked. |
| East-west traffic | Workload (T3) → workload/system | Packets | Cilium enforces policy at both endpoints. Each tenant pod's egress is confined to its own subtree, platform system services, and `world` (out-of-cluster); ingress is open to `world`+`cluster`. A flow needs the source's egress to allow it, so an unrelated tenant has no egress path to another tenant's pod. | Cross-tenant pod-to-pod traffic is **denied** (by source egress). Within a subtree, parent→descendant is allowed; child→parent is denied except for specific ancestor services (vminsert, etcd, ingress). Caveats (see Non-goals): open ingress and LoadBalancer hairpin. |

### Goals

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great set of goals here that are very clear. How do you expose these to your users?

### Non-goals

- Defending against a compromised **cluster administrator, node root, or infrastructure administrator** — trusted by design and out of scope (`SECURITY.md`).
- Treating the **T1 platform control plane** (`cozystack-api` ServiceAccount, Flux, system operators) as a sandbox; these hold cluster-admin-equivalent power.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean isolating from a malicious T1 platform control plane? This might be a clearer phrasing...


- Defending against a compromised **cluster administrator, node root, or infrastructure administrator** — trusted by design and out of scope (`SECURITY.md`).
- Treating the **T1 platform control plane** (`cozystack-api` ServiceAccount, Flux, system operators) as a sandbox; these hold cluster-admin-equivalent power.
- **Upstream-only vulnerabilities** not introduced or worsened by Cozystack packaging or defaults.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you reviewed your dependency footprint?

- **Dependency and image CVE scanning (SCA).** An organization-wide pipeline built on Trivy scans every non-fork, non-archived repository in the `cozystack` organization — Go modules, Dockerfile base images, and container images referenced by the packaged charts — against NVD, vendor advisories and the GitHub Advisory Database. CRITICAL findings are scanned every 6 hours, the remaining severities weekly; findings are noise-filtered and tracked as issues under a triage matrix with per-severity targets (CRITICAL 1 business day to triage, 7-day fix target). Two limitations are worth stating: image references assembled at render time by the `cozy-lib.image` helper are invisible to values-file discovery, and per-CVE findings and triage state are currently held in a private repository, so the control is not yet externally verifiable. Aggregate reporting is now published at [`docs/security/reports/`](reports/): monthly reports reduced to counts by severity, plus a backlog reconciliation auditing every tracked finding against what shipped. Publishing the pipeline policy, the triage matrix, the finding status model and the scanning scripts, while keeping per-finding detail embargoed until fixed, is outstanding. The documented triage target is not being met and the role that owns it is unassigned; remediation is happening but the pipeline cannot record it, and the reconciliation sets out both.
- **Dependency updates.** Renovate manages Go modules, Dockerfiles, and GitHub Actions, plus a small set of regex-pinned references, with OpenSSF OSV vulnerability alerts enabled and automerge disabled. The `helm-values` manager is disabled repo-wide, so the curated component images that make up the shipped platform are bumped by maintainers when preparing a release rather than automatically. There is no Dependabot configuration; Renovate is the dependency-update tool.
- **Supply-chain posture.** OpenSSF Scorecard runs weekly with published results. Most GitHub Actions are pinned by commit SHA; a small number of recently added workflows still use floating tags and are being brought into line. Renovate is configured to pin digests; applying digest pins across container base images is in progress.
- **Signed / reproducible builds.** Honestly noted as gaps: release images are **not** signed (no cosign/sigstore), build provenance is currently disabled (`--provenance=false`), SBOM generation is plumbed but off by default, and reproducible builds are not yet established. These are recognized improvement areas for the incubation timeframe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reproducible builds is likely hard. Doing in-toto attestations is an easy first step.

…, T1 non-goal, and in-toto step

Incorporates @JustinCappos's review on cncf#2306.

Signed-off-by: tym83 <6355522@gmail.com>
@tym83

tym83 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @JustinCappos — all addressed (source doc updated in cozystack/cozystack#4352, this snapshot re-synced to match):

  • L35 (why / impact): added the consequence — a tenant's entire write surface is a small, server-validated set of Application fields (never a HelmRelease, Secret or RBAC object), so it cannot escalate or reach another tenant by crafting a privileged manifest; and provisioning becomes one declarative resource rather than a chart the tenant must assemble and secure.
  • L37 (what is trusted / who does what): clarified that Cilium, cert-manager, RBAC and OIDC are platform-managed — the cluster administrator (T0) wires them and tenants inherit the posture; a tenant configures none of them, only the values in its Application resources.
  • L92 (T1 phrasing): reworded — yes, this is about not defending a compromised T1 platform control plane; it holds cluster-admin-equivalent power by design and is trusted.
  • L149 (builds): agreed — named build-provenance / in-toto attestations as the near-term first step ahead of full reproducibility, followed by cosign signing.
  • L77 (how are the goals exposed to users?): they are published as the design-level threat model in-repo (docs/security/threat-model.md) and summarized in this self-assessment; SECURITY.md and the docs site carry the operator-facing parts. Happy to add a shorter user-facing "security model" page to the docs site if that would help.
  • L93 (dependency footprint reviewed?): yes — an org-wide Trivy SCA pipeline scans Go modules, base images and chart-referenced images across every non-fork repo, and the August backlog reconciliation enumerated the footprint down to 247 distinct (package, installed-version) roots; Renovate with OSV alerts tracks updates (see Secure Development Practices → SCA).

@JustinCappos JustinCappos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks!

Based upon what I've seen here, you're well positioned to do a joint assessment. It probably shouldn't be a huge lift.

Comment thread projects/cozystack/security-assessment/self-assessment.md Outdated
Comment thread projects/cozystack/security-assessment/self-assessment.md

@sherine-k sherine-k left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tym83
Thanks !
I have just a nit pick for you below (and a little question).

Point the docs/security/reports link at the absolute cozystack/cozystack URL so
it resolves from the cncf/toc snapshot (was a repo-relative path). Per @sherine-k
review on the incubation self-assessment.

Signed-off-by: tym83 <6355522@gmail.com>
@tym83

tym83 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Hi @sherine-k — thanks, both addressed.

Broken link: fixed — the docs/security/reports/ link now points at the absolute cozystack/cozystack URL (it was a repo-relative path that did not resolve from the snapshot).

OIDC / tenant clusters: your reading is correct, with one v1.6 addition. Cozystack has two independent auth layers:

  1. Platform (management) OIDC authenticates users to the management clusters kube-apiserver, the aggregated cozystack-api, and the dashboard/console. Keycloak group membership maps to RBAC in the management cluster — a cozystack-cluster-admin group plus per-tenant tenant-<name>-view/-use/-admin/-super-admin groups auto-created with the tenant. This token is scoped to the management cluster only.

  2. Tenant Kubernetes clusters are Kamaji-hosted — each has its own kube-apiserver and its own authentication. The management-cluster OIDC access token does not authenticate to a tenant cluster. To reach their cluster a tenant user retrieves a per-cluster kubeconfig: the always-available static admin kubeconfig (Secret kubernetes-<name>-admin-kubeconfig, key admin.conf, a cluster-CA-issued break-glass credential), downloadable from the dashboard, targeting that clusters Kamaji control-plane endpoint. (Separately, the tenants management-cluster access uses the service-account bearer token in the tenant-<name> Secret — that is for cozystack-api/dashboard, not the tenant cluster.)

  3. As of v1.6 a tenant cluster can optionally enable its own OIDC via the Kubernetes app CRD (oidc.mode): System (trust the platform Keycloak through a per-cluster public client, writes a kubectl oidc-login kubeconfig) or CustomConfig (the tenants own IdP). Audiences are per-cluster, so a token minted for one tenant cluster is rejected by anothers apiserver. It is off by default (None).

Docs: https://cozystack.io/docs/v1.6/operations/oidc/ and https://cozystack.io/docs/v1.6/kubernetes/oidc-authentication/

Happy to fold a one-line clarification into the assessments Authenticate row if that would help readers.

@sherine-k sherine-k left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@kfaseela
kfaseela merged commit 3e025eb into cncf:main Sep 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants