Add trusted workload launcher example#97
Conversation
| |---------|-------------| | ||
| | [launcher](./launcher) | Generic launcher pattern for Docker Compose apps | | ||
| | [launcher](./launcher) | Generic launcher pattern for Docker Compose apps (auto-update) | | ||
| | [trusted-workload-launcher](./trusted-workload-launcher) | Tiny auditable launcher image that fetches a workload at a full upstream Git commit SHA — opposite trust posture to `launcher/` (no auto-update). The launcher image digest attests the launcher itself; the workload pin lives in a config file that must be attested via dstack `compose_hash`/`config_id` or baked into a derived image | |
There was a problem hiding this comment.
Agent: Shortened to a single sentence (commit 3c44cce). Row now reads: Auditable launcher that pins a workload to a full upstream Git commit SHA (no auto-update). The longer trust-model discussion stays in the example's README/VERIFY.md.
There was a problem hiding this comment.
Still too lang. We need an one liner description. Not a compression of a paragraph.
There was a problem hiding this comment.
Agent: Compressed to a true one-liner (commit b0478b6): Run a pinned Git commit in a TEE. Six words, fits the Details-table cadence. The longer trust-model discussion stays in trusted-workload-launcher/README.md and VERIFY.md.
There was a problem hiding this comment.
In general, the verification process is too hard. Part of the reason is Intel TDX's complexity. Imagine with AWS Nitro Enclave and AMD SEV, users can just get the reference values of the image measurement from the build attested on Sigstore, and verify the attestation, and compare the reference values. But in our case, we have more than 7 steps. This will drive users away.
There was a problem hiding this comment.
Agent: Restructured VERIFY.md to lead with a 5-step Quick path up front (commit 3c44cce): pull dstack attestation -> read attested compose (image digest + inline config) -> verify launcher Sigstore attestation -> audit upstream COMMIT_SHA -> spot-check logs. The detailed step-by-step CLI checklist now sits below as the deep path, with the TDX/event-log specifics scoped to step 2 where they are actually needed. A reader who already trusts the dstack/Phala verifier can stop after the quick path.
There was a problem hiding this comment.
Can we simplify it even more? Give you some inspiration: Now we verify the launcher itself, and then read the config (git repo, install and run commands). What if we put the install and run command inside the git repo? So the cmd config verification step will be merged into the source code provenance.
This is just one example. We need to try hard to compress the verification steps even more from other angles.
There was a problem hiding this comment.
Agent: Took your hint and folded install/run into source provenance by making it the default mode rather than a convention (commits b0478b6 + 331c3d0 for precision wording).
What changed runtime-wise:
INSTALL_CMDandRUN_CMDare now optional. In default mode the launcher runsbash tee-launch.shfrom the pinned commit (underREPO_SUBDIRif set, else repo root). No exec bit on the file is required.INSTALL_CMDwithoutRUN_CMDis rejected at validation. Advanced mode (RUN_CMDset, with optionalINSTALL_CMD) remains for repos that cannot be modified.- Three new tests cover default-mode happy path, default-mode-with-missing-script (fail closed), and
INSTALL_CMDwithoutRUN_CMD. All 19 tests pass.
What that means for VERIFY.md:
- Quick path is now 4 steps instead of 5: (1) dstack attestation -> launcher image digest + REPO_URL + COMMIT_SHA (+ REPO_SUBDIR if used), (2) Sigstore build-provenance attestation on the launcher image digest, (3) audit the upstream repo at COMMIT_SHA — which now also covers
tee-launch.shunder the same source provenance, (4) spot-check runtime logs. The dedicated "extract and audit INSTALL_CMD/RUN_CMD" step disappears for default-mode deployments. - Advanced mode is documented as an explicit one-step extension:
RUN_CMD/INSTALL_CMDare read out of the attested compose in step 1 and audited as trust-bearing deployment config (not as source at COMMIT_SHA, since they live in the launcher config rather than the upstream repo). WORK_DIRis now called out as local plumbing (not trust-bearing);CHILD_ENV_FILEis called out as runtime configuration that affects behavior but not the bytes that run.
Convention chosen: fixed path tee-launch.sh (workload repo root, or REPO_SUBDIR/tee-launch.sh). The launcher invokes it with bash tee-launch.sh, so it does not need to be marked executable; this avoids the cross-platform exec-bit footgun. Failure to find it is a hard error — no fallback.
- Shorten root README Details row - Document line-oriented config and call-a-script pattern for multi-line - Drop non-ASCII (em-dash) from launcher script comments and log line - Rewrite VERIFY.md: lead with 5-step quick path, add Mermaid diagrams, drop TIP terminology, promote compose-mounted config as the recommended production path, rewrite source provenance around Sigstore attestation (not bit-for-bit reproducibility), and re-anchor the smoke transcript on compose-hash <-> sha256(app_compose) binding from a real production Phala smoke (CVM twl-cfg-smoke-20260511-180207, deleted after capture).
Drop "reproducibly built" claims outside VERIFY.md. The release workflow publishes a Sigstore build-provenance attestation that binds the image digest to a specific GitHub workflow run / repo / ref / SHA; that is a signed chain of custody, not a guarantee of bit-for-bit reproducibility. Updated README.md trust-model diagram and verifier list, and the examples/web-app.conf header.
Make INSTALL_CMD and RUN_CMD optional and add a recommended default mode:
when neither is set the launcher runs 'bash tee-launch.sh' from the
pinned commit (under REPO_SUBDIR if set, else repo root). The script's
bytes are covered by source provenance of COMMIT_SHA, so the trust-
bearing config in default mode collapses to REPO_URL + COMMIT_SHA. The
existing RUN_CMD path is preserved as an advanced mode for repos that
cannot host their own entry script; INSTALL_CMD remains optional and
must accompany RUN_CMD.
Aligned changes:
- bin/trusted-workload-launcher: validate INSTALL_CMD requires RUN_CMD,
drop "INSTALL_CMD must be set" and "RUN_CMD required" gates, add the
default-mode branch that requires ./tee-launch.sh in target. No exec
bit required (we invoke 'bash tee-launch.sh'). Refreshed top comment
and parse_config comment to reflect the new model.
- tests/run-tests.sh: extended fixture repo with c3 (adds
sub/tee-launch.sh, intentionally non-executable), added
default_mode_happy, default_mode_missing_script_fails, and
install_cmd_without_run_cmd_fails.
- README.md, examples/web-app.conf: lead with default mode; advanced
mode explicitly scoped to "workload repo cannot host its own entry
script"; clarified that no executable bit is required.
- VERIFY.md: quick path shortened to 4 steps for default mode, with an
explicit one-line note that advanced mode adds the command audit;
step 4 covers tee-launch.sh under source provenance and step 5 shows
the default-mode log lines. Updated the recommended-path Mermaid
diagram. Smoke transcript annotated that the prior production run
used advanced mode because Hello-World ships no tee-launch.sh, and
that the compose-hash binding it demonstrates is identical.
- Root README: Details-table description compressed to a true one-liner
("Run a pinned Git commit in a TEE.").
Three precision fixes to avoid overclaiming the simplification: - Default mode: trust-bearing config is REPO_URL + COMMIT_SHA and REPO_SUBDIR when used (it selects which tee-launch.sh runs). Made this explicit in the example README intro, the default-mode section, the VERIFY.md quick-path summary and Mermaid diagram, and step 4 of the deep-path checklist. Clarified that WORK_DIR is local plumbing (not trust-bearing) and that CHILD_ENV_FILE / runtime env can affect runtime behavior but not the bytes that run, so it should be audited as runtime deployment configuration rather than as source. - Advanced mode: corrected the framing so RUN_CMD / INSTALL_CMD are described as "trust-bearing deployment config" rather than "source code at the pinned commit" — they live in the launcher config, not in the upstream repo at COMMIT_SHA, and so are not covered by its source provenance. - VERIFY.md log example: reordered to match what the launcher actually emits (mode line during the config summary, then checking out, then HEAD verified, then exec) and described the lines as ones the output should include, not a strict transcript.
Summary
Adds
trusted-workload-launcher, a small auditable dstack example that starts a workload from an attested config containing a full upstream Git commit SHA.The launcher:
HEAD, runsINSTALL_CMD, thenexecsRUN_CMD;Also adds
VERIFY.md, which explains the TIP-style verification story: dstack attestation establishes the launcher deployment identity, source/image provenance validates the launcher code, attested config or a derived image binds the workload pin, and runtime logs corroborate but do not replace attestation.Validation
Local checks:
./trusted-workload-launcher/tests/run-tests.sh-> 16/16 passshellcheck trusted-workload-launcher/bin/trusted-workload-launcher trusted-workload-launcher/tests/run-tests.sh-> passyamllint -c .yamllint .github/workflows/trusted-workload-launcher-release.yml-> passdocker build -f docker/Dockerfile -t trusted-workload-launcher:test .fromtrusted-workload-launcher/-> passdocker run --rm trusted-workload-launcher:test --help-> passProduction smoke test on Phala Cloud:
twl-smoke-20260511-091916ontdx.small;docker.io/h4x3rotab/trusted-workload-launcher-smoke@sha256:6c508c15c45c8aacbbbfab3754724ef9ef104a67e1c53a9c35b50be47e86433e;HEAD verified: 7fd1a60b01f91b314f59955a4e4d4e80d8edf11dforhttps://github.com/octocat/Hello-World.git;TWL_PINNED_HEAD=7fd1a60b01f91b314f59955a4e4d4e80d8edf11dandTWL_READY;Notes
The generic launcher image digest proves the launcher implementation, not the workload pin by itself. The docs cover two production binding paths: dstack-attested compose/config, or a derived image that bakes in the config.