Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .github/workflows/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
contents: read
packages: write
id-token: write
attestations: write # JEF-275: write the Sigstore-signed SLSA build-provenance attestation
attestations: write # write the Sigstore-signed SLSA build-provenance attestation
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
push: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/v') }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# sccache's R2 backend for the in-image cargo build (JEF-584, ADR-0020).
# sccache's R2 backend for the in-image cargo build (ADR-0020).
# `secret-envs` (key=envname) reads these straight out of the RUNNER POD's env,
# where the `sccache-r2` Secret is injected via envFrom (cluster repo:
# charts/actions/runners/values-protector.yaml) — the repo has no Actions secret
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
[[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "missing/invalid build digest: '$DIGEST'" >&2; exit 1; }
echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
# Sigstore-signed SLSA build-provenance attestation (keyless: Fulcio + Rekor) — what
# protector's provenance observer verifies to the "verified" posture (JEF-275). The buildx
# protector's provenance observer verifies to the "verified" posture. The buildx
# `provenance: true` above is the unsigned variant the observer never reads.
- name: Attest build provenance (Sigstore-signed SLSA)
if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/v') }}
Expand Down
28 changes: 14 additions & 14 deletions .github/workflows/chart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ jobs:
--set mode=enforce >/dev/null 2>&1; then
echo "mode: enforce with empty enforceScope was not refused"; exit 1
fi
- name: "Assert namespace-scoped enforce tightens NetworkPolicy write to per-namespace Roles (JEF-277)"
- name: "Assert namespace-scoped enforce tightens NetworkPolicy write to per-namespace Roles"
run: |
# JEF-277: NetworkPolicy is namespaced and the engine only writes cuts inside
# NetworkPolicy is namespaced and the engine only writes cuts inside
# enforceScope. A NAMESPACE-only enforceScope must derive a per-namespace
# Role+RoleBinding for networkpolicies create/delete/patch in EACH listed namespace,
# and must NOT grant the cluster-wide networkpolicies write on the ClusterRole.
Expand Down Expand Up @@ -168,7 +168,7 @@ jobs:
echo "ok"
- name: Assert feed-fetcher sidecar is ON by default and auto-wires the engine
run: |
# JEF-238 default-on: a stock install must render the feed-fetcher NATIVE SIDECAR
# default-on: a stock install must render the feed-fetcher NATIVE SIDECAR
# (an initContainer with restartPolicy: Always) on the engine pod, a shared feeds
# emptyDir, and the engine auto-wired to the KEV file via PROTECTOR_KEV_FILE — with
# NO CronJob, NO ConfigMap, and NO feed-sync RBAC (the sidecar makes no apiserver
Expand All @@ -189,7 +189,7 @@ jobs:
|| { echo "engine not auto-wired to KEV by default"; exit 1; }
echo "$render" | grep -q 'kev.json' \
|| { echo "engine not pointed at the sidecar kev.json"; exit 1; }
# Engine auto-wired to the EPSS file the same sidecar writes (JEF-243) — the
# Engine auto-wired to the EPSS file the same sidecar writes — the
# predictive exploitation axis alongside KEV.
echo "$render" | grep -q 'PROTECTOR_EPSS_FILE' \
|| { echo "engine not auto-wired to EPSS by default"; exit 1; }
Expand All @@ -202,13 +202,13 @@ jobs:
if echo "$render" | grep -qE 'kev-snapshot|advisory-snapshot'; then
echo "feed-sync ConfigMaps still rendered (should be an emptyDir now)"; exit 1
fi
# The NVD advisory feed was retired (JEF-242): KEV is the ONLY feed now, so no
# The NVD advisory feed was retired: KEV is the ONLY feed now, so no
# advisory env and no advisory.json must render anywhere.
if echo "$render" | grep -q 'PROTECTOR_ADVISORY_FILE'; then
echo "advisory env rendered despite the advisory feed being retired (JEF-242)"; exit 1
echo "advisory env rendered despite the advisory feed being retired"; exit 1
fi
if echo "$render" | grep -q 'advisory.json'; then
echo "advisory.json path rendered despite the advisory feed being retired (JEF-242)"; exit 1
echo "advisory.json path rendered despite the advisory feed being retired"; exit 1
fi
echo "ok"
- name: Assert feedSync.enabled=false renders no sidecar and no auto-wire
Expand All @@ -231,9 +231,9 @@ jobs:
echo "engine auto-wired advisory despite feedSync.enabled=false"; exit 1
fi
echo "ok"
- name: "Assert liveness/readiness probes tolerate a sweep-CPU burst (JEF-560)"
- name: "Assert liveness/readiness probes tolerate a sweep-CPU burst"
run: |
# JEF-560: a protector container was crashlooping (Ready never latching, restart
# a protector container was crashlooping (Ready never latching, restart
# count climbing, clean exit 0) because the default 1s probe timeout / 3-strike
# (30s) budget was too tight for the CPU the per-pass signing/provenance sweep
# legitimately bursts under the chart's CPU limit — kubelet SIGTERM'd a healthy,
Expand All @@ -256,11 +256,11 @@ jobs:
# The CPU limit that produced the throttling-induced probe misses also needs
# headroom (the RAM-tight request is unchanged; only the burst ceiling moves).
echo "$ready" | grep -q 'cpu: 500m' \
|| { echo "cpu limit not raised past the JEF-560 250m ceiling"; exit 1; }
|| { echo "cpu limit not raised past the 250m ceiling"; exit 1; }
echo "ok"
- name: Assert the feed-fetcher sidecar is unprivileged and has NO apiserver access
run: |
# JEF-238 egress boundary: the sidecar is the ONLY container with egress. It must
# egress boundary: the sidecar is the ONLY container with egress. It must
# run unprivileged (non-root, no privilege escalation, read-only rootfs, all caps
# dropped) and — unlike the retired CronJob — make NO apiserver call: no dedicated
# ServiceAccount, no Role/RoleBinding for it, no kubectl. It fetches the FULL public
Expand All @@ -284,12 +284,12 @@ jobs:
echo "feed-fetcher invokes kubectl (should make no apiserver call)"; exit 1
fi
# It must fetch the KEV feed and the EPSS feed (the two exploitation-intel feeds; the
# advisory feed was retired, JEF-242).
# advisory feed was retired).
echo "$render" | grep -q 'known_exploited_vulnerabilities.json' \
|| { echo "feed-fetcher does not fetch the KEV feed"; exit 1; }
echo "$render" | grep -q 'epss_scores-current.csv.gz' \
|| { echo "feed-fetcher does not fetch the EPSS feed (JEF-243)"; exit 1; }
|| { echo "feed-fetcher does not fetch the EPSS feed"; exit 1; }
if echo "$render" | grep -q 'PROTECTOR_ADVISORY_FILE'; then
echo "advisory env rendered despite the advisory feed being retired (JEF-242)"; exit 1
echo "advisory env rendered despite the advisory feed being retired"; exit 1
fi
echo "ok"
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name: e2e (k3d)
permissions:
contents: read
on:
# Gate PRs AND every merge to main (JEF-303): e2e is a REQUIRED check via branch
# Gate PRs AND every merge to main: e2e is a REQUIRED check via branch
# protection, so it must run on PRs to block a red merge — not just post-merge (the
# old post-merge-only signal let a red e2e land silently). It builds an image and
# stands up k3d (minutes) on a GitHub-hosted runner (it needs a local Docker daemon,
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
# Secret via envFrom, so none of it belongs in this workflow and no credential is
# committed here.
#
# The redis backend is deliberately NOT set here any more (JEF-564): sccache selects
# The redis backend is deliberately NOT set here any more: sccache selects
# ONE backend from its env, so setting both would make which one you get an
# implementation detail of sccache's precedence -- the R2 cutover could read as done
# while every build still went to redis. If the pod env is missing, sccache degrades
Expand Down Expand Up @@ -80,13 +80,13 @@ jobs:
npm --prefix engine/web ci --ignore-scripts
npm --prefix engine/web run build
- name: Lint dashboard client (eslint + jsx-a11y)
# JEF-499: the accessibility gate. eslint-plugin-jsx-a11y (recommended, mapped onto Preact
# the accessibility gate. eslint-plugin-jsx-a11y (recommended, mapped onto Preact
# JSX) fails the build on an a11y authoring regression in engine/web/src; the axe route-smoke
# (the runtime half) runs in the `test` job's `npm run test`. Deps were installed by the
# `npm ci --ignore-scripts` above.
run: npm --prefix engine/web run lint
- name: Typecheck dashboard client (tsc --noEmit)
# JEF-399: eslint's no-unused-vars (JEF-499) catches unused imports; it does not type-check.
# eslint's no-unused-vars catches unused imports; it does not type-check.
# `tsc --noEmit` over engine/web/src (tsconfig.json: checkJs, jsx: react-jsx / preact, offline —
# no `.d.ts` fetch) fails the build on a real type error (e.g. a JSX prop of the wrong type)
# slipping into the bundle. Deps were installed by the `npm ci --ignore-scripts` above.
Expand All @@ -105,7 +105,7 @@ jobs:
runs-on: protector-runners
env:
# sccache backend comes from the runner pod's `sccache-r2` envFrom -- see the
# note on the first job. The redis backend is deliberately unset (JEF-564).
# note on the first job. The redis backend is deliberately unset.
RUSTC_WRAPPER: sccache
CARGO_INCREMENTAL: "0"
# Authenticate binstall's GitHub REST API calls (release-artifact lookups). The
Expand Down Expand Up @@ -138,9 +138,9 @@ jobs:
npm --prefix engine/web ci --ignore-scripts
npm --prefix engine/web run build
- name: Dashboard client tests
# The v4 Preact client's offline unit + interaction tests (vitest + jsdom, ADR-0025 /
# JEF-397): the keyed reconcile, the state-preservation (JEF-351) acceptance test, client
# escaping, the honesty empty-states, and the JEF-499 vitest-axe route-smoke (every top-level
# The v4 Preact client's offline unit + interaction tests (vitest + jsdom, ADR-0025):
# the keyed reconcile, the state-preservation acceptance test, client
# escaping, the honesty empty-states, and the vitest-axe route-smoke (every top-level
# view + AuthGate + status strip asserted free of serious/critical axe violations — an a11y
# regression goes red here). `npm ci` above installed the dev toolchain.
run: npm --prefix engine/web run test
Expand Down Expand Up @@ -183,7 +183,7 @@ jobs:
contents: read
packages: write
id-token: write
attestations: write # JEF-275: write the Sigstore-signed SLSA build-provenance attestation
attestations: write # write the Sigstore-signed SLSA build-provenance attestation
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -236,7 +236,7 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# sccache's R2 backend for the in-image cargo build (JEF-584, ADR-0020).
# sccache's R2 backend for the in-image cargo build (ADR-0020).
# `secret-envs` (key=envname) reads these straight out of the RUNNER POD's env,
# where the `sccache-r2` Secret is injected via envFrom (cluster repo:
# charts/actions/runners/values-protector.yaml) — the repo has no Actions secret
Expand Down Expand Up @@ -272,7 +272,7 @@ jobs:
[[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "missing/invalid build digest: '$DIGEST'" >&2; exit 1; }
echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
# Sigstore-signed SLSA build-provenance attestation (keyless: Fulcio + Rekor), pushed to ghcr
# as an OCI referrer. This is what protector's own provenance observer VERIFIES (JEF-275): the
# as an OCI referrer. This is what protector's own provenance observer VERIFIES: the
# SLSA v1 predicate carries this repo + workflow as builder.id + workflow.repository, and the
# keyless Fulcio/Rekor chain is what trips `keyless_verified` → the "verified" posture. The
# buildx `provenance: true` above is the UNSIGNED variant embedded in the image index, which
Expand Down
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# the rust:1-bookworm builder is built on bookworm so the two stay in sync and the
# dynamically-linked binary loads on the slim runtime.
# Pulled via mirror.gcr.io (Google's Docker Hub pull-through cache) — the homelab
# buildkit's shared IP exhausts Docker Hub's anonymous quota → 429 (JEF-78).
# buildkit's shared IP exhausts Docker Hub's anonymous quota → 429.
# Node stage (ADR-0025): build the Preact dashboard bundle from source. The Rust builder
# `include_str!`s engine/web/dist/dashboard.js, which is gitignored (built, never
# committed) — so it must be produced here and COPYed in before `cargo build`. This
# fetches preact+esbuild-wasm from npm exactly as the cargo stages fetch crates from
# crates.io; zero-egress is scoped to the RUNNING engine, not the build (ADR-0025). Pulled
# via mirror.gcr.io for the same Docker Hub quota reason as the cargo base (JEF-78).
# via mirror.gcr.io for the same Docker Hub quota reason as the cargo base.
# `npm ci --ignore-scripts` kills install hooks; the build uses esbuild-WASM (arch-neutral,
# so the same command works on the amd64 and arm64 native builders — no per-arch esbuild
# binary to resolve).
Expand All @@ -34,11 +34,11 @@ RUN apt-get update \
# glibc. (Changing CFLAGS also reruns aws-lc-sys' build script, rebuilding a stale,
# toolchain-mismatched object left in the build cache.)
ENV CFLAGS=-std=gnu17
# sccache (JEF-84) is the dep-caching layer now — it shares the rustc object cache with the
# sccache is the dep-caching layer now — it shares the rustc object cache with the
# in-cluster Redis (cluster repo charts/sccache), reached via the meshed BuildKit's own identity,
# so a workspace dep compiled by ANY repo's image build (or the CI test build) is reused here.
# cargo-chef was REMOVED: sccache + cargo-chef's `cook` fight over the shared /app/target dir and
# abort with "Failed to open file for hashing: …/lib*.rmeta" (JEF-389) — a conflict that is
# abort with "Failed to open file for hashing: …/lib*.rmeta" — a conflict that is
# backend-independent (it fails on the local fallback too). A single plain `cargo build` compiles
# deps then workspace crates in dependency order, so every `--extern` .rmeta exists when sccache
# hashes it. sccache is a HARD GATE here — if it can't start against Redis the build FAILS (no
Expand All @@ -54,7 +54,7 @@ RUN set -eux; ver=0.16.0; \
| tar -xz -C /usr/local/bin --strip-components=1 "sccache-v${ver}-${a}-unknown-linux-musl/sccache"
ENV RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0
# sccache backend = the shared Cloudflare R2 bucket (cluster repo: charts/sccache,
# ADR-0020, JEF-584), replacing the in-cluster Redis this used to hardcode. Config +
# ADR-0020), replacing the in-cluster Redis this used to hardcode. Config +
# bucket-scoped token arrive as BuildKit build SECRETS below — never ENV or a build-arg,
# both of which persist in `docker history` on every image we push to ghcr.
#
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ like Cilium/Calico).

`/metrics` exposes the engine's OTLP instruments (no-op unless an OTLP endpoint is
configured). Among them, the **runtime-corroboration coverage** gauges mirror the same
per-node liveness classification the dashboard readiness row reads (JEF-308
[JEF-422]), so an operator watching only `/metrics` sees the same blind count the UI
per-node liveness classification the dashboard readiness row reads (→
), so an operator watching only `/metrics` sees the same blind count the UI
does. They are **counts only — no per-node label dimension** (node names are
attacker-influenceable, so a per-node series would be a cardinality/DoS vector), and
they exclude out-of-scope reporters (nodes the agent isn't scheduled on):
Expand Down
8 changes: 4 additions & 4 deletions agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# DaemonSet's securityContext, never baked into the image.

# bookworm-based to match the bookworm-slim runtime's glibc 2.36.
# Via mirror.gcr.io (Docker Hub pull-through) to dodge the anonymous 429 (JEF-78).
# Via mirror.gcr.io (Docker Hub pull-through) to dodge the anonymous 429.
FROM mirror.gcr.io/library/rust:1-bookworm AS builder
WORKDIR /app
# cmake: reqwest's rustls provider (aws-lc-rs → aws-lc-sys) is a C build. zstd: unpacks
Expand All @@ -20,7 +20,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends cmake curl ca-certificates zstd \
&& rm -rf /var/lib/apt/lists/*
ENV CFLAGS=-std=gnu17
# sccache (JEF-84) is the dep-caching layer — it shares the rustc object cache with the in-cluster
# sccache is the dep-caching layer — it shares the rustc object cache with the in-cluster
# Redis (cluster repo charts/sccache), reached via the meshed BuildKit's own identity, so a
# workspace dep compiled by ANY repo's image build (or the CI test build) is reused here. sccache
# is a HARD GATE — if it can't start against Redis the build FAILS (no fallback). The build
Expand All @@ -35,7 +35,7 @@ RUN set -eux; ver=0.16.0; \
ENV RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0
# Prebuilt bpf-linker (sha256-pinned, per-arch) + nightly/rust-src for the bpf target's
# build-std. Prebuilt so the image build stays light (no LLVM source build).
# ARCH-AWARE: with native multi-arch builders (JEF-295/#161) this builder stage runs on
# ARCH-AWARE: with native multi-arch builders (#161) this builder stage runs on
# the TARGET arch, so a hardcoded aarch64 bpf-linker Syntax-errors when run on the amd64
# build leg ("bpf-linker: 1: Syntax error"). Pick the musl build by TARGETARCH
# (arm64->aarch64, amd64->x86_64), keeping the per-arch SHA-256 pin.
Expand Down Expand Up @@ -63,7 +63,7 @@ COPY agent/ agent/
COPY behavior/ behavior/
WORKDIR /app/agent
# Workspace target is /app/agent/target; the excluded ebpf crate keeps its own.
# sccache backend = the shared Cloudflare R2 bucket (charts/sccache, ADR-0020, JEF-584).
# sccache backend = the shared Cloudflare R2 bucket (charts/sccache, ADR-0020).
# Config + bucket-scoped token arrive as build SECRETS, never ENV/build-arg (those persist
# in `docker history`). Fail-soft: scripts/start-sccache-docker.sh probes, retries, then
# degrades to a local disk cache rather than failing the build.
Expand Down
Loading
Loading