Sign inSign up

jc3rny/network-debug-toolbox

By jc3rny

Updated about 1 month ago

Image
0

2.0K

jc3rny/network-debug-toolbox repository overview

network-debug-toolbox

Hardened, non-root network debugging toolbox for Kubernetes — a low-CVE, Wolfi-based alternative to nicolaka/netshoot.

Pull it into a running pod or node with kubectl debug and you get the usual network diagnostics (tcpdump, conntrack, iproute2, mtr, …) — but from a minimal, regularly rebuilt base, running as a non-root user, and still able to capture packets thanks to file capabilities.

Why

  • Secure base: Chainguard Wolfi (cgr.dev/chainguard/wolfi-base) — minimal, glibc, rebuilt frequently, typically low/zero CVE.
  • Curated toolset: only what network/system debugging needs (fewer packages = smaller attack surface): tcpdump bind-tools iproute2 iputils netcat-openbsd socat conntrack-tools iptables nftables ethtool mtr iperf3 lsof strace curl openssl jq bash. No kubectl (you run it from your workstation; bundling it + a mounted SA token is a lateral-movement risk).
  • Non-root by default (uid 65532). tcpdump/ping carry file capabilities, so capture works as non-root with the --profile=netadmin (pods) / --profile=sysadmin (nodes) debug profile — never root. (Kubernetes has no ambient caps, so file caps are required.)
  • Scanned with Trivy on every build (image vulns + Dockerfile misconfig); fails on HIGH/CRITICAL. A CycloneDX SBOM is generated and attached to the published image.

Use with kubectl debug

# published to both registries — pick either:
IMG=jc3rny/network-debug-toolbox:latest              # Docker Hub
IMG=ghcr.io/jc3rny/library/network-debug-toolbox:latest      # GHCR (no pull rate limits)

# inside a pod (packet capture needs the netadmin profile)
kubectl debug -it <pod> -n <ns> --image=$IMG --target=<container> --profile=netadmin -- bash
# then, inside: tcpdump -i any -nn 'port 8080'

# on a node / host netns (e.g. to see pre-SNAT client IPs before kube-proxy rewrites them)
kubectl debug node/<node> -it --image=$IMG --profile=sysadmin -- \
  tcpdump -i any -nn 'tcp port <nodePort>'

# save a pcap for Wireshark (no -t — a TTY corrupts the binary stream)
kubectl debug -i <pod> -n <ns> --image=$IMG --target=<container> --profile=netadmin -- \
  tcpdump -i any -w - 'port 8080' > pod.pcap

Non-root note: capture works non-root because the binaries carry file capabilities and you pass --profile=netadmin/sysadmin. Deep host filesystem access during node debug may still need a root override; non-root + caps covers network capture.

Verify capture works (inside the debug container)
id                                  # uid=65532 (non-root)
getcap "$(command -v tcpdump)"      # → cap_net_admin,cap_net_raw=ep
tcpdump -D                          # lists interfaces (no permission error)

Build & scan locally

make            # build + scan Dockerfile config + scan image (fails on HIGH/CRITICAL)
make verify     # full vulnerability report (all severities, never fails)
make verify-caps# assert tcpdump keeps its file caps (non-root capture guard)
make sbom       # CycloneDX SBOM

Trivy runs via Docker (no local install needed). Local builds are native (e.g. arm64 on Apple Silicon) for speed.

Publish

CI is split into a validation gate and a publish stage (.github/workflows/):

  • ci.yml — on pull requests: runs the reusable validate.yml gate (build + Trivy config/image scan + file-caps guard + helm lint). No secrets, no push. Make validate / build-scan the required status check on main.
  • release.yml — on push to main (→ latest), v* tags (→ semver), and a weekly rebuild: runs the same gate, then pushes multi-arch (linux/amd64,linux/arm64) image + chart to both Docker Hub and GHCR.

Published artifacts:

ArtifactDocker HubGHCR
Imagejc3rny/network-debug-toolboxghcr.io/jc3rny/library/network-debug-toolbox
Chartjc3rny/network-debug-toolbox-helmghcr.io/jc3rny/helm/network-debug-toolbox

GHCR auth uses the built-in GITHUB_TOKEN (no setup). Docker Hub needs these under Settings → Secrets and variables → Actions:

NameKindPurpose
DOCKERHUB_USERNAMEVariableDocker Hub account the image is pushed under (not sensitive — kept a variable so it stays readable in logs)
DOCKERHUB_TOKENSecretDocker Hub access token, Read & Write, all repositories

No PAT is needed — everything runs on the built-in GITHUB_TOKEN.

Images and charts are signed with cosign (keyless/OIDC); verify with cosign verify <ref> --certificate-identity-regexp '.*' --certificate-oidc-issuer https://token.actions.githubusercontent.com.

Versioned releases (release-please)

Uses Conventional Commits (feat: → minor, fix: → patch). release-please keeps a release PR that bumps the version + CHANGELOG.md + the chart's version/appVersion. You merge that PR when you want to cut a release; on merge, the versioned, signed image and chart are published automatically (release-please calls release.yml in the same run). Dependabot base-image bumps land as fix(...) so they queue into the next patch release (action bumps use ci(...) — changelog only). :latest already tracks main, so base-image patches reach users immediately, independent of cutting a versioned release.

The release PR is created by GITHUB_TOKEN, so CI does not auto-run on it. Merge it with the admin "merge without waiting for requirements" option, or close/reopen it once to trigger CI. (Dependabot PRs are unaffected — they run CI and auto-merge normally.)

To publish manually instead:

docker login
make publish IMAGE=<user>/network-debug-toolbox TAG=$(date +"%Y%m%d")

make publish forces linux/amd64, runs the full scan + verify-caps gate, pushes, then attaches the CycloneDX SBOM to the pushed image as an OCI referrer (needs orasbrew install oras). Pin the Wolfi base to a digest before production use (the Dockerfile already pins one; refresh it with the command in the Dockerfile header).

Helm chart

helm/ deploys the image as a per-node DaemonSet you exec into for node-level debugging (hardened, non-root by default — see helm/values.yaml). CI packages it and pushes it as an OCI artifact to both registries on main/v* (use either):

# GHCR
helm install ndt oci://ghcr.io/jc3rny/helm/network-debug-toolbox \
  --namespace netdebug --create-namespace
# Docker Hub
helm install ndt oci://registry-1.docker.io/jc3rny/network-debug-toolbox-helm \
  --namespace netdebug --create-namespace

# then exec into the pod on a node and capture
kubectl -n netdebug exec -it ds/ndt-network-debug-toolbox -- \
  tcpdump -i any -nn 'tcp port <port>'

hostNetwork/hostPID require the namespace to allow it under Pod Security: kubectl label ns netdebug pod-security.kubernetes.io/enforce=privileged --overwrite.

scripts/presnat-capture.sh

Helper to capture pre-SNAT traffic (real client IPs) for a LoadBalancer/NodePort Service across nodes — it discovers the VIP, service ports and nodePorts, builds the BPF filter, runs kubectl debug node/... on each node with this image, and (in pcap mode) merges the per-node captures. See ./presnat-capture.sh -h.

License

Apache-2.0 — see LICENSE.

Tag summary

Content type

Image

Digest

sha256:8fdfcffff

Size

33.8 MB

Last updated

about 1 month ago

docker pull jc3rny/network-debug-toolbox