Skip to content

feat(Cubemaster): opt-in envd injection for image-based templates#626

Open
0717dn6hyc8-byte wants to merge 4 commits into
TencentCloud:masterfrom
0717dn6hyc8-byte:feat/envd-bake-into-template-rootfs
Open

feat(Cubemaster): opt-in envd injection for image-based templates#626
0717dn6hyc8-byte wants to merge 4 commits into
TencentCloud:masterfrom
0717dn6hyc8-byte:feat/envd-bake-into-template-rootfs

Conversation

@0717dn6hyc8-byte

@0717dn6hyc8-byte 0717dn6hyc8-byte commented Jun 23, 2026

Copy link
Copy Markdown

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

Component Change
cubemastercli template create-from-image Adds --enable-inject-envd to explicitly enable envd injection.
cubemastercli template create-from-image Adds --envd-path to select the host-side envd binary.

If --envd-path is omitted, CubeMaster falls back to $CUBE_MASTER_ENVD_HOST_DIR/envd, then /usr/local/share/cubesandbox-envd/envd.

2. Template rootfs injection

Component Behavior
templatecenter Copies envd into /usr/local/bin/envd inside the exported template rootfs before sealing the ext4 artifact.
templatecenter Injection only happens when cube.master.inject_envd=true is present in template container overrides.
templatecenter Missing or unreadable envd binary fails the template build instead of producing an incomplete artifact.

3. Artifact fingerprinting

Component Behavior
templatecenter Includes the injected envd binary SHA256 in the template spec fingbled.
templatecenter Leaves fingerprints unchanged for templates that do not enable envd injection.

This prevents reusing artifacts baked with an older envd binary after the selected envd version changes.

4. Runtime entrypoint wrapping

Component Behavior
CubeMaster sandbox service When a template carries cube.master.inject_envd=true, wraps the main container command .
CubeMaster sandbox service Starts /usr/local/bin/envd in the background and then execs the original command.
CubeMaster sandbox service Adds the default envd exposed port.
CubeMaster sandbox service Skips wrapping if the command is already wrapped.

Assisted-by: Claude Code:ppio/pa/gpt-5.5

@cubesandboxbot

cubesandboxbot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review: PR #626 - opt-in envd injection for image-based templates

Summary

This 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.


Security

1. 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 Quality

2. 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 Coverage

5. Notable coverage gaps in guard clauses

  • injectEnvdSidecar: non-cubebox instance-type early return and empty-containers guard are untested
  • stripPerRequestEnvdAnnotations: nil request and empty-annotations guards are untested
  • buildOriginalCommand: empty-command fallback returning ["sleep", "infinity"] is untested

Verdict

No blocking issues, but the EnvdPath path-validation concern (item 1) should be addressed before merge. Items 2-5 are moderate-quality improvements.

Comment thread CubeMaster/pkg/templatecenter/envd_inject.go Outdated
Comment thread CubeMaster/pkg/templatecenter/fingerprint.go Outdated
Comment thread CubeMaster/pkg/service/sandbox/envd_sidecar.go Outdated
Comment thread CubeMaster/pkg/templatecenter/envd_inject.go Outdated
Comment thread CubeMaster/pkg/templatecenter/fingerprint.go Outdated
Comment thread CubeMaster/pkg/service/sandbox/envd_sidecar.go Outdated
Comment thread CubeMaster/pkg/templatecenter/envd_inject.go Outdated
Comment thread CubeMaster/pkg/templatecenter/fingerprint.go Outdated
Comment thread CubeMaster/pkg/templatecenter/envd_inject.go Outdated
Comment thread CubeMaster/pkg/service/sandbox/envd_sidecar.go Outdated
Comment thread CubeMaster/pkg/templatecenter/envd_inject.go Outdated
@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat: Add support for default injection of envd in cubemastercli #0 feat: opt-in envd injection for image-based templates Jun 23, 2026
@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat: opt-in envd injection for image-based templates feat(cubemaster): opt-in envd injection for image-based templates Jun 23, 2026
@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat(cubemaster): opt-in envd injection for image-based templates feat(Cubemaster): opt-in envd injection for image-based templates Jun 23, 2026
@fslongjin

Copy link
Copy Markdown
Member

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.

@0717dn6hyc8-byte

Copy link
Copy Markdown
Author

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. envd provides the access layer for the development environment, but many base images do not have it preinstalled. Maintaining a large number of derived images only to integrate envd would add unnecessary cost and complexity.

Therefore, this PR injects envd during template rootfs creation. This allows those base images to gain a consistent development runtime capability without modifying their Dockerfiles or rebuilding the source images.

@fslongjin

Copy link
Copy Markdown
Member

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. envd provides the access layer for the development environment, but many base images do not have it preinstalled. Maintaining a large number of derived images only to integrate envd would add unnecessary cost and complexity.

Therefore, this PR injects envd during template rootfs creation. This allows those base images to gain a consistent development runtime capability without modifying their Dockerfiles or rebuilding the source images.

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.

@kinwin-ustc

Copy link
Copy Markdown
Collaborator
  1. You should add your DCO;

  2. Please modify the CubeBot review comments;

  3. Please add corresponding documentation for the newly added CubeMasterCLI parameters.

@0717dn6hyc8-byte 0717dn6hyc8-byte force-pushed the feat/envd-bake-into-template-rootfs branch from 0fdacf3 to 7fc6cb8 Compare July 1, 2026 08:14
@0717dn6hyc8-byte

Copy link
Copy Markdown
Author
  1. You should add your DCO;
  2. Please modify the CubeBot review comments;
  3. Please add corresponding documentation for the newly added CubeMasterCLI parameters.

Thanks for the review. I have resolved these problems:

  • Added DCO sign-off to all commits.
  • Addressed the CubeBot review comments, including envd path consistency, fingerprint error handling, redundant envd reads, misleading log wording, and the unused error return.
  • Added documentation for the new CubeMasterCLI parameters in both English and Chinese:
    • docs/guide/tutorials/template-from-image.md
    • docs/zh/guide/tutorials/template-from-image.md

Please take another look when convenient.

@0717dn6hyc8-byte 0717dn6hyc8-byte force-pushed the feat/envd-bake-into-template-rootfs branch from 7fc6cb8 to c186e32 Compare July 1, 2026 09:04
@kinwin-ustc

Copy link
Copy Markdown
Collaborator

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"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why expose this field in the HTTP API?


if strings.HasPrefix(k, "cube.container.") {
c.Annotations[k] = v
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@0717dn6hyc8-byte 0717dn6hyc8-byte force-pushed the feat/envd-bake-into-template-rootfs branch from c186e32 to d9c356d Compare July 1, 2026 13:52
Comment on lines +29 to +38
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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants