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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ jobs:
java: true
cluster: true
helm: false
- name: "Tier 19: Dependency CVE remediation"
tiers: --tier 19
java: true
cluster: true
helm: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Expand All @@ -136,6 +141,12 @@ jobs:
PYTHONDONTWRITEBYTECODE: "1"
run: python3 -m unittest discover -s integration-tests/e2e -p 'nodeprofile_fixtures_test.py' -v

- name: Test CVE sweep logic
if: contains(matrix.tiers, '--tier 19')
env:
PYTHONDONTWRITEBYTECODE: "1"
run: python3 -m unittest discover -s integration-tests/e2e -p 'cve_sweep_test.py' -v

- name: Set up Helm
if: matrix.helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
Expand Down
29 changes: 29 additions & 0 deletions docs/managed-dependency-bundles.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,35 @@ layout and cache behavior.

---

## Responding to a dependency CVE

When a bundled library (for example `log4j-core`) receives a critical CVE:

1. **Identify affected workloads from what is running.** Brewlet does not track
bundles on `JavaApplication` or on pods. Instead, map each running pod's
digest-pinned image to its managed-dependency evidence with
`brewlet inspect`. That evidence names the bundle and SBOM digests. Read the
components and purls from the bundle's CycloneDX SBOM referrer. Treat any
image you cannot trace to an SBOM as unverified, not clean.
2. **Publish a patched bundle** from the updated BOM under a new version or tag.
Bundles are immutable, and publishing one does not change running
applications.
3. **Recompose and roll out.** Run `brewlet push` again with the *same* thin
application JAR, `--dependency-bundle` pointing at the patched bundle, and
its lock. No application rebuild is required. The application layer and
`applicationJarDigest` stay the same, and only the managed classpath layer
changes. Then roll each workload to the new image digest with a
zero-downtime rolling update (for example, `maxUnavailable: 0`). Brewlet does
not swap classpath layers inside a running JVM, so a new pod on the new digest
is the unit of remediation.
4. **Verify** by repeating step 1 against the running pods.

E2E tier 19 (`integration-tests/e2e/tier19-cve-remediation.sh`) runs this
procedure end to end. It uses `integration-tests/e2e/cve_sweep.py` as the
reference SBOM sweep.

---

## Failure behavior

Managed publication fails closed. It does not silently rebuild dependency
Expand Down
31 changes: 25 additions & 6 deletions integration-tests/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ The harness does not switch branches or modify component sources. It uses
| Tool | Tiers |
|---|---|
| Go | all |
| Python 3 | 2, 4, 12, 13, 16, 17 |
| JDK 21+ | 2, 3, 7, 8, 9, 12, 14-18 |
| Docker | 3, 6, 7, 8-12, 14-18 |
| kubectl and a reachable cluster | 4-18 |
| Python 3 | 2, 4, 12, 13, 16, 17, 19 |
| JDK 21+ | 2, 3, 7, 8, 9, 12, 14-19 |
| Docker | 3, 6, 7, 8-12, 14-19 |
| kubectl and a reachable cluster | 4-19 |
| Helm | 4 (optional), 10, 15, 17 |
| OpenSSL | 4, 5, 6, 10, 11 |

Host-only tiers 1-3 need no cluster. Tiers 4-7 and 13 exercise API-server
behavior. Tiers 6, 8-12, and 14-18 require local containerd nodes that
behavior. Tiers 6, 8-12, and 14-19 require local containerd nodes that
Docker can enter, such as kind. Managed clusters skip those node-side paths. Tier 13
also proves the control-plane guard: a NodeProfile that does not set
`nodePool.includeControlPlane` never counts or schedules onto a control-plane
Expand Down Expand Up @@ -87,6 +87,18 @@ checks that the running pod keeps its original JDK and container, and that a
retired-root sweep keeps the root that pod still uses. Finally, it asserts
that `kubectl rollout restart` moves the same application image onto the
patched JDK, after which an idle sweep reclaims the retired root.
Tier 19 rehearses a critical CVE in a library shipped by a managed dependency
bundle. It publishes a bundle with a stub `log4j-core` 2.14.1, composes three
thin-JAR apps (two on that bundle, one control on an unrelated bundle), and
deploys them to a prod and a staging namespace. `e2e/cve_sweep.py` then traces
every running Brewlet pod from its digest-pinned image through `brewlet inspect`
evidence to the bundle's CycloneDX SBOM. It flags exactly the affected prod
workloads and fails closed on images it cannot trace. The tier publishes a
patched bundle (2.17.1) and recomposes the *unchanged* thin JARs onto it,
asserting that only the classpath layer changed. It rolls the Deployments by
digest (`maxUnavailable: 0`) while a client polls the Service, and asserts that
no request was dropped and that the post-remediation prod sweep is clean. There
is no in-place JVM swap: a "hot" redeploy is a recompose plus a rolling update.

**Architecture coverage.** Tiers pick up the node's architecture automatically,
but hosted runners are amd64, so `.github/workflows/e2e.yml` runs the host-only
Expand All @@ -111,6 +123,13 @@ PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \
-s integration-tests/e2e -p 'nodeprofile_fixtures_test.py' -v
```

Run tier 19's SBOM sweep logic offline (fake CLI, no cluster) with:

```bash
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \
-s integration-tests/e2e -p 'cve_sweep_test.py' -v
```

Run `./e2e/run.sh --reset` before repeating Kubernetes tiers. It removes only
Brewlet-owned labels, annotations, CRDs, runtime classes, webhook configuration,
RBAC, and fixed test namespaces. It does not delete application workloads outside
Expand All @@ -124,7 +143,7 @@ Common environment-specific skips:

- Tier 5 skips when a cluster cannot reach a host-bound webhook; tier 6 covers
the same assertions in-cluster.
- Tiers 8, 9, 12, and 14-18 skip if no schedulable local containerd node
- Tiers 8, 9, 12, and 14-19 skip if no schedulable local containerd node
can be provisioned.
- Tier 12 skips when the node's `ctr` supports neither `images unpack` nor
import-time unpack; tier 16 applies the same rule.
Expand Down
Loading
Loading