CrowdControl.Provider.Gce.Startup (crowd_control v0.1.1)

Copy Markdown View Source

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 port sandboxd binds on the VM's loopback, default 8080. Must be above 1024: the agent is not root and gets no CAP_NET_BIND_SERVICE.
  • :capture_path — default /var/log/cc/out.jsonl