fix(capture): make HostPath a subpath under a fixed base dir#2335
Conversation
There was a problem hiding this comment.
I sent some comments on the current approach. I'd like to suggest a different approach:
Instead of allowing the user to set any folder in the host, use the value provided by the user in outputConfiguration.hostPath, to create that as a subpath inside a default folder. E.g. defaultOutputDir = /captures and user sets outputConfiguration.hostPath=my_capture123, then it will be saved to /captures/my_capture123
Keep the validation against using .. to go outside of that folder, and we don't need to create a breaking change which is to create a new list of allowedPrefixes.
Thanks @alexcastillo Agreed. I implemented exactly that (user value is treated as a subpath under an operator-configured base dir, default /var/log/retina/captures); pushed in the last commit. The only remaining breaking change is that absolute hostPath values are now rejected loudly instead of being silently relocated, to avoid surprising on-disk moves on upgrade. |
cf829e8 to
05a0423
Compare
|
@SRodi have you reviewed the download capture flow? Will they download from right location with your change? I noticed that there's no change in |
0dc68fb to
a743a5a
Compare
good catch! thanks @alexcastilio! We need to automate capture and download tests to avoid manual testing for each capture change. Here is a manual test for now
|
There was a problem hiding this comment.
Pull request overview
This PR hardens the capture “HostPath” output option by treating Capture.spec.outputConfiguration.hostPath as a relative subpath and resolving it under an operator-controlled absolute base directory, preventing Capture CR authors from mounting arbitrary host directories into privileged pods.
Changes:
- Added HostPath validation + resolution helper (with tests) to block absolute paths and traversal, and to ensure the resolved path stays under an operator-configured base directory.
- Updated capture job rendering to use the resolved HostPath consistently across volume
HostPath.Path,VolumeMount.MountPath, annotations, and theCAPTURE_OUTPUT_LOCATION_HOST_PATHenv var. - Introduced/configured
captureHostPathBaseDirvia operator config + Helm values, updated CLI flags/defaults, and refreshed docs/samples/CRD comments.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/capture/podblobupload.yaml | Updates sample HostPath to a relative subpath. |
| samples/capture/nodeblobupload.yaml | Updates sample HostPath to a relative subpath. |
| samples/capture/node-s3upload-minio.yaml | Updates sample HostPath to a relative subpath. |
| samples/capture/node-s3upload-aws.yaml | Updates sample HostPath to a relative subpath. |
| samples/capture/capture-specific-pod-on-a-node.yaml | Updates sample HostPath to a relative subpath. |
| pkg/controllers/operator/capture/controller.go | Removes redundant error logging when job translation fails. |
| pkg/config/capture.go | Adds CaptureHostPathBaseDir to operator capture config. |
| pkg/capture/utils/annotations.go | Adds resolved-host-path plumbing for pod annotations. |
| pkg/capture/utils/annotations_test.go | Adds unit tests for new annotation behavior. |
| pkg/capture/hostpath_validation.go | Introduces HostPath validation + resolution under a base dir. |
| pkg/capture/hostpath_validation_test.go | Adds unit tests for HostPath validation and containment. |
| pkg/capture/crd_to_job.go | Validates HostPath early and uses resolved paths for volumes/mounts/env/annotations. |
| pkg/capture/crd_to_job_test.go | Updates tests for relative HostPath semantics and resolved path usage. |
| operator/config/config.go | Defaults/cleans/validates the configured HostPath base dir. |
| docs/05-Concepts/CRDs/Capture.md | Documents HostPath as relative subpath under a base dir. |
| docs/04-Captures/04-managed-storage-account.md | Updates examples to use relative HostPath. |
| docs/04-Captures/03-crd.md | Updates CRD examples to use relative HostPath. |
| docs/02-Installation/03-Config.md | Documents new Helm/operator config value capture.hostPathBaseDir. |
| deploy/standard/manifests/controller/helm/retina/values.yaml | Adds Helm value capture.hostPathBaseDir. |
| deploy/standard/manifests/controller/helm/retina/templates/operator-configmap.yaml | Wires Helm value into operator configmap. |
| crd/api/v1alpha1/capture_types.go | Updates CRD godoc for HostPath behavior/validation. |
| cli/cmd/capture/download_test.go | Fixes test isolation by resetting flag-bound globals between subtests. |
| cli/cmd/capture/create.go | Changes CLI HostPath defaults, adds --host-path-base-dir, and passes base dir into translator config. |
| cli/cmd/capture/create_test.go | Updates/extends CLI tests for new HostPath semantics and config propagation. |
| cli/cmd/capture/capture.go | Extends CLI options struct with hostPathBaseDir. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
A Capture CR's OutputConfiguration.HostPath was passed unchecked into corev1.HostPathVolumeSource, letting a CRD author mount any host directory RW into the privileged capture pod and write pcap output anywhere on the node filesystem. Add validateHostPath which requires an absolute, traversal-free path that, after cleaning, equals or is nested under one of the configured allowed prefixes (with a trailing-separator check to prevent prefix-confusion such as /foo vs /foo-evil). Wire it into both validateCapture (early reject) and initJobTemplate (uses the cleaned path for the Volume and VolumeMount). Add CaptureConfig.CaptureHostPathAllowedPrefixes, default it to [/var/log/retina/captures] in the operator config loader, and expose it via the Helm value capture.hostPathAllowedPrefixes. The CLI auto- allows the user-supplied --host-path so interactive UX is preserved; the threat model is CRD-author abuse via the operator. Tests: new table-driven coverage for traversal, prefix-confusion, defaulting, and rejection; existing crd_to_job tests updated to configure /tmp/capture as an allowed prefix.
OutputConfiguration.HostPath was passed unchecked into
HostPathVolumeSource.Path, letting any Capture CR author mount an
arbitrary host directory RW into the privileged capture pod.
Reinterpret the field as a relative subpath name that the operator
joins under a single, operator-configured base directory
(capture.hostPathBaseDir, default /var/log/retina/captures). Reject
absolute paths, Windows drive-letter paths, and any '..' segment
loudly during reconciliation; do not silently rewrite. The joined,
cleaned path is used for both the host volume and the workload's
mount path / output env var so the capture writes where the volume
is actually mounted.
The CLI follows the same rule via a new --host-path-base-dir flag;
the previous CLI auto-allow hack is removed. Samples, CRD field
godoc, generated CRD manifest, and user docs are updated.
BREAKING CHANGE: Capture CRs supplying an absolute HostPath are now
rejected. Migrate to a bare subpath name; the mount becomes
${capture.hostPathBaseDir}/${value}
65af10e to
e01f8d0
Compare
bd91f65 to
e920a64
Compare

Problem
Capture.spec.outputConfiguration.hostPathwas passed unchecked intocorev1.HostPathVolumeSource.Path. Any user able to create aCaptureCR could mount an arbitrary host directory RW into a privileged pod and write artifacts anywhere on the node.Fix
HostPathis now a relative subpath name. The operator mounts${baseDir}/${hostPath}, wherebaseDiris set by the cluster operator via Helm valuecapture.hostPathBaseDir(default/var/log/retina/captures). CR authors cannot influencebaseDir.Validation rejects (loudly, no silent rewrite):
./, WindowsC:\, leading\)..segment, before or afterfilepath.CleanbaseDirThe resolved path is used for
HostPathVolumeSource.Path, the containerVolumeMount.MountPath, and theCAPTURE_OUTPUT_LOCATION_HOST_PATHenv var.Key changes
pkg/capture/hostpath_validation.go+ tests (validateHostPath, sentinel errors,DefaultHostPathBaseDir).pkg/capture/crd_to_job.go:validateCapturerejects early;initJobTemplateandobtainCaptureOutputEnvuse the resolved path.pkg/config/capture.go+operator/config/config.go: newCaptureHostPathBaseDir(defaulted, cleaned, must be absolute).capture.hostPathBaseDirinvalues.yamlandoperator-configmap.yaml.--host-pathdefault is nowretina; new--host-path-base-dirflag; auto-allow hack removed.Breaking change
Capture CRs with an absolute
hostPath(e.g./tmp/retina) are rejected. Migration: use a bare name (retina); the mount becomes/var/log/retina/captures/retina. Operators needing a different root setcapture.hostPathBaseDir.Test
go test ./pkg/capture/ ./pkg/config/ ./operator/config/ ./cli/...— passgo vet ./pkg/capture/ ./pkg/config/ ./operator/config/ ./cli/...— clean., POSIX/Windows absolute, pre-clean.., post-cleanfoo/../bar, escape, accepted nested subpaths.../foo/../barrejected with no Job created and a singleCaptureErrorcondition; bare subpath mounts at${baseDir}/${name}; overridingcapture.hostPathBaseDirrelocates the mount without changing the CR.Related Issue
If this pull request is related to any issue, please mention it here. Additionally, make sure that the issue is assigned to you before submitting this pull request.
Checklist
git commit -S -s ...). See this documentation on signing commits.Screenshots (if applicable) or Testing Completed
Negative — absolute HostPath is rejected loudly
Negative — parent segment is rejected
Negative — post-clean traversal is rejected
Positive — bare subpath is accepted
Override the base directory
Additional Notes
Add any additional notes or context about the pull request here.
Please refer to the CONTRIBUTING.md file for more information on how to contribute to this project.