forked from wrenn/wrenn
Rename sandbox prefix to cl-, add MMDS metadata, fix proxy port routing
- Change sandbox ID prefix from sb- to cl- (capsule) throughout - Fix proxy URL regex character class: base36 uses 0-9a-z, not just hex - Add MMDS V2 config and metadata to VM boot flow so envd can read WRENN_SANDBOX_ID and WRENN_TEMPLATE_ID from inside the guest - Pass TemplateID through VMConfig into both fresh and snapshot boot paths
This commit is contained in:
@ -18,9 +18,9 @@ import (
|
|||||||
"git.omukk.dev/wrenn/sandbox/internal/lifecycle"
|
"git.omukk.dev/wrenn/sandbox/internal/lifecycle"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sandboxHostPattern matches hostnames like "49999-sb-abcd1234.localhost" or
|
// sandboxHostPattern matches hostnames like "49999-cl-abcd1234.localhost" or
|
||||||
// "49999-sb-abcd1234.example.com". Captures: port, sandbox ID.
|
// "49999-cl-abcd1234.example.com". Captures: port, sandbox ID.
|
||||||
var sandboxHostPattern = regexp.MustCompile(`^(\d+)-(sb-[0-9a-f-]+)\.`)
|
var sandboxHostPattern = regexp.MustCompile(`^(\d+)-(cl-[0-9a-z]+)\.`)
|
||||||
|
|
||||||
// SandboxProxyWrapper wraps an existing HTTP handler and intercepts requests
|
// SandboxProxyWrapper wraps an existing HTTP handler and intercepts requests
|
||||||
// whose Host header matches the {port}-{sandbox_id}.{domain} pattern. Matching
|
// whose Host header matches the {port}-{sandbox_id}.{domain} pattern. Matching
|
||||||
@ -48,7 +48,7 @@ func NewSandboxProxyWrapper(inner http.Handler, queries *db.Queries, pool *lifec
|
|||||||
|
|
||||||
func (h *SandboxProxyWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *SandboxProxyWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
host := r.Host
|
host := r.Host
|
||||||
// Strip port from Host header (e.g. "49999-sb-abcd1234.localhost:8000" → "49999-sb-abcd1234.localhost")
|
// Strip port from Host header (e.g. "49999-cl-abcd1234.localhost:8000" → "49999-cl-abcd1234.localhost")
|
||||||
if colonIdx := strings.LastIndex(host, ":"); colonIdx != -1 {
|
if colonIdx := strings.LastIndex(host, ":"); colonIdx != -1 {
|
||||||
host = host[:colonIdx]
|
host = host[:colonIdx]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ func NewRefreshToken() string {
|
|||||||
// --- Formatting (pgtype.UUID → prefixed string for API/RPC output) ---
|
// --- Formatting (pgtype.UUID → prefixed string for API/RPC output) ---
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PrefixSandbox = "sb-"
|
PrefixSandbox = "cl-"
|
||||||
PrefixUser = "usr-"
|
PrefixUser = "usr-"
|
||||||
PrefixTeam = "team-"
|
PrefixTeam = "team-"
|
||||||
PrefixAPIKey = "key-"
|
PrefixAPIKey = "key-"
|
||||||
|
|||||||
@ -46,8 +46,8 @@ func TestFormatParseRoundTrip(t *testing.T) {
|
|||||||
id := NewSandboxID()
|
id := NewSandboxID()
|
||||||
formatted := FormatSandboxID(id)
|
formatted := FormatSandboxID(id)
|
||||||
|
|
||||||
if formatted[:3] != "sb-" {
|
if formatted[:3] != "cl-" {
|
||||||
t.Fatalf("expected sb- prefix, got %s", formatted)
|
t.Fatalf("expected cl- prefix, got %s", formatted)
|
||||||
}
|
}
|
||||||
if len(formatted) != 3+base36IDLen {
|
if len(formatted) != 3+base36IDLen {
|
||||||
t.Fatalf("expected %d chars total, got %d: %s", 3+base36IDLen, len(formatted), formatted)
|
t.Fatalf("expected %d chars total, got %d: %s", 3+base36IDLen, len(formatted), formatted)
|
||||||
|
|||||||
@ -96,8 +96,8 @@ func TestTemplateRootfs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPauseSnapshotDir(t *testing.T) {
|
func TestPauseSnapshotDir(t *testing.T) {
|
||||||
got := PauseSnapshotDir("/var/lib/wrenn", "sb-abc123")
|
got := PauseSnapshotDir("/var/lib/wrenn", "cl-abc123")
|
||||||
want := "/var/lib/wrenn/snapshots/sb-abc123"
|
want := "/var/lib/wrenn/snapshots/cl-abc123"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("PauseSnapshotDir() = %q, want %q", got, want)
|
t.Errorf("PauseSnapshotDir() = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,6 +169,7 @@ func (m *Manager) Create(ctx context.Context, sandboxID string, teamID, template
|
|||||||
// Boot VM — Firecracker gets the dm device path.
|
// Boot VM — Firecracker gets the dm device path.
|
||||||
vmCfg := vm.VMConfig{
|
vmCfg := vm.VMConfig{
|
||||||
SandboxID: sandboxID,
|
SandboxID: sandboxID,
|
||||||
|
TemplateID: id.UUIDString(templateID),
|
||||||
KernelPath: layout.KernelPath(m.cfg.WrennDir),
|
KernelPath: layout.KernelPath(m.cfg.WrennDir),
|
||||||
RootfsPath: dmDev.DevicePath,
|
RootfsPath: dmDev.DevicePath,
|
||||||
VCPUs: vcpus,
|
VCPUs: vcpus,
|
||||||
@ -1033,6 +1034,7 @@ func (m *Manager) createFromSnapshot(ctx context.Context, sandboxID string, team
|
|||||||
// Restore VM.
|
// Restore VM.
|
||||||
vmCfg := vm.VMConfig{
|
vmCfg := vm.VMConfig{
|
||||||
SandboxID: sandboxID,
|
SandboxID: sandboxID,
|
||||||
|
TemplateID: id.UUIDString(templateID),
|
||||||
KernelPath: layout.KernelPath(m.cfg.WrennDir),
|
KernelPath: layout.KernelPath(m.cfg.WrennDir),
|
||||||
RootfsPath: dmDev.DevicePath,
|
RootfsPath: dmDev.DevicePath,
|
||||||
VCPUs: vcpus,
|
VCPUs: vcpus,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ func TestSafeName(t *testing.T) {
|
|||||||
{"simple", "minimal", false},
|
{"simple", "minimal", false},
|
||||||
{"with-dash", "template-abc123", false},
|
{"with-dash", "template-abc123", false},
|
||||||
{"with-dot", "my-snapshot.v2", false},
|
{"with-dot", "my-snapshot.v2", false},
|
||||||
{"sandbox-id", "sb-12345678", false},
|
{"sandbox-id", "cl-12345678", false},
|
||||||
{"single-char", "a", false},
|
{"single-char", "a", false},
|
||||||
{"numbers", "123", false},
|
{"numbers", "123", false},
|
||||||
{"max-length", "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01", false},
|
{"max-length", "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01", false},
|
||||||
|
|||||||
@ -4,9 +4,13 @@ import "fmt"
|
|||||||
|
|
||||||
// VMConfig holds the configuration for creating a Firecracker microVM.
|
// VMConfig holds the configuration for creating a Firecracker microVM.
|
||||||
type VMConfig struct {
|
type VMConfig struct {
|
||||||
// SandboxID is the unique identifier for this sandbox (e.g., "sb-a1b2c3d4").
|
// SandboxID is the unique identifier for this sandbox (e.g., "cl-a1b2c3d4").
|
||||||
SandboxID string
|
SandboxID string
|
||||||
|
|
||||||
|
// TemplateID is the template UUID string used to populate MMDS metadata
|
||||||
|
// so that envd can read WRENN_TEMPLATE_ID from inside the guest.
|
||||||
|
TemplateID string
|
||||||
|
|
||||||
// KernelPath is the path to the uncompressed Linux kernel (vmlinux).
|
// KernelPath is the path to the uncompressed Linux kernel (vmlinux).
|
||||||
KernelPath string
|
KernelPath string
|
||||||
|
|
||||||
|
|||||||
@ -101,6 +101,31 @@ func (c *fcClient) setMachineConfig(ctx context.Context, vcpus, memMB int) error
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setMMDSConfig enables MMDS V2 token-based access on the given network interface.
|
||||||
|
// Must be called before startVM.
|
||||||
|
func (c *fcClient) setMMDSConfig(ctx context.Context, ifaceID string) error {
|
||||||
|
return c.do(ctx, http.MethodPut, "/mmds/config", map[string]any{
|
||||||
|
"version": "V2",
|
||||||
|
"network_interfaces": []string{ifaceID},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmdsMetadata is the metadata payload written to the Firecracker MMDS store.
|
||||||
|
// envd reads this via PollForMMDSOpts to populate WRENN_SANDBOX_ID and WRENN_TEMPLATE_ID.
|
||||||
|
type mmdsMetadata struct {
|
||||||
|
SandboxID string `json:"instanceID"`
|
||||||
|
TemplateID string `json:"envID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMMDS writes sandbox metadata to the Firecracker MMDS store.
|
||||||
|
// Can be called after the VM has started.
|
||||||
|
func (c *fcClient) setMMDS(ctx context.Context, sandboxID, templateID string) error {
|
||||||
|
return c.do(ctx, http.MethodPut, "/mmds", mmdsMetadata{
|
||||||
|
SandboxID: sandboxID,
|
||||||
|
TemplateID: templateID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// startVM issues the InstanceStart action.
|
// startVM issues the InstanceStart action.
|
||||||
func (c *fcClient) startVM(ctx context.Context) error {
|
func (c *fcClient) startVM(ctx context.Context) error {
|
||||||
return c.do(ctx, http.MethodPut, "/actions", map[string]string{
|
return c.do(ctx, http.MethodPut, "/actions", map[string]string{
|
||||||
|
|||||||
@ -71,6 +71,13 @@ func (m *Manager) Create(ctx context.Context, cfg VMConfig) (*VM, error) {
|
|||||||
return nil, fmt.Errorf("start VM: %w", err)
|
return nil, fmt.Errorf("start VM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 5: Push sandbox metadata into MMDS so envd can read
|
||||||
|
// WRENN_SANDBOX_ID and WRENN_TEMPLATE_ID from inside the guest.
|
||||||
|
if err := client.setMMDS(ctx, cfg.SandboxID, cfg.TemplateID); err != nil {
|
||||||
|
_ = proc.stop()
|
||||||
|
return nil, fmt.Errorf("set MMDS metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
vm := &VM{
|
vm := &VM{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
process: proc,
|
process: proc,
|
||||||
@ -108,6 +115,12 @@ func configureVM(ctx context.Context, client *fcClient, cfg *VMConfig) error {
|
|||||||
return fmt.Errorf("set machine config: %w", err)
|
return fmt.Errorf("set machine config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MMDS config — enable V2 token access on eth0 so that envd can read
|
||||||
|
// WRENN_SANDBOX_ID and WRENN_TEMPLATE_ID from inside the guest.
|
||||||
|
if err := client.setMMDSConfig(ctx, "eth0"); err != nil {
|
||||||
|
return fmt.Errorf("set MMDS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +251,12 @@ func (m *Manager) CreateFromSnapshot(ctx context.Context, cfg VMConfig, snapPath
|
|||||||
return nil, fmt.Errorf("resume VM: %w", err)
|
return nil, fmt.Errorf("resume VM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 5: Push sandbox metadata into MMDS.
|
||||||
|
if err := client.setMMDS(ctx, cfg.SandboxID, cfg.TemplateID); err != nil {
|
||||||
|
_ = proc.stop()
|
||||||
|
return nil, fmt.Errorf("set MMDS metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
vm := &VM{
|
vm := &VM{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
process: proc,
|
process: proc,
|
||||||
|
|||||||
Reference in New Issue
Block a user