13 Commits
Author SHA1 Message Date
Aleksei Sviridkin 800bb0369d feat(redis)!: refuse external access without TLS at admission
A Redis release with external: true and TLS off publishes a plaintext
endpoint outside the cluster, and nothing stopped a tenant from asking
for one. The chart never infers TLS from external, because enabling it
moves Redis to a TLS-only listener, so external: true without an
explicit tls.enabled: true is exactly that case. A
ValidatingAdmissionPolicy on the Redis kind now refuses it with a
message naming the release and a remedy that works.

TLS is fixed when a Redis instance is created: the operator's
RedisFailover schema refuses a change, but the Redis API accepts it.
A spec's tls.enabled is therefore a request, not the instance's state,
and cannot prove on UPDATE that an instance has TLS; trusting it would
let a tenant flip it, expose the instance, and remove it again. So
external access can be enabled only at creation, where tls.enabled
does decide the instance, and any UPDATE turning it on is refused.

The RedisFailover schema does not exist before the first reconcile, so
a release admitted as external with TLS could have TLS dropped before
the instance is created. tls.enabled is therefore frozen while
external access stays on. Withdrawing external access in the same edit
lifts the freeze, which leaves a tenant a way to unblock an instance
whose TLS was flipped earlier, and the callers trusted by the tenant
host policy are exempt so an administrator can repair one in place.
Other edits to an instance that is already external pass, so releases
exposed in plaintext before the policy keep working.

Postgres is left out on purpose. CNPG pins ssl=on, and its default
pg_hba accepts plaintext from any client that asks, whatever the
chart's tls.enabled says; that field only decides whether the external
hostname goes into the certificate. A policy keyed on it would close
nothing.

The decisions are tested by compiling the shipped template with the
apiserver's own CEL compiler, since a structural chart test cannot
show what the policy admits.

BREAKING CHANGE: creating a Redis with external: true and without
tls.enabled: true, which applied before, is now refused, including a
GitOps re-apply of such a manifest into a fresh cluster. Turning
external on for an existing Redis is refused as well, so exposing an
instance means creating a new one with TLS, and tls.enabled can no
longer be changed on an instance with external access.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-03 01:42:57 +03:00
Aleksei Sviridkin 3b5af9dc55 fix(tenant): skip Cilium policies on isp-hosted without Cilium
The isp-hosted variants install no CNI of their own, so on a host
without Cilium the cilium.io/v2 kinds do not exist and the tenant
install fails, taking the root tenant and everything that depends on
cozystack-basics down with it.

The policies now render on isp-hosted only when the host serves both
Cilium policy kinds. On every other variant Cozystack installs Cilium
itself, so they keep rendering unconditionally: a CRD that is missing
at render time there fails the install loudly instead of leaving the
tenant without network isolation, because helm-controller does not
re-render a release when only API discovery changes.

On isp-hosted without Cilium the tenant gets no network isolation from
Cozystack; isolating tenants is then up to the host's CNI.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-02 12:06:54 +03:00
Aleksei Sviridkin 900711567a fix(cozystack-basics): require route hostnames in tenant namespaces
The route hostname policy short-circuited on an absent field. Its CEL
opened with `!has(object.spec.hostnames) ||`, so an HTTPRoute,
TLSRoute or GRPCRoute declaring no hostnames satisfied the first
operand and was
admitted without a single hostname compared against the tenant apex.
An explicitly empty list reached the same outcome by a different path,
because `all()` over an empty list is vacuously true.

A route that names no hostname does not match nothing. Under Gateway
API it inherits the hostnames of every listener it attaches to, so it
covers whatever that listener covers. Require the field to be present
and non-empty before validating it.

All three policies rendered by the template share one CEL string, so
HTTPRoute, TLSRoute and GRPCRoute pick the change up together. The denial message
now names the missing-hostnames case, and the Gateway e2e grep moves to
the part of that message common to every denial it issues.

The Gateway e2e suite carried the one fixture that relied on the old
behaviour: its parentRef probe declared no hostnames and is applied
into the suite's tenant namespace. It now reads the apex from the
namespace label and names a hostname under it, as the neighbouring
cases in that suite already did.

Radius is unchanged: the policy still matches only tenant-* namespaces.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-04 18:08:02 +03:00
Aleksei Sviridkin fb740db0b0 docs(security): separate the fail-open Gateway-listener policy from the fail-closed route policies
The self-assessment summarised the whole hostname policy set as
fail-closed, while its own table row states that the Gateway-listener
policy passes a Gateway whose namespace carries no host label. Say
which policies are which. The port-443 kinds comment in the reconciler
test also tied the GRPCRoute admission rule to the port-80 listener;
the rule matches the kind wherever it attaches, and the port-443
exclusion is a least-privilege choice, not the reason the rule exists.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-04 01:18:07 +03:00
Aleksei Sviridkin af19aa933c docs(security): record GRPCRoute in the route hostname policy set
The port-443 kinds set still excludes GRPCRoute, TCPRoute and UDPRoute.
cilium#45559 governs only that every port-443 listener carries the same
set, not what is in it, so the contents are a least-privilege choice:
nothing the platform ships needs gRPC, TCP or UDP routing on port 443.
TCPRoute and UDPRoute additionally carry no hostname and no admission
rule gates them.

The gateway README described HTTPS and TLS-passthrough listeners as
carrying different kinds. They carry the same one, which is what
cilium#45559 requires.

The threat model and the self-assessment enumerate which route kinds the
hostname policies cover, and both listed two.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-03 22:17:29 +03:00
Timur Tukaev ec2203a934 docs(security): publish vulnerability reporting and a backlog reconciliation
Four independent reviews of the first draft of this reporting found that it
overstated remediation, understated how much of the backlog was never
applicable, and contained a disclosure leak the stated rule had not
anticipated. What lands here is the corrected version.

The reconciliation is the substantive artifact. It audits every finding the
CVE-scanning pipeline has tracked against what the project actually shipped,
because the pipeline has no write-back from this repository and so has never
marked anything fixed. Findings: remediation did happen and is invisible to the
tracker, concentrated in one wave in June 2026 rather than the steady stream an
earlier draft claimed; 57% of the backlog was never applicable or cannot be
assessed, the largest block being packages that contain no executable
vulnerable code, chiefly kernel headers, since a container never provides a
running kernel; reachable exposure in a stock install is an estimated 2-5% of
the tracker; four component bumps are available upstream and not yet taken; and
the pipeline itself has seven defects, including deduplication by CVE
identifier alone and a broken attribution step that left 16% of findings
unattributable.

The monthly reports are cut back to per-month facts. Remediation is no longer
attributed to individual months: crossing a severity-banded count of still-open
findings with one month's component bumps, which anyone can enumerate from the
commit log, narrows the search for unpatched exposure more than a bare count
does. An earlier draft did exactly that while refusing to publish bare counts
on disclosure grounds. The triage-latency figure is also gone, because 494 of
495 decisions were recorded in a single bulk pass and a per-finding latency
cannot be recovered from a closing timestamp.

Two process-tracking issues that the pipeline's severity filter should have
excluded are no longer counted as findings, so the total is 825 rather than 827.

The disclosure rule is revised in two directions, both more open: findings with
no upstream fix will be named with mitigation guidance, since there is nothing
to wait for and a mitigation is then the only available control; and the
generator's guard now covers every advisory namespace rather than CVE alone,
refuses to emit any file if any file would leak, and takes released evidence
rather than a stale tracker label as its source of truth.

SECURITY.md, the self-assessment and the GTR are brought into line: all three
previously described this reporting as unpublishable, and the GTR refused on
principle the counts the reports print.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-08-11 00:40:31 +05:00
Timur Tukaev e1fc2ab82e docs(security): fix the supported set at three lines and record that no backport rule exists
Maintainer decisions on the two questions this branch raised rather than
answered.

Supported versions are the latest stable minor and the two minors preceding it.
Expressed as a rolling rule, so it names concrete lines when read against the
Releases page without going stale, and it replaces the activity-window test,
which classified every 1.x line back to 1.0 as supported and so described
something wider than the project does.

There is no documented rule for choosing which supported lines a fix is
backported to; it is a per-fix maintainer decision weighing severity against
backport risk. Say that plainly in SECURITY.md instead of implying a policy.

That also resolves the promise in the incident-response process, which said a
fix ships across all affected supported release lines. It does not necessarily,
and with no rule governing the choice that sentence committed the project to
something it has not agreed. Point at the per-fix decision instead.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-08-10 21:55:37 +05:00
Timur Tukaev ea3d68493a docs(security): restore the triage admission and correct the nightly timeline
The previous commit narrowed a true statement into a false one. It said the
scanner gap was remediation throughput and that triage ran on schedule; triage
is also behind, and the backlog awaiting a triage decision includes findings at
critical severity well past the one-business-day target. Both tracks are now
stated as behind. Figures stay out of this repository for the disclosure reason
the document already gives and belong in the private channel, so the admission
is qualitative rather than rounded down.

The nightly characterisation was also wrong. It read as fixed-then-regressed,
which understates it: after the fix in #3332 the scheduled run passed for four
days, went red for five, passed for three, and has failed every run since
2026-08-02 — seven of the last twenty-four passed. It is flapping, not newly
broken, and the current failure is untriaged rather than provably unfiled.

Carry the target-attainment caveat into SECURITY.md, which stated the triage
SLAs with no indication they are not being met, and resync the self-assessment
on the monthly report: it names findings individually and so is not publishable
until reduced to counts by severity.

Also note in SECURITY.md what the activity test actually classifies today —
every 1.x line from 1.0 to 1.6 — and how backport effort is prioritised across
that set, since supporting seven lines in parallel otherwise invites the
question it does not answer.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-08-10 18:57:33 +05:00
Timur Tukaev d30d638575 docs(security): correct scanner, Renovate and support-line claims after internal review
Two internal reviews of the previous commit found that it overstated several
controls. Corrections, all verified against the tree and the GitHub API:

CodeQL is not a required status check on main — the required checks are
pre-commit and E2E Tests — so the questionnaire no longer says it gates merges.
SECURITY.md and the security self-assessment already hedged this correctly and
the GTR now matches them.

Renovate does not cover Helm charts: helm-values is disabled repo-wide because
many image refs are assembled by the cozy-lib.image helper rather than written
literally in values.yaml. Say so, and say that the curated platform component
images are bumped by maintainers per release rather than by a bot.

The rewritten supported-versions table narrowed the support window to the
latest two minors, which contradicts practice — several 1.x lines receive patch
releases concurrently. Restate it as a description of what the project actually
does, keyed to active release branches rather than to version numbers.

The CVE-scanning pipeline is described more precisely: noise filters run before
issue creation, discovery reads direct Go requirements with transitive coverage
coming from the filesystem scan, and render-time image refs are a known blind
spot. Its SLAs are labelled as targets rather than measured attainment, and the
confidentiality claim is narrowed from the whole repository to per-CVE findings
and triage state, since the policy documents and scripts hold no vulnerability
data and are what actually evidences the control.

SBOM and provenance are stated from the build tooling rather than from an issue
title: provenance is explicitly disabled and SBOM generation is implemented but
defaulted off. zizmor is added as a third gating scanner in both documents.

Also close markers that repository evidence settles: the in-tree security
self-assessment, threat model and incident-response documents; no default audit
policy is shipped; and there is no top-level NOTICE file, which is stated as a
compliance gap rather than deferred. Fix the phantom OWNERS reference at its
source in CONTRIBUTOR_LADDER.md, and add current CI health next to the e2e
workflow links so the red nightly run is disclosed rather than discovered.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-08-10 18:45:10 +05:00
tym83andClaude 60704530ed docs(security): align security docs with current controls for incubation DD
Bring the security documentation in line with controls that are already
in place, and add the two coordinated-disclosure items flagged during the
incubation due-diligence review.

- self-assessment: DCO is enforced by an automated check; private intake
  is GitHub Private Vulnerability Reporting; OpenSSF passing badge achieved
  2026-04-01 (working toward silver); GHSA/PVR framing for advisory history;
  incident-response now summarized publicly.
- SECURITY.md: route reports to Private Vulnerability Reporting with a
  MAINTAINERS.md fallback; add a 90-day coordinated-disclosure window and a
  CVSS-based remediation-target table.
- CODEOWNERS: document that it encodes the area-scoped Reviewer role, which
  is intentionally distinct from the MAINTAINERS.md governance roster, and
  that security reporting is not routed through this file.
- Add docs/security/incident-response.md as the public incident runbook.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
2026-07-07 13:52:17 +05:00
tym83andClaude 9a4bcfb13c docs(security): mark self-assessment stage as completed
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
2026-07-07 13:19:44 +05:00
tym83andClaude 19ab910f66 docs(security): add security self-assessment
Add the CNCF security self-assessment required for the Incubation due
diligence review, alongside the existing design-level threat model. It
documents the trust boundaries, actors/actions, security functions,
secure development practices, and known gaps.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
2026-07-07 13:11:34 +05:00
Aleksei Sviridkin 286f260570 docs(security): add design-level threat model
Document the platform's actors, boundary-crossing actions, and security
goals/non-goals, organized by trust boundary (T0-T4) per the CNCF
TAG-Security Actors/Actions model. Records the primary trust-boundary
invariant (tenant RBAC grants no HelmRelease/core writes; the aggregated
API server fixes the chart reference server-side) and the two admission
enforcement points (core kube-apiserver vs cozystack-api). Every claim
cites a source file so the model can be re-verified as the code evolves.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-07-06 18:23:11 +03:00