Renders the startup-script that turns a bare Debian VM into a sandbox
running sandboxd.
Pure: render/1 takes options and returns a string. No cloud, no network, no
side effects — which matters because this script is the one part of
CrowdControl.Provider.Gce that cannot be observed from the BEAM once it
starts running. A rendering bug is otherwise only visible as a VM that never
answers GET /v1/health, minutes later, on a substrate that bills by the
second.
No secret is ever in the script body
The rendered text is stored in instance metadata, and instance metadata is
readable by anything that can compute.instances.get the VM — a
roles/compute.viewer on the project included. So the script contains no
token. It fetches attributes/cc-sandboxd-token from the metadata server at
service start and exports it into sandboxd's environment only, so
CC_SANDBOXD_TOKEN exists in exactly one process's environment and nowhere
on disk.
Anything on the VM that can reach the metadata server can read that token,
including the sandboxed CLI. That is not an escalation — the token only
authorizes exec/stdin/stream against the agent running beside it, which
that code already drives — but it is precisely why no other credential may
travel this way, and why CrowdControl.Provider.Gce attaches no service
account by default.
The release is verified, not trusted
:sandboxd_url is fetched over the network onto a VM that then runs it as a
service, so :sandboxd_sha256 is mandatory. A missing checksum is a
validation error rather than a skipped check: "no checksum configured" and
"checksum verified" must never be the same code path. set -euo pipefail
plus an explicit sha256sum -c means a mismatched artifact is never
extracted, so the agent never answers health, so acquire/1 destroys the VM.
The expected artifact
CI publishes sandboxd-linux-amd64.tar.gz / sandboxd-linux-arm64.tar.gz
with a .sha256 sidecar. The archive's top level is sandboxd/
(bin, erts-*, lib, releases), so it is unpacked with tar -C /opt
and lands on /opt/sandboxd/bin/sandboxd. Pass the sidecar's hash as
:sandboxd_sha256, and pick the tarball matching the :machine_type's
architecture — an OTP release must run on the glibc and CPU it was built for.
Ordering
The caller's :bootstrap_script runs before the agent is installed, as
root. That ordering is load-bearing: GET /v1/health is the provider's only
readiness signal, and putting the bootstrap first makes a healthy agent imply
a finished bootstrap. Reversed, a sandbox would pass health while node and
the agent CLI were still installing, and the session's first exec would
fail on a sandbox that looked ready.
Two accounts, deliberately
sandboxd runs as an unprivileged ccagent system user, not as root and not
as the SSH user. The guest agent adds metadata SSH users to google-sudoers,
so running the agent as the tunnel's SSH user would hand every exec
passwordless root on the VM.
Options
:sandboxd_url— URL of the release tarball described above (required):sandboxd_sha256— that archive's SHA-256, 64 hex characters (required):bootstrap_script— shell run as root before the agent is installed; omit it for an image that already carries the agent CLI:agent_port— the portsandboxdbinds on the VM's loopback, default8080. Must be above 1024: the agent is not root and gets noCAP_NET_BIND_SERVICE.:capture_path— default/var/log/cc/out.jsonl