mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-07-10 01:28:35 +00:00
feat(cli): enable GCP Shielded VM (Secure Boot) by default
Provision every GCP instance as a Shielded VM (Secure Boot + vTPM + integrity monitoring) by default. This is what the Cloudflare (CF) skill needs to attest the VM, and is good hygiene for all spawns. GCP's default Ubuntu LTS images are Shielded-VM-compatible, so boots are unaffected. Opt out with the new --no-secure-boot flag (sets GCP_NO_SECURE_BOOT=1) for custom images that are not UEFI/Secure-Boot-capable. Secure Boot is GCP-only here; AWS spawns run on Lightsail, which does not expose Secure Boot, so the flag is a no-op on other clouds.
This commit is contained in:
parent
2be1a78dce
commit
585d3466e3
6 changed files with 80 additions and 1 deletions
17
README.md
17
README.md
|
|
@ -54,6 +54,7 @@ spawn delete -c hetzner # Delete a server on Hetzner
|
|||
| `spawn <agent> <cloud> --config <file>` | Load options from a JSON config file |
|
||||
| `spawn <agent> <cloud> --steps <list>` | Comma-separated setup steps to enable |
|
||||
| `spawn <agent> <cloud> --custom` | Show interactive size/region pickers |
|
||||
| `spawn <agent> gcp --no-secure-boot` | Disable GCP Shielded VM (Secure Boot is on by default) |
|
||||
| `spawn <agent>` | Show available clouds for an agent |
|
||||
| `spawn <cloud>` | Show available agents for a cloud |
|
||||
| `spawn matrix` | Full agent x cloud matrix |
|
||||
|
|
@ -159,6 +160,22 @@ spawn claude gcp --beta tarball --beta parallel
|
|||
|
||||
`--fast` enables `tarball`, `images`, and `parallel` (not `recursive`).
|
||||
|
||||
#### Secure Boot (GCP)
|
||||
|
||||
GCP instances are provisioned as [Shielded VMs](https://cloud.google.com/security/shielded-cloud/shielded-vm)
|
||||
**by default** — Secure Boot, vTPM, and integrity monitoring are all enabled. This
|
||||
hardens the boot chain and is required for the Cloudflare (CF) skill to attest the VM.
|
||||
GCP's default Ubuntu LTS images are Shielded-VM-compatible, so this works out of the box.
|
||||
|
||||
Opt out for a custom image that is not UEFI/Secure-Boot-capable:
|
||||
|
||||
```bash
|
||||
spawn claude gcp --no-secure-boot
|
||||
```
|
||||
|
||||
> Secure Boot is a GCP-only feature here. AWS spawns run on Lightsail, which does not
|
||||
> expose Secure Boot; the flag is a no-op on other clouds.
|
||||
|
||||
#### Recursive Spawn
|
||||
|
||||
Use `--beta recursive` to let spawned VMs create their own child VMs:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { mockBunSpawn, mockClackPrompts } from "./test-helpers";
|
|||
|
||||
mockClackPrompts();
|
||||
|
||||
import { DEFAULT_MACHINE_TYPE, DEFAULT_ZONE, getConnectionInfo } from "../gcp/gcp";
|
||||
import { buildShieldedArgs, DEFAULT_MACHINE_TYPE, DEFAULT_ZONE, getConnectionInfo } from "../gcp/gcp";
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -82,6 +82,31 @@ describe("gcp/getConnectionInfo", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// ─── buildShieldedArgs (Secure Boot default) ─────────────────────────────────
|
||||
|
||||
describe("gcp/buildShieldedArgs", () => {
|
||||
it("enables Shielded VM (Secure Boot) by default", () => {
|
||||
delete process.env.GCP_NO_SECURE_BOOT;
|
||||
expect(buildShieldedArgs()).toEqual([
|
||||
"--shielded-secure-boot",
|
||||
"--shielded-vtpm",
|
||||
"--shielded-integrity-monitoring",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns no shielded flags when GCP_NO_SECURE_BOOT=1", () => {
|
||||
process.env.GCP_NO_SECURE_BOOT = "1";
|
||||
expect(buildShieldedArgs()).toEqual([]);
|
||||
});
|
||||
|
||||
it("only opts out for the exact value '1' (any other value stays on)", () => {
|
||||
process.env.GCP_NO_SECURE_BOOT = "0";
|
||||
expect(buildShieldedArgs()).toContain("--shielded-secure-boot");
|
||||
process.env.GCP_NO_SECURE_BOOT = "true";
|
||||
expect(buildShieldedArgs()).toContain("--shielded-secure-boot");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── promptMachineType ───────────────────────────────────────────────────────
|
||||
|
||||
describe("gcp/promptMachineType", () => {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ describe("Unknown Flag Detection", () => {
|
|||
"--debug",
|
||||
"--name",
|
||||
"--reauth",
|
||||
"--no-secure-boot",
|
||||
"--prune",
|
||||
"--json",
|
||||
"--yes",
|
||||
|
|
@ -216,6 +217,7 @@ describe("KNOWN_FLAGS completeness", () => {
|
|||
"--clear",
|
||||
"--custom",
|
||||
"--reauth",
|
||||
"--no-secure-boot",
|
||||
"--zone",
|
||||
"--region",
|
||||
"--machine-type",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const KNOWN_FLAGS = new Set([
|
|||
"--clear",
|
||||
"--custom",
|
||||
"--reauth",
|
||||
"--no-secure-boot",
|
||||
"--zone",
|
||||
"--region",
|
||||
"--machine-type",
|
||||
|
|
|
|||
|
|
@ -148,6 +148,30 @@ export const DEFAULT_ZONE = "us-central1-a";
|
|||
|
||||
export const DEFAULT_DISK_SIZE_GB = 40;
|
||||
|
||||
// ─── Shielded VM (Secure Boot) ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* GCP Shielded VM flags, enabled by DEFAULT on every spawned instance.
|
||||
*
|
||||
* Secure Boot + a measured (vTPM) and integrity-monitored boot chain is what
|
||||
* the Cloudflare (CF) skill needs to attest the VM, and it is good hygiene for
|
||||
* every spawn. GCP's Ubuntu LTS images (the default here) are
|
||||
* Shielded-VM-compatible, so turning this on does not break boots.
|
||||
*
|
||||
* Opt out with the `--no-secure-boot` CLI flag (sets `GCP_NO_SECURE_BOOT=1`),
|
||||
* for the rare case of a custom image that is not UEFI/Secure-Boot-capable.
|
||||
*/
|
||||
export function buildShieldedArgs(): string[] {
|
||||
if (process.env.GCP_NO_SECURE_BOOT === "1") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
"--shielded-secure-boot",
|
||||
"--shielded-vtpm",
|
||||
"--shielded-integrity-monitoring",
|
||||
];
|
||||
}
|
||||
|
||||
// ─── State ──────────────────────────────────────────────────────────────────
|
||||
|
||||
interface GcpState {
|
||||
|
|
@ -781,6 +805,7 @@ export async function createInstance(
|
|||
]
|
||||
: []),
|
||||
`--metadata=ssh-keys=${sshKeysMetadata}`,
|
||||
...buildShieldedArgs(),
|
||||
`--project=${_state.project}`,
|
||||
"--quiet",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ function checkUnknownFlags(args: string[]): void {
|
|||
console.error(` ${pc.cyan("--model, -m <id>")} Set the LLM model (e.g. openai/gpt-5.3-codex)`);
|
||||
console.error(` ${pc.cyan("--name")} Set the spawn/resource name`);
|
||||
console.error(` ${pc.cyan("--reauth")} Force re-prompting for cloud credentials`);
|
||||
console.error(` ${pc.cyan("--no-secure-boot")} Disable GCP Shielded VM (Secure Boot, on by default)`);
|
||||
console.error(` ${pc.cyan("--config <path>")} Load config from JSON file`);
|
||||
console.error(` ${pc.cyan("--steps <list>")} Comma-separated setup steps to enable`);
|
||||
console.error(` ${pc.cyan("--repo <slug|url>")} Clone a template repo and apply spawn.md`);
|
||||
|
|
@ -928,6 +929,14 @@ async function main(): Promise<void> {
|
|||
process.env.SPAWN_REAUTH = "1";
|
||||
}
|
||||
|
||||
// Extract --no-secure-boot boolean flag — opt out of GCP Shielded VM
|
||||
// (Secure Boot + vTPM + integrity monitoring), which is on by default.
|
||||
const noSecureBootIdx = filteredArgs.indexOf("--no-secure-boot");
|
||||
if (noSecureBootIdx !== -1) {
|
||||
filteredArgs.splice(noSecureBootIdx, 1);
|
||||
process.env.GCP_NO_SECURE_BOOT = "1";
|
||||
}
|
||||
|
||||
// Extract --fast boolean flag — enables images + tarballs + parallel setup
|
||||
const fastIdx = filteredArgs.indexOf("--fast");
|
||||
if (fastIdx !== -1) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue