Skip to content

Add trusted workload launcher example#97

Draft
h4x3rotab wants to merge 5 commits into
mainfrom
trusted-workload-launcher
Draft

Add trusted workload launcher example#97
h4x3rotab wants to merge 5 commits into
mainfrom
trusted-workload-launcher

Conversation

@h4x3rotab
Copy link
Copy Markdown
Contributor

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:

  • parses a line-oriented config without sourcing it;
  • rejects branches, tags, short SHAs, unknown keys, and path escapes;
  • clones/fetches the configured repo, checks out the full commit, verifies HEAD, runs INSTALL_CMD, then execs RUN_CMD;
  • ships as a digest-pinned Ubuntu 24.04 image with a GitHub/Sigstore provenance workflow.

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 pass
  • shellcheck trusted-workload-launcher/bin/trusted-workload-launcher trusted-workload-launcher/tests/run-tests.sh -> pass
  • yamllint -c .yamllint .github/workflows/trusted-workload-launcher-release.yml -> pass
  • docker build -f docker/Dockerfile -t trusted-workload-launcher:test . from trusted-workload-launcher/ -> pass
  • docker run --rm trusted-workload-launcher:test --help -> pass

Production smoke test on Phala Cloud:

  • deployed temporary CVM twl-smoke-20260511-091916 on tdx.small;
  • used derived image docker.io/h4x3rotab/trusted-workload-launcher-smoke@sha256:6c508c15c45c8aacbbbfab3754724ef9ef104a67e1c53a9c35b50be47e86433e;
  • launcher logs showed HEAD verified: 7fd1a60b01f91b314f59955a4e4d4e80d8edf11d for https://github.com/octocat/Hello-World.git;
  • workload self-check printed TWL_PINNED_HEAD=7fd1a60b01f91b314f59955a4e4d4e80d8edf11d and TWL_READY;
  • CVM was deleted after verification.

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.

Comment thread README.md Outdated
|---------|-------------|
| [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 |
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Too long

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Still too lang. We need an one liner description. Not a compression of a paragraph.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread trusted-workload-launcher/README.md
Comment thread trusted-workload-launcher/bin/trusted-workload-launcher
Comment thread trusted-workload-launcher/VERIFY.md Outdated
Comment thread trusted-workload-launcher/VERIFY.md
Comment thread trusted-workload-launcher/VERIFY.md Outdated
Comment thread trusted-workload-launcher/VERIFY.md Outdated
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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_CMD and RUN_CMD are now optional. In default mode the launcher runs bash tee-launch.sh from the pinned commit (under REPO_SUBDIR if set, else repo root). No exec bit on the file is required. INSTALL_CMD without RUN_CMD is rejected at validation. Advanced mode (RUN_CMD set, with optional INSTALL_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_CMD without RUN_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.sh under 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_CMD are 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_DIR is now called out as local plumbing (not trust-bearing); CHILD_ENV_FILE is 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.

h4x3rotab added 4 commits May 11, 2026 18:05
- 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.
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.

1 participant