feat(Cubemaster): opt-in envd injection for image-based templates#626
feat(Cubemaster): opt-in envd injection for image-based templates#6260717dn6hyc8-byte wants to merge 4 commits into
Conversation
Review: PR #626 - opt-in envd injection for image-based templatesSummaryThis PR is well-structured and clearly motivated. The injection is opt-in (disabled by default), the fingerprinting correctly invalidates cached artifacts when the envd binary changes, and stripPerRequestEnvdAnnotations prevents users from manually enabling envd injection on sandbox requests. Tests are comprehensive for the core injection and sidecar paths. There are a few issues worth addressing before merging. Security1. EnvdPath accepted via HTTP API without path validation (envd_inject.go:29-38) CreateTemplateFromImageReq.EnvdPath is deserialized from json and passed directly to os.ReadFile with no filepath.Clean, allowlist, or symlink validation. An HTTP client could read arbitrary readable host files (credentials, TLS keys, /etc/shadow) by specifying their path: the contents would be embedded in the template rootfs artifact. This is an opt-in flag behind a feature gate that requires explicit --enable-inject-envd CLI interaction, so the risk in practice depends on who can reach the HTTP endpoint. Still, defense-in-depth applies: normalizeTemplateImageRequest should strip or validate EnvdPath for HTTP-initiated requests, or at minimum apply filepath.Clean and reject .. components. Additionally, EnvdPath is persisted in database job records without sanitization (unlike RegistryPassword which is zeroed before serialization in fingerprint.go:76). This should be zeroed as well. Code Quality2. Inconsistent opt-in sentinel between packages (envd_sidecar.go:22) envd_sidecar.go checks val != "true" as a bare string literal, while envd_inject.go defines envdInjectAnnotationOptIn = "true" as a package-private constant. The sidecar cannot reference it. If the opt-in value ever changes, the sidecar silently stops matching. Export the constant to constants.go so both packages share the same sentinel. 3. Potential duplicate port in ExposedPorts (envd_sidecar.go:38) injectEnvdSidecar unconditionally appends port 49983 to out.ExposedPorts before getExposedPorts runs in ConstructCubeletReq. If the template exposed-ports annotation already includes 49983, the port appears twice. Check for the port before appending, or deduplicate after both calls. 4. Error message references CLI flag in server-side code (envd_inject.go:60) The error message mentions --envd-path but prepareEnvdInjectionPayload is also called from the HTTP API path where --envd-path is meaningless. Use a neutral message like "set CUBE_MASTER_ENVD_HOST_DIR or provide envd_path in the request". Test Coverage5. Notable coverage gaps in guard clauses
VerdictNo blocking issues, but the EnvdPath path-validation concern (item 1) should be addressed before merge. Items 2-5 are moderate-quality improvements. |
|
I want to understand the motivation behind this PR and under what scenarios it is necessary. I feel that this goes against common usage—since it's built from a container image, why would we need to additionally inject an envd? This will also affect the container's startup commands and such. I find the design of this PR very strange. |
For example, in development-environment / code-sandbox scenarios, we want to quickly build interactive development sandboxes from existing base images. Therefore, this PR injects |
Indeed, you make a good point. From that perspective, automatically injecting envd is indeed a great idea! Thank you for your PR. I'll study the code in your PR over the next couple of days. |
|
0fdacf3 to
7fc6cb8
Compare
Thanks for the review. I have resolved these problems:
Please take another look when convenient. |
7fc6cb8 to
c186e32
Compare
|
There is a conflict between the current code and the main branch. You need to rebase your code based on the latest main branch and resubmit it |
| ExposedPorts []int32 `json:"exposed_ports,omitempty"` | ||
| DistributionScope []string `json:"distribution_scope,omitempty"` | ||
| ContainerOverrides *ContainerOverrides `json:"container_overrides,omitempty"` | ||
| EnvdPath string `json:"envd_path,omitempty"` |
There was a problem hiding this comment.
Why expose this field in the HTTP API?
|
|
||
| if strings.HasPrefix(k, "cube.container.") { | ||
| c.Annotations[k] = v | ||
| } |
There was a problem hiding this comment.
What is the relationship between this and envd inject?
Signed-off-by: liujie <254906391+0717dn6hyc8-byte@users.noreply.github.com>
Signed-off-by: liujie <254906391+0717dn6hyc8-byte@users.noreply.github.com>
Signed-off-by: liujie <254906391+0717dn6hyc8-byte@users.noreply.github.com>
Signed-off-by: liujie <254906391+0717dn6hyc8-byte@users.noreply.github.com>
c186e32 to
d9c356d
Compare
| func envdHostPath(req *types.CreateTemplateFromImageReq) string { | ||
| if req != nil && req.EnvdPath != "" { | ||
| return req.EnvdPath | ||
| } | ||
| dir := os.Getenv(envdHostDirEnv) | ||
| if dir == "" { | ||
| dir = envdHostDirDefault | ||
| } | ||
| return filepath.Join(dir, envdBinaryName) | ||
| } |
There was a problem hiding this comment.
Security: No path validation on EnvdPath input
req.EnvdPath is accepted via the HTTP API (json:"envd_path,omitempty" on CreateTemplateFromImageReq.EnvdPath) and passed directly to os.ReadFile without filepath.Clean, allowlist checks, or symlink resolution. An HTTP client could read arbitrary readable files from the CubeMaster host (e.g., /etc/shadow, TLS keys) by specifying their path, and the content would be embedded into the template rootfs artifact.
At minimum, apply filepath.Clean and reject paths containing .. components. The canonical validation in normalizeTemplateImageRequest should strip or validate this field for HTTP-initiated requests.
|
|
||
| func injectEnvdSidecar(ctx context.Context, req *types.CreateCubeSandboxReq, out *cubebox.RunCubeSandboxRequest) { | ||
| val, ok := req.Annotations[constants.CubeAnnotationsInjectEnvd] | ||
| if !ok || val != "true" { |
There was a problem hiding this comment.
Inconsistent opt-in sentinel
This check uses a bare string literal "true", while the templatecenter package defines envdInjectAnnotationOptIn = "true" as a package-private constant. If the opt-in value is ever updated (e.g., to "1" or "yes"), the sidecar will silently stop matching. Consider exporting the constant to constants.go so both packages share the same sentinel value.
| return | ||
| } | ||
| wrapEntrypointForEnvd(mainContainer) | ||
| out.ExposedPorts = append(out.ExposedPorts, envdDefaultPort) |
There was a problem hiding this comment.
Potential duplicate port in ExposedPorts
injectEnvdSidecar runs before getExposedPorts in ConstructCubeletReq. If the template's exposed-ports annotation already includes 49983, the port will appear twice in the final slice. Consider checking out.ExposedPorts for the port before appending, or deduplicating after both calls.
| srcPath := envdHostPath(req) | ||
| data, err := os.ReadFile(srcPath) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("envd-inject: read %q (set %s or --envd-path to override): %w", srcPath, envdHostDirEnv, err) |
There was a problem hiding this comment.
Error message references CLI flag in server-side code
This error message mentions --envd-path, which is a CLI flag. When prepareEnvdInjectionPayload is called from the HTTP API path (not the CLI), the mention of --envd-path is confusing. Consider using a server-neutral message like "set %s or provide envd_path in the request".
Summary
This PR introduces an optional envd injection path for templates created from OCI images. When enabled, CubeMaster embeds a host-side envd binary into the template root filesystem before sealing the ext4 artifact. It also records the binary's SHA256 in the template fingerprint and marks the generated template request, so restored sandboxes can start the embedded envd using an entrypoint wrapper.
By default, this injection feature is disabled to maintain the existing behavior for the community.
Motivation
Some CubeSandbox users need an envd process to be available automatically in restored sandboxes, so developer tooling can attach without requiring image authors to modify every base image manually.
What's changed
1. CLI opt-in controls
cubemastercli template create-from-image--enable-inject-envdto explicitly enableenvdinjection.cubemastercli template create-from-image--envd-pathto select the host-sideenvdbinary.If
--envd-pathis omitted, CubeMaster falls back to$CUBE_MASTER_ENVD_HOST_DIR/envd, then/usr/local/share/cubesandbox-envd/envd.2. Template rootfs injection
templatecenterenvdinto/usr/local/bin/envdinside the exported template rootfs before sealing the ext4 artifact.templatecentercube.master.inject_envd=trueis present in template container overrides.templatecenterenvdbinary fails the template build instead of producing an incomplete artifact.3. Artifact fingerprinting
templatecenterenvdbinary SHA256 in the template spec fingbled.templatecenterenvdinjection.This prevents reusing artifacts baked with an older
envdbinary after the selectedenvdversion changes.4. Runtime entrypoint wrapping
CubeMaster sandbox servicecube.master.inject_envd=true, wraps the main container command .CubeMaster sandbox service/usr/local/bin/envdin the background and thenexecs the original command.CubeMaster sandbox serviceenvdexposed port.CubeMaster sandbox serviceAssisted-by: Claude Code:ppio/pa/gpt-5.5