Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions experiments/memorybundle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Experimental full-VM artifact bundle

This operator-only tool reuses `checkpoint` streaming authenticated encryption
for a **disposable same-host memory-resume experiment**. It changes no driver,
resume API, checkpoint format, or production policy. Authenticated guest RAM can
contain credentials; production's existing prohibition remains in force.

Build with `GOOS=linux GOARCH=amd64 go build -o memorybundle ./experiments/memorybundle`.
The source is a private (`0700`) directory containing exactly six nonempty regular
files: `memory`, `vmstate`, `rootfs`, `workspace`, `home`, and `metadata.json`.
The operator must pause the guest and capture matching disk contents before
sealing; this tool cannot establish that consistency itself.

```sh
memorybundle -action seal -store /private/experiment/bundle -key /private/experiment/key -dir /private/experiment/capture -generation 1
memorybundle -action verify -store /private/experiment/bundle -key /private/experiment/key -generation 1
memorybundle -action open -store /private/experiment/bundle -key /private/experiment/key -dir /private/experiment/restored -generation 1
```

Generate a random 32-byte key in a separate `0600` file. Keep the source, store,
key, and destination parent operator-owned and inaccessible to other writers.
Use a dedicated store, never a production checkpoint store: the context is
intentionally fixed to `memory-experiment` / `synthetic-session`. Specify a new
positive generation for each capture. There is no control-plane authorization
or freshness ledger; possession of the local key authorizes this experiment.

`seal` verifies the encrypted stream after writing it. `verify` authenticates
content but does not check the six-file layout. `open` authenticates into a private
staging directory, validates the layout, then publishes a new destination. It
refuses existing destinations and serializes cooperating opens with an exclusive
lock file. An interrupted open may leave private staging/lock files; inspect and
remove them only after confirming no operation remains active.

Before starting any restored VMM, the separate operator harness must validate
software/CPU compatibility, pin every disk path, retain the network slot, confirm
the original VMM is gone, and prevent replay of an already consumed generation.
The tool does not start or stop VMs, interpret metadata, refresh credentials,
compress sparse files, or preserve sparse allocation on extraction. Full logical
memory and disk sizes therefore determine ciphertext and restore storage costs.

Delete **all** encrypted and plaintext copies, staging files, keys, and disposable
host disks after the experiment. Removing a credential from the live guest does
not remove its earlier copy from a snapshot. Never upload bundles or guest logs
to a PR, CI artifact store, or public issue.
137 changes: 137 additions & 0 deletions experiments/memorybundle/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// memorybundle is an operator-only experiment; it is not a production resume API.
// All paths must be on a private, operator-owned filesystem with no other writers.
package main

import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"

"github.com/tokencanopy/rainier/checkpoint"
"github.com/tokencanopy/rainier/internal/driver"
)

var required = map[string]bool{"memory": true, "vmstate": true, "rootfs": true, "workspace": true, "home": true, "metadata.json": true}

func validate(dir string) error {
info, err := os.Lstat(dir)
if err != nil {
return err
}
if !info.IsDir() || info.Mode().Perm()&0077 != 0 {
return errors.New("artifact directory must be private")
}
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
if len(entries) != len(required) {
return errors.New("incorrect artifact count")
}
for _, e := range entries {
info, err := e.Info()
if err != nil {
return err
}
if !required[e.Name()] || !info.Mode().IsRegular() || info.Size() == 0 {
return errors.New("unexpected or empty artifact")
}
}
return nil
}
func bundle(action, storePath, keyPath, dir string, generation uint64) error {
if action != "seal" && action != "verify" && action != "open" {
return errors.New("unknown action")
}
c := checkpoint.Context{Workspace: "memory-experiment", Session: "synthetic-session", Generation: generation}
if err := c.Validate(); err != nil {
return err
}
key, err := driver.LoadCheckpointKey(keyPath)
if err != nil {
return err
}
store, err := driver.NewDirBlobStore(storePath)
if err != nil {
return err
}
keys, err := checkpoint.NewStaticKeyWrapper("memory-experiment-key", key)
if err != nil {
return err
}
ctx := context.Background()
// The local operator owns this experimental store and key. This is NOT a
// control-plane authorization policy and must never be wired into a service.
reader, err := checkpoint.NewReader(store, keys, checkpoint.ReaderOptions{Authorize: func(context.Context, checkpoint.Context, checkpoint.Manifest) error { return nil }})
if err != nil {
return err
}
switch action {
case "seal":
if err := validate(dir); err != nil {
return err
}
writer, err := checkpoint.NewWriter(store, keys, checkpoint.WriterOptions{KeyRef: "memory-experiment-key"})
if err != nil {
return err
}
if _, err = writer.Write(ctx, c, checkpoint.DirSource(dir)); err != nil {
return err
}
_, err = reader.Verify(ctx, c)
return err
case "verify":
_, err = reader.Verify(ctx, c)
return err
case "open":
if dir == "" {
return errors.New("destination required")
}
absolute, err := filepath.Abs(dir)
if err != nil {
return err
}
lock, err := os.OpenFile(absolute+".lock", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
if err != nil {
return err
}
lock.Close()
defer os.Remove(absolute + ".lock")
if _, err = os.Lstat(absolute); !os.IsNotExist(err) {
return errors.New("destination already exists or cannot be checked")
}
stage, err := os.MkdirTemp(filepath.Dir(absolute), ".memory-restore-")
if err != nil {
return err
}
defer os.RemoveAll(stage)
if _, err = reader.Restore(ctx, c, stage); err != nil {
return err
}
if err = validate(stage); err != nil {
return err
}
return os.Rename(stage, absolute)
}
panic("unreachable")
}
func main() {
action := flag.String("action", "", "seal, verify, or open")
store := flag.String("store", "", "private experimental blob directory")
key := flag.String("key", "", "private 32-byte key file, outside artifact directory")
dir := flag.String("dir", "", "source or new destination directory")
generation := flag.Uint64("generation", 0, "explicit snapshot generation")
flag.Parse()
if *store == "" || *key == "" || flag.NArg() != 0 {
fmt.Fprintln(os.Stderr, "store and key required; no positional arguments")
os.Exit(2)
}
if err := bundle(*action, *store, *key, *dir, *generation); err != nil {
fmt.Fprintln(os.Stderr, "memory bundle operation failed:", err)
os.Exit(1)
}
fmt.Println("memory bundle operation verified")
}
147 changes: 147 additions & 0 deletions experiments/memorybundle/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package main

import (
"bytes"
"context"
"github.com/tokencanopy/rainier/checkpoint"
"github.com/tokencanopy/rainier/internal/driver"
"os"
"path/filepath"
"testing"
)

var artifacts = []string{"memory", "vmstate", "rootfs", "workspace", "home", "metadata.json"}

func fixture(t *testing.T) (string, string, string) {
t.Helper()
root := t.TempDir()
source := filepath.Join(root, "source")
os.Mkdir(source, 0700)
for _, n := range artifacts {
if err := os.WriteFile(filepath.Join(source, n), []byte("synthetic "+n), 0600); err != nil {
t.Fatal(err)
}
}
key := filepath.Join(root, "key")
os.WriteFile(key, bytes.Repeat([]byte{42}, 32), 0600)
return source, filepath.Join(root, "store"), key
}
func TestRoundTrip(t *testing.T) {
source, store, key := fixture(t)
if err := bundle("seal", store, key, source, 1); err != nil {
t.Fatal(err)
}
if err := bundle("verify", store, key, "", 1); err != nil {
t.Fatal(err)
}
target := filepath.Join(t.TempDir(), "restore")
if err := bundle("open", store, key, target, 1); err != nil {
t.Fatal(err)
}
for _, n := range artifacts {
b, e := os.ReadFile(filepath.Join(target, n))
if e != nil || string(b) != "synthetic "+n {
t.Fatalf("artifact %s mismatch", n)
}
}
if err := bundle("open", store, key, target, 1); err == nil {
t.Fatal("overwrote existing destination")
}
}
func TestRejectInvalidSource(t *testing.T) {
for _, kind := range []string{"missing", "extra", "symlink", "directory"} {
t.Run(kind, func(t *testing.T) {
source, store, key := fixture(t)
switch kind {
case "missing":
os.Remove(filepath.Join(source, "home"))
case "extra":
os.WriteFile(filepath.Join(source, "extra"), []byte("x"), 0600)
case "symlink":
os.Remove(filepath.Join(source, "home"))
os.Symlink(key, filepath.Join(source, "home"))
case "directory":
os.Remove(filepath.Join(source, "home"))
os.Mkdir(filepath.Join(source, "home"), 0700)
}
if err := bundle("seal", store, key, source, 1); err == nil {
t.Fatal("accepted invalid source")
}
})
}
}
func TestRejectDamageWithoutPublishing(t *testing.T) {
for _, kind := range []string{"wrong-key", "tamper", "truncate", "wrong-generation"} {
t.Run(kind, func(t *testing.T) {
source, store, key := fixture(t)
if err := bundle("seal", store, key, source, 1); err != nil {
t.Fatal(err)
}
generation := uint64(1)
if kind == "wrong-key" {
os.WriteFile(key, bytes.Repeat([]byte{43}, 32), 0600)
} else if kind == "wrong-generation" {
generation = 2
} else {
var largest string
var size int64
filepath.Walk(store, func(p string, i os.FileInfo, e error) error {
if e == nil && i.Mode().IsRegular() && i.Size() > size {
largest = p
size = i.Size()
}
return e
})
b, e := os.ReadFile(largest)
if e != nil {
t.Fatal(e)
}
if kind == "tamper" {
b[len(b)/2] ^= 1
} else {
b = b[:len(b)/2]
}
os.WriteFile(largest, b, 0600)
}
target := filepath.Join(t.TempDir(), "restore")
if err := bundle("open", store, key, target, generation); err == nil {
t.Fatal("accepted damaged bundle")
}
if _, err := os.Lstat(target); !os.IsNotExist(err) {
t.Fatal("published failed restore")
}
})
}
}

func TestRestoreRejectsAuthenticatedIncompleteBundle(t *testing.T) {
source, storePath, keyPath := fixture(t)
os.Remove(filepath.Join(source, "home"))
key, err := driver.LoadCheckpointKey(keyPath)
if err != nil {
t.Fatal(err)
}
store, err := driver.NewDirBlobStore(storePath)
if err != nil {
t.Fatal(err)
}
keys, err := checkpoint.NewStaticKeyWrapper("memory-experiment-key", key)
if err != nil {
t.Fatal(err)
}
writer, err := checkpoint.NewWriter(store, keys, checkpoint.WriterOptions{KeyRef: "memory-experiment-key"})
if err != nil {
t.Fatal(err)
}
_, err = writer.Write(context.Background(), checkpoint.Context{Workspace: "memory-experiment", Session: "synthetic-session", Generation: 1}, checkpoint.DirSource(source))
if err != nil {
t.Fatal(err)
}
target := filepath.Join(t.TempDir(), "restore")
if err := bundle("open", storePath, keyPath, target, 1); err == nil {
t.Fatal("published incomplete but authenticated bundle")
}
if _, err := os.Lstat(target); !os.IsNotExist(err) {
t.Fatal("failed restore was published")
}
}
Loading