Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,225 changes: 6,225 additions & 0 deletions .github/dapper-headers.json

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion .github/scripts/enforce-builder-vulnerabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ def violations(report, ledger, boundary, headers):
if (headers.get("package") != "linux-libc-dev"
or headers.get("version") != HEADER_VERSION or headers.get("purl") != HEADER_PURL):
raise ValueError("Changed exact header review")
refresh = headers.get("mediumRefresh", {})
refreshed = {}
for entry in refresh.get("entries", []):
identifier = entry.get("id")
official = ("https://raw.githubusercontent.com/canonical/ubuntu-security-notices/"
+ str(refresh.get("officialSnapshot")) + "/osv/cve/2026/UBUNTU-"
+ str(identifier) + ".json")
if (identifier in refreshed or identifier not in headers.get("ids", [])
or entry.get("package") != "linux-libc-dev" or entry.get("version") != HEADER_VERSION
or entry.get("purl") != HEADER_PURL or entry.get("severity") != "MEDIUM"
or entry.get("decision") != "UNFIXED_MEDIUM_HEADER_ONLY"
or entry.get("officialFixedVersions") != []
or entry.get("officialRangeEvents") != [{"introduced": "0"}]
or entry.get("officialUrl") != official
or not entry.get("rawSha256") or not entry.get("target")):
raise ValueError("Invalid exact medium header refresh")
refreshed[identifier] = entry
reviewed = {}
for entry in ledger.get("unfixed", []):
key = (entry["id"], entry["package"], entry["version"], entry["purl"])
Expand Down Expand Up @@ -54,7 +71,8 @@ def violations(report, ledger, boundary, headers):
if boundary != expected:
raise ValueError("Missing exact installed header-only proof")
if (not fixed and key[0] in headers.get("ids", [])
and key[2] == HEADER_VERSION and key[3] == HEADER_PURL):
and key[2] == HEADER_VERSION and key[3] == HEADER_PURL
and (key[0] not in refreshed or finding.get("Severity") == "MEDIUM")):
continue
severity = finding.get("Severity", "UNKNOWN")
entry = reviewed.get(key)
Expand Down
24 changes: 24 additions & 0 deletions .github/scripts/test_builder_vulnerability_policy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import importlib.util
import json
from copy import deepcopy
import unittest
from pathlib import Path
spec = importlib.util.spec_from_file_location("gate", Path(__file__).with_name("enforce-builder-vulnerabilities.py"))
Expand Down Expand Up @@ -69,6 +71,28 @@ def test_fixed_header_rejected(self):
self.header()["FixedVersion"] = "7.0.0-99"; self.assertTrue(self.check())
def test_new_header_advisory_rejected(self):
self.header()["VulnerabilityID"] = "NEW"; self.assertTrue(self.check())
def refreshed_header(self):
self.headers = json.loads((Path(__file__).resolve().parents[1] / "dapper-headers.json").read_text())
f = self.header()
f.update(VulnerabilityID=self.headers["mediumRefresh"]["entries"][0]["id"], Severity="MEDIUM")
return f
def test_refreshed_exact_medium_header(self):
self.refreshed_header()
self.assertEqual(len(self.headers["mediumRefresh"]["entries"]), 222)
self.assertEqual(self.check(), [])
def test_refreshed_headers_cannot_allow_high_unknown_or_available_fix(self):
for severity in ("LOW", "HIGH", "CRITICAL", "UNKNOWN"):
self.refreshed_header()["Severity"] = severity
with self.subTest(severity=severity): self.assertTrue(self.check())
self.refreshed_header()["FixedVersion"] = "7.0.0-99"
self.assertTrue(self.check())
def test_refreshed_review_fixed_or_package_drift_rejected(self):
self.refreshed_header()
original = deepcopy(self.headers)
for key, value in (("officialFixedVersions", ["7.0.0-99"]), ("purl", "pkg:generic/other@1")):
self.headers = deepcopy(original)
self.headers["mediumRefresh"]["entries"][0][key] = value
with self.subTest(key=key), self.assertRaises(ValueError): self.check()
def test_header_version_or_purl_drift_rejected(self):
for field in ("InstalledVersion", "PkgIdentifier"):
f = self.header()
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
env:
DAPPER_IMAGE: pasturestack/compose-cli-dapper:${{ github.sha }}
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
VERSION_OVERRIDE: 0.14.39
VERSION_OVERRIDE: 0.14.40
PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/orchestration-engine/releases/download/v0.183.281/orchestration-engine-0.183.281.jar
PLATFORM_COMPAT_JAR_SHA256: da2a8a51562ed16e296f7e29e99482bb44042ff0834cca679bbe01d951ba1682

Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
}

run_ci
artifact="dist/artifacts/compose-executor-0.14.39-linux-amd64.gz"
artifact="dist/artifacts/compose-executor-0.14.40-linux-amd64.gz"
test -s "$artifact"
cp "$artifact" /tmp/compose-executor-first.gz

Expand All @@ -81,7 +81,7 @@ jobs:
mkdir -p evidence/product
gzip -cd "$artifact" > evidence/product/compose-executor
chmod +x evidence/product/compose-executor
evidence/product/compose-executor --version | grep -F '0.14.39' >/dev/null
evidence/product/compose-executor --version | grep -F '0.14.40' >/dev/null
cp "$artifact" evidence/
(
cd evidence
Expand Down
21 changes: 21 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

The migration preserves Docker Compose parsing, stack event names, generated stack fields, service labels, catalog question data, upgrade and rollback payloads, and compatibility API behavior.

The v0.14.40 candidate adds one server-private mode,
`compose-executor.real --catalog-launch-preview`, with fixed inspect/finalize
JSON contracts and bounded stdin/stdout. Existing parser, conversion, service
factory, launch-config and secret-reference owners produce the exact graph;
there is no second renderer or model-supplied URL/file/command interface.
Typed templates use the reviewed bounded subset and freeze the approved bytes.

Only a trusted Engine event may carry `catalogLaunchDelegation`. Its event-local
client uses the fixed delegation header for schema/read/transform/child calls;
missing or invalid proof cannot fall back to service FULL. Control progress and
replies retain the original trusted service channel, and unmarked ordinary
events retain their original client and API behavior. The Engine must recheck
the original actor/Key, source/plan/Stack/event/graph, destination create/update
and references on each request. Completion needs its committed ledger, full
intended links and normal deployment readiness, not only a Stack reply.

KVM, unsupported provider/host/build/file/dependency/network graphs, external or
cross-Stack links and unsupported typed inputs fail closed in this private mode;
they remain pending typed adapters rather than completed UI features. Component
source/unit and official CI evidence do not establish matching Server runtime.

The archived external Compose implementation is no longer a build dependency. The conversion, collection-copy, logger, and Compose configuration-hash contracts still required by this compatibility client are implemented locally and covered by behavior tests. This does not expand or silently reinterpret the accepted Compose document format.

`platform-compose.yml`, `PLATFORM_URL`, `PLATFORM_ACCESS_KEY`, and `PLATFORM_SECRET_KEY` are preferred. The historical companion filename, legacy CLI alias, `RANCHER_*` and `CATTLE_*` variables, `io.rancher.*` labels, generated `RancherCompose` and `RancherClient` types, legacy service-image identifiers, and vendored `github.com/rancher/*` paths remain only as wire, data, or dependency contracts.
Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

The maintained source uses Ubuntu 26.04, Go 1.27.3, and Python 3.14.9 for integration-source verification. Ubuntu build packages, the base image, and Go archives are fixed by snapshot, exact version, and SHA-256. Go dependencies are managed by `go.mod` and a reproducible module vendor tree. AWS access uses AWS SDK for Go v2; container structures use the maintained Moby API module; the command parser uses urfave/cli v3. The abandoned AWS SDK v1, Rancher generated-client modules, event-subscriber module, `docker/libcompose`, and other GOPATH-era dependencies have been removed. The small Rancher 1.6 protocol boundary retained by this compatibility CLI is maintained and tested locally. The Dapper builder does not embed a Docker CLI or mount a Docker socket; Docker is required only on the host to create and run the isolated builder. The maintained binaries are `pasturestack-compose` and `compose-executor`; `platform-compose.yml` is the preferred companion file. Releases are built and verified from the preserved source history; this repository does not enable automated deployment.

The current published component is
[v0.14.39](https://github.com/PastureStack/compose-cli/releases/tag/v0.14.39).
The **v0.14.40** verification candidate adds the private two-phase Catalog launch
materializer and event-local delegated execution. It shares normal service,
volume, launch-config, secret-reference, sidekick and hash owners; ordinary CLI
and executor calls without a delegation marker keep their existing behavior.
See [the owner contract](docs/catalog-launch-owner.md) and
[candidate release note](docs/releases/compose-executor-0.14.40.md).

The matching Engine owns the original actor/Key ceiling, human approval,
encrypted inputs, exact graph and child-request ledger. Neither a component
build nor a Stack reply proves workload success. This candidate is unpublished;
official gates and complete matching Server runtime acceptance are pending.

## Configuration

Use `PLATFORM_URL`, `PLATFORM_ACCESS_KEY`, and `PLATFORM_SECRET_KEY`. Historical `RANCHER_*` and `CATTLE_*` settings remain compatibility fallbacks. Operator messages support `PASTURESTACK_LOCALE=en-US` and `zh-TW`.
Expand All @@ -31,7 +45,8 @@ publishes `compose-executor-0.14.39-linux-amd64.gz`. Published tags and assets
remain immutable; a later recipe or documentation commit does not replace
their bytes or change their producer identity.

Set `VERSION_OVERRIDE=0.14.39` for this recipe. Source changes alone are not
Set `VERSION_OVERRIDE=0.14.40` to verify this candidate; rebuild the published
release only from its numeric tag and original recipe. Source changes alone are not
publication proof: verify the official release assets and their SHA-256.
Component publication and matching Server acceptance are separate evidence
boundaries. The Python integration suite must run against an
Expand Down Expand Up @@ -66,6 +81,5 @@ proof; this is not a kernel or runtime exemption. Ubuntu security micro-updates
apply only to already-installed packages without changing the Python ABI.

The focused `Builder full-severity security` workflow validates builder-only
maintenance without rebuilding or republishing the unchanged `v0.14.39`
runtime. Raw distro pip metadata is retained alongside the actual replaced seed
maintenance without rebuilding or replacing published runtime assets. Raw distro pip metadata is retained alongside the actual replaced seed
paths, wheel hash, and existing narrow pip-vendor VEX evidence.
31 changes: 31 additions & 0 deletions catalog_launch_main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"bytes"
"os"
"os/exec"
"strings"
"testing"

"github.com/PastureStack/compose-cli/cataloglaunch"
)

func TestCatalogLaunchCommandOmitsPrivateParserDiagnostics(t *testing.T) {
if os.Getenv("PST_CATALOG_LAUNCH_TEST_PROCESS") == "1" {
os.Args = []string{"compose-executor", cataloglaunch.Command}
main()
return
}
command := exec.Command(os.Args[0], "-test.run=^TestCatalogLaunchCommandOmitsPrivateParserDiagnostics$")
command.Env = append(os.Environ(), "PST_CATALOG_LAUNCH_TEST_PROCESS=1")
command.Stdin = strings.NewReader(`{"name":"chosen","dockerCompose":"private-parser-answer-marker"}`)
var stdout, stderr bytes.Buffer
command.Stdout = &stdout
command.Stderr = &stderr
if command.Run() == nil {
t.Fatal("malformed input succeeded")
}
if stderr.Len() != 0 || strings.Contains(stdout.String(), "private-parser") || strings.TrimSpace(stdout.String()) != `{"code":"CatalogLaunchUnsupported"}` {
t.Fatal("private diagnostics reached process output")
}
}
34 changes: 34 additions & 0 deletions cataloglaunch/finalize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cataloglaunch

import (
"encoding/json"
"testing"
)

func TestCatalogFinalizeRejectsUnknownOrMissingTransformAndUnresolvedReferences(t *testing.T) {
input := exampleInput()
input.Phase = "finalize"
input.TransformedLaunchConfigs = map[string]json.RawMessage{"app": json.RawMessage(`{"imageUuid":"docker:busybox:1.37.0","logConfig":{"config":{}}}`)}
result, err := Prepare(input)
if err != nil || result.Phase != "finalize" || len(result.ServiceRequests) != 1 || result.ServiceRequests[0].ResourceType != "service" {
t.Fatal("finalize failed")
}
for _, change := range []func(*Input){
func(i *Input) { i.TransformedLaunchConfigs["foreign"] = json.RawMessage(`{}`) },
func(i *Input) { delete(i.TransformedLaunchConfigs, "app") },
func(i *Input) { i.TransformedLaunchConfigs["app"] = json.RawMessage(`null`) },
func(i *Input) { i.ResolvedSecrets = map[string]string{"unrequested": "1se7"} },
func(i *Input) { i.Phase = "inspect" },
} {
copy := input
copy.TransformedLaunchConfigs = map[string]json.RawMessage{"app": input.TransformedLaunchConfigs["app"]}
change(&copy)
if _, err := Prepare(copy); err == nil {
t.Fatal("unbound finalize input admitted")
}
}
input.RancherCompose = "version: '2'\nservices:\n app:\n secrets:\n - chosen\n"
if _, err := Prepare(input); err == nil {
t.Fatal("unresolved secret accepted")
}
}
Loading
Loading