guest: enforce app compose version policy#754
Open
kvinwang wants to merge 2 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the AppCompose schema to treat manifest_version as a string (while still accepting legacy numeric 1/2 on input) and enforces version-based policies in the guest during system setup, including a new os_version_policy.min constraint.
Changes:
- Canonicalize
manifest_versionparsing to a string with legacy numeric 1/2 compatibility and addmanifest_version_u32()helper indstack-types. - Enforce a guest-side maximum supported manifest version and enforce
os_version_policy.min(before KMS/env/docker setup) indstack-utilsystem setup. - Update SDK/UI AppCompose types to support string
manifest_versionand the newos_version_policyfield.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vmm/ui/src/composables/useVmManager.ts | Extends UI AppCompose typing for string/number manifest_version and optional os_version_policy.min. |
| sdk/python/src/dstack_sdk/get_compose_hash.py | Extends Python AppCompose typing for string/number manifest_version and adds os_version_policy. |
| sdk/js/src/get-compose-hash.ts | Extends JS SDK AppCompose typing for string/number manifest_version and adds os_version_policy. |
| sdk/go/dstack/compose_hash.go | Updates Go SDK AppCompose to accept string/number manifest_version and adds os_version_policy support. |
| guest-agent/src/rpc_service.rs | Updates guest-agent test AppCompose construction for new Rust AppCompose fields/types. |
| dstack-util/src/system_setup.rs | Adds guest enforcement for max supported manifest version + os_version_policy.min, and validates early in system setup. |
| dstack-types/src/lib.rs | Changes Rust AppCompose manifest_version to canonical string form with custom deserialization + adds os_version_policy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+35
to
+41
| // AppCompose represents the application composition structure | ||
| type AppCompose struct { | ||
| ManifestVersion *int `json:"manifest_version,omitempty"` | ||
| Name string `json:"name,omitempty"` | ||
| Features []string `json:"features,omitempty"` // Deprecated | ||
| Runner string `json:"runner"` | ||
| DockerComposeFile string `json:"docker_compose_file,omitempty"` | ||
| DockerConfig *DockerConfig `json:"docker_config,omitempty"` | ||
| PublicLogs *bool `json:"public_logs,omitempty"` | ||
| PublicSysinfo *bool `json:"public_sysinfo,omitempty"` | ||
| PublicTcbinfo *bool `json:"public_tcbinfo,omitempty"` | ||
| KmsEnabled *bool `json:"kms_enabled,omitempty"` | ||
| GatewayEnabled *bool `json:"gateway_enabled,omitempty"` | ||
| TproxyEnabled *bool `json:"tproxy_enabled,omitempty"` // For backward compatibility | ||
| LocalKeyProviderEnabled *bool `json:"local_key_provider_enabled,omitempty"` | ||
| KeyProvider KeyProviderKind `json:"key_provider,omitempty"` | ||
| KeyProviderID string `json:"key_provider_id,omitempty"` // hex string | ||
| AllowedEnvs []string `json:"allowed_envs,omitempty"` | ||
| NoInstanceID *bool `json:"no_instance_id,omitempty"` | ||
| SecureTime *bool `json:"secure_time,omitempty"` | ||
| BashScript string `json:"bash_script,omitempty"` // Legacy | ||
| PreLaunchScript string `json:"pre_launch_script,omitempty"` // Legacy | ||
| ManifestVersion interface{} `json:"manifest_version,omitempty"` | ||
| Name string `json:"name,omitempty"` | ||
| Features []string `json:"features,omitempty"` // Deprecated | ||
| Runner string `json:"runner"` | ||
| DockerComposeFile string `json:"docker_compose_file,omitempty"` |
Comment on lines
110
to
+123
| // GetComposeHash computes the SHA256 hash of the application composition | ||
| func GetComposeHash(appCompose AppCompose, normalize ...bool) (string, error) { | ||
| shouldNormalize := len(normalize) > 0 && normalize[0] | ||
|
|
||
| if shouldNormalize { | ||
| appCompose = preprocessAppCompose(appCompose) | ||
| } | ||
|
|
||
| // Convert to generic map for sorting | ||
| jsonBytes, err := json.Marshal(appCompose) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
Comment on lines
+31
to
+33
| type OsVersionPolicy struct { | ||
| Min string `json:"min,omitempty"` | ||
| } |
Comment on lines
+765
to
+775
| fn verify_manifest_version(app_compose: &AppCompose) -> Result<u32> { | ||
| let manifest_version = app_compose | ||
| .manifest_version_u32() | ||
| .context("Invalid manifest_version")?; | ||
| if manifest_version > MAX_SUPPORTED_MANIFEST_VERSION { | ||
| bail!( | ||
| "Unsupported manifest_version: {manifest_version}, max supported: {MAX_SUPPORTED_MANIFEST_VERSION}" | ||
| ); | ||
| } | ||
| Ok(manifest_version) | ||
| } |
2eff4d4 to
14adc15
Compare
14adc15 to
fdf3309
Compare
- Allow requirements on any manifest_version >= 3 instead of == 3, so a future v4 guest does not reject v4 manifests carrying requirements - Reject non-canonical manifest_version strings like "03" and "+3" - Rewrite unquote_os_release_value with single-pass escape handling (fixes backslash-before-quote mishandling; single quotes take no escapes, matching shell semantics) - Add tests for the above
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
app-compose.jsonmanifest_versionas a string, while keeping legacy numeric1/2compatibility and rejecting numeric3+.MAX_SUPPORTED_MANIFEST_VERSIONrequirementsunlessmanifest_version == "3"requirementssupport:requirements.os_versionuses Rust semver requirement syntax, e.g.">=0.6.0, <0.7.0"requirements.platformsreuses existingAttestationModedeserialize values, e.g."dstack-gcp-tdx"platformsmeans any platform; explicitplatforms: []means no platform is supported"3"for OS images>= 0.6.0, while preserving legacy numeric2for older images.manifest_versionandrequirements.Example
{ "manifest_version": "3", "requirements": { "os_version": ">=0.6.0", "platforms": ["dstack-gcp-tdx"] } }Validation
cargo fmt --all --checkgit diff --checkcargo test -p dstack-typescargo test -p dstack-utilcargo test -p dstack-attest compatibility_tests::attestation_mode_deserializes_canonical_namescargo check -p dstack-vmm -p dstack-guest-agentcargo clippy -- -D warnings -D clippy::expect_used -D clippy::unwrap_used --allow unused_variablescargo test --all-features --no-runcd vmm/ui && npm run buildcd sdk/go && go test ./dstack -run '^$'python3 -m py_compile sdk/python/src/dstack_sdk/get_compose_hash.py sdk/python/src/dstack_sdk/__init__.py