Add guest-only workspace secrets
Add explicit workspace secrets across the CLI, SDK, and MCP, with create-time secret definitions and per-call secret-to-env mapping for exec, shell open, and service start. Persist only safe secret metadata in workspace records, materialize secret files under /run/pyro-secrets, and redact secret values from exec output, shell reads, service logs, and surfaced errors. Fix the remaining real-guest shell gap by shipping bundled guest init alongside the guest agent and patching both into guest-backed workspace rootfs images before boot. The new init mounts devpts so PTY shells work on Firecracker guests, while reset continues to recreate the sandbox and re-materialize secrets from stored task-local secret material. Validation: uv lock; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; and a real guest-backed Firecracker smoke covering workspace create with secrets, secret-backed exec, shell, service, reset, and delete.
This commit is contained in:
parent
18b8fd2a7d
commit
fc72fcd3a1
32 changed files with 1980 additions and 181 deletions
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -14,6 +15,8 @@ def test_resolve_runtime_paths_default_bundle() -> None:
|
|||
assert paths.jailer_bin.exists()
|
||||
assert paths.guest_agent_path is not None
|
||||
assert paths.guest_agent_path.exists()
|
||||
assert paths.guest_init_path is not None
|
||||
assert paths.guest_init_path.exists()
|
||||
assert paths.artifacts_dir.exists()
|
||||
assert paths.manifest.get("platform") == "linux-x86_64"
|
||||
|
||||
|
|
@ -51,17 +54,56 @@ def test_resolve_runtime_paths_checksum_mismatch(
|
|||
guest_agent_path = source.guest_agent_path
|
||||
if guest_agent_path is None:
|
||||
raise AssertionError("expected guest agent in runtime bundle")
|
||||
guest_init_path = source.guest_init_path
|
||||
if guest_init_path is None:
|
||||
raise AssertionError("expected guest init in runtime bundle")
|
||||
copied_guest_dir = copied_platform / "guest"
|
||||
copied_guest_dir.mkdir(parents=True, exist_ok=True)
|
||||
(copied_guest_dir / "pyro_guest_agent.py").write_text(
|
||||
guest_agent_path.read_text(encoding="utf-8"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(copied_guest_dir / "pyro-init").write_text(
|
||||
guest_init_path.read_text(encoding="utf-8"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("PYRO_RUNTIME_BUNDLE_DIR", str(copied_bundle))
|
||||
with pytest.raises(RuntimeError, match="checksum mismatch"):
|
||||
resolve_runtime_paths()
|
||||
|
||||
|
||||
def test_resolve_runtime_paths_guest_init_checksum_mismatch(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
source = resolve_runtime_paths()
|
||||
copied_bundle = tmp_path / "bundle"
|
||||
shutil.copytree(source.bundle_root.parent, copied_bundle)
|
||||
copied_platform = copied_bundle / "linux-x86_64"
|
||||
copied_guest_init = copied_platform / "guest" / "pyro-init"
|
||||
copied_guest_init.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
||||
monkeypatch.setenv("PYRO_RUNTIME_BUNDLE_DIR", str(copied_bundle))
|
||||
with pytest.raises(RuntimeError, match="checksum mismatch"):
|
||||
resolve_runtime_paths()
|
||||
|
||||
|
||||
def test_resolve_runtime_paths_guest_init_manifest_malformed(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
source = resolve_runtime_paths()
|
||||
copied_bundle = tmp_path / "bundle"
|
||||
shutil.copytree(source.bundle_root.parent, copied_bundle)
|
||||
manifest_path = copied_bundle / "linux-x86_64" / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
guest = manifest.get("guest")
|
||||
if not isinstance(guest, dict):
|
||||
raise AssertionError("expected guest manifest section")
|
||||
guest["init"] = {"path": "guest/pyro-init"}
|
||||
manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
||||
monkeypatch.setenv("PYRO_RUNTIME_BUNDLE_DIR", str(copied_bundle))
|
||||
with pytest.raises(RuntimeError, match="runtime guest init manifest entry is malformed"):
|
||||
resolve_runtime_paths()
|
||||
|
||||
|
||||
def test_doctor_report_has_runtime_fields() -> None:
|
||||
report = doctor_report()
|
||||
assert "runtime_ok" in report
|
||||
|
|
@ -72,6 +114,7 @@ def test_doctor_report_has_runtime_fields() -> None:
|
|||
assert isinstance(runtime, dict)
|
||||
assert "firecracker_bin" in runtime
|
||||
assert "guest_agent_path" in runtime
|
||||
assert "guest_init_path" in runtime
|
||||
assert "component_versions" in runtime
|
||||
assert "environments" in runtime
|
||||
networking = report["networking"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue