Skip to content

feat(mirror): read-only capture tap spec (NET_RAW-hardened context + pinned capture image + CaptureCommand)#5703

Merged
devantler merged 1 commit into
mainfrom
claude/mirror-capture-tap
Jul 2, 2026
Merged

feat(mirror): read-only capture tap spec (NET_RAW-hardened context + pinned capture image + CaptureCommand)#5703
devantler merged 1 commit into
mainfrom
claude/mirror-capture-tap

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Daily AI Assistant

Fixes #5702. Part of #4521 (P1 mirror-only); follows #5685's InjectTap/WaitForTap.

What

  • Hardened tap security context (non-configurable): the injected ephemeral tap now runs with capabilities: {drop: [ALL], add: [NET_RAW]}, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, and seccompProfile: RuntimeDefault. NET_RAW is the one capability passive pcap capture needs; the read-only guarantee of mirror mode rests on the tap never holding more.
  • Pinned capture-capable default image: DefaultTapImage moves from alpine:latest (inert placeholder, no capture tooling, floating tag) to docker.io/nicolaka/netshoot:v0.16 — the de-facto standard network-debug image, carries tcpdump, pinned to a release tag. WithTapImage still overrides. ⚠️ New external runtime dependency (a third-party image) — flagged for steering; alternatives were a ksail-published tap image (a whole new release artifact, disproportionate for P1) or runtime apk add tcpdump (network-dependent, mutates the container, defeats the read-only root fs).
  • CaptureCommand(port): builds the validated tcpdump invocation the upcoming exec-stream increment runs inside the tap — tcpdump -p -i any -U -w - tcp port <n> (unbuffered pcap to stdout, non-promiscuous, scoped to the target TCP port).
  • doc.go records the capture design decision (also commented on [Repo Assist] [feature]: add local-remote service mirroring (Telepresence/mirrord-style dev bridge) #4521): passive pcap via CAP_NET_RAW, delivered over the already-embedded exec channel — a userspace proxy cannot passively observe the app container's inbound traffic, and being in-path needs NET_ADMIN (= Phase 2 intercept). Consequence: mirror-only mode needs no reverse tunnel; the tunnel returns in Phase 2.

Notes

  • No CLI surface change; sleep infinity stays the inert injection command, so WaitForTap semantics are untouched. Capture runs as a later exec, not as the container command — injection and capture lifecycles stay independent.
  • PSS note: NET_RAW is rejected under the restricted Pod Security profile; mirror is a dev-cluster inner-loop tool, and the failure mode is an explicit admission error, not silent breakage.

Validation

  • go build ./... green; go test ./pkg/svc/... 4459 passed / 78 packages; golangci-lint run ./pkg/svc/mirror/... 0 issues.
  • New tests: TestCaptureCommand_* (invocation shape, port bounds, rejection) and assertHardenedTapSecurityContext pinning the security context in TestInjectTapDefaults.

Next increments (per #5702)

  1. Exec-based capture session streaming pcap to the local process (pure-Go pcapgo reader — new Go dep, will be flagged).
  2. workload mirror CLI wiring.
  3. Phase 2 intercept (in-path, NET_ADMIN, reverse tunnel).

…pinned capture image, CaptureCommand

The injected tap ephemeral container now carries a hardened, non-configurable
security context (drop ALL / add NET_RAW, no privilege escalation, read-only
root filesystem, RuntimeDefault seccomp) — NET_RAW being the one capability
passive pcap capture needs — and defaults to a pinned capture-capable image
(nicolaka/netshoot:v0.16, carries tcpdump; WithTapImage still overrides).
CaptureCommand builds the validated tcpdump invocation (unbuffered pcap to
stdout, non-promiscuous, scoped to the target TCP port) that the upcoming
exec-stream increment runs inside the tap.

Capture design recorded on the epic: passive pcap via NET_RAW over the
embedded exec channel; no reverse tunnel needed for mirror-only mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

MegaLinter analysis: Success

✅ Linters with no issues

actionlint, bash-exec, git_diff, hadolint, jscpd, jsonlint, lychee, markdown-table-formatter, markdownlint, prettier, prettier, shellcheck, shfmt, stylelint, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@github-code-quality

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Go

Go / code-coverage/go

The overall coverage in the branch remains at 64%, unchanged from the branch.

Show a code coverage summary of the most impacted files.
File d5bc194 cc6dce9 +/-
pkg/client/reconciler/poll.go 84% 80% -4%
pkg/svc/provisi...etzner/token.go 77% 73% -4%
pkg/svc/provisi...storage_gate.go 83% 86% +3%
pkg/svc/mirror/inject.go 0% 92% +92%
pkg/svc/mirror/capture.go 0% 100% +100%

Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@devantler devantler marked this pull request as ready for review July 2, 2026 18:01
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 26368937-69fe-4251-9a80-d4c7cf8e828e

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4562c and cc6dce9.

📒 Files selected for processing (5)
  • pkg/svc/mirror/capture.go
  • pkg/svc/mirror/capture_test.go
  • pkg/svc/mirror/doc.go
  • pkg/svc/mirror/inject.go
  • pkg/svc/mirror/inject_test.go

📝 Walkthrough

Walkthrough

Adds a CaptureCommand helper in a new pkg/svc/mirror/capture.go file that builds a validated tcpdump argument list for TCP port capture, with unit tests. Updates inject.go to pin the tap image to netshoot:v0.16 and apply a hardened SecurityContext (dropped capabilities except NET_RAW, no privilege escalation, read-only root filesystem, RuntimeDefault seccomp), with corresponding test updates and revised doc.go design commentary.

Changes

Mirror capture and tap hardening

Layer / File(s) Summary
CaptureCommand builder and tests
pkg/svc/mirror/capture.go, pkg/svc/mirror/capture_test.go
Adds ErrInvalidCapturePort sentinel and CaptureCommand(port int) producing a validated tcpdump argv slice; tests cover exact output, port bounds (1, 65535), and invalid ports (0, -1, 65536).
Tap image pin and SecurityContext hardening
pkg/svc/mirror/inject.go, pkg/svc/mirror/inject_test.go, pkg/svc/mirror/doc.go
Changes DefaultTapImage to docker.io/nicolaka/netshoot:v0.16, adds tapSecurityContext() applied to the injected ephemeral container (drop all capabilities except NET_RAW, no privilege escalation, read-only root filesystem, RuntimeDefault seccomp), updates tests to assert the hardened context, and rewrites Phase 1 documentation to describe passive pcap capture over the exec channel.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant CaptureCommand
    participant InjectTap
    participant TapContainer

    Caller->>CaptureCommand: CaptureCommand(port)
    CaptureCommand->>CaptureCommand: validate port range
    CaptureCommand-->>Caller: tcpdump argv or ErrInvalidCapturePort

    Caller->>InjectTap: InjectTap(...)
    InjectTap->>InjectTap: tapSecurityContext()
    InjectTap->>TapContainer: create EphemeralContainer(image, securityContext)
    TapContainer-->>InjectTap: container injected (sleep infinity)
Loading

Related issues: #5702 (direct)

Suggested labels: enhancement, mirror, security

Suggested reviewers: devantler

🐰 A tap sneaks in with NET_RAW alone,
No shell, no root, just packets flown,
tcpdump whispers on stdout's stream,
A pinned netshoot fulfills the scheme,
Read-only mirrors, safely grown.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main mirror capture tap changes, including hardened context, pinned image, and CaptureCommand.
Description check ✅ Passed The description is directly related to the changeset and matches the implemented mirror capture tap work.
Linked Issues check ✅ Passed The PR appears to satisfy #5702 by hardening the tap, pinning the capture image, adding CaptureCommand, and updating the docs.
Out of Scope Changes check ✅ Passed The changes stay within the mirror capture tap scope and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/mirror-capture-tap

Comment @coderabbitai help to get the list of available commands.

@devantler devantler merged commit 9df7499 into main Jul 2, 2026
42 checks passed
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Jul 2, 2026
@devantler devantler deleted the claude/mirror-capture-tap branch July 2, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

feat(mirror): read-only capture tap — NET_RAW-hardened spec + pcap capture command (#4521 P1)

1 participant