sorcery-go/pkg/runtime/baremetal.go

196 lines
7.0 KiB
Go
Executable File

// BareMetal runtime adapter for Sorcery-Go.
//
// BareMetal deploys Essences directly to the host filesystem without
// any container or VM isolation. This is the simplest mode and is
// useful for:
// - CI environments where isolation is handled externally
// - Single-node development setups
// - Systems where container runtimes are unavailable
//
// Security enforcement still applies: the eBPF Tomb Guard LSM hook
// protects the Tomb regardless of whether containers are used.
package runtime
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
// BareMetalRuntime deploys Essences directly to the host filesystem.
type BareMetalRuntime struct {
deployRoot string
}
// NewBareMetalRuntime creates a baremetal runtime adapter.
func NewBareMetalRuntime() *BareMetalRuntime {
return &BareMetalRuntime{
deployRoot: "/opt/sorcery/sanctums",
}
}
func (r *BareMetalRuntime) Type() Type { return RuntimeBareMetal }
func (r *BareMetalRuntime) Name() string { return "Bare Metal (no container)" }
// Probe always succeeds — baremetal is always available.
func (r *BareMetalRuntime) Probe() error { return nil }
// Create creates a directory for the sanctum deployment.
func (r *BareMetalRuntime) Create(ctx context.Context, opts CreateOpts) (string, error) {
sanctumPath := filepath.Join(r.deployRoot, opts.Name)
if err := os.MkdirAll(sanctumPath, 0755); err != nil {
return "", err
}
// TODO: persist sanctum metadata (name, runtime, arch, created) to a
// JSON sidecar file under sanctumPath/metadata.json for status reporting.
return opts.Name, nil
}
// Start is a no-op for baremetal (the files are already on disk).
func (r *BareMetalRuntime) Start(ctx context.Context, sanctumID string) error {
return nil
}
// Stop is a no-op for baremetal.
func (r *BareMetalRuntime) Stop(ctx context.Context, sanctumID string) error {
return nil
}
// Freeze sends SIGSTOP to all processes running from the sanctum path.
func (r *BareMetalRuntime) Freeze(ctx context.Context, sanctumID string) error {
// For baremetal, we can try to freeze via the cgroup of any process
// running from the sanctum directory. This is a best-effort approach.
cgPath := r.CgroupPath(sanctumID)
if cgPath != "" {
freezeFile := cgPath + "/cgroup.freeze"
if _, err := os.Stat(freezeFile); err == nil {
return os.WriteFile(freezeFile, []byte("1"), 0644)
}
}
return nil
}
// Thaw resumes a frozen baremetal sanctum.
func (r *BareMetalRuntime) Thaw(ctx context.Context, sanctumID string) error {
cgPath := r.CgroupPath(sanctumID)
if cgPath != "" {
freezeFile := cgPath + "/cgroup.freeze"
if _, err := os.Stat(freezeFile); err == nil {
return os.WriteFile(freezeFile, []byte("0"), 0644)
}
}
return nil
}
// Destroy removes the sanctum directory.
func (r *BareMetalRuntime) Destroy(ctx context.Context, sanctumID string) error {
sanctumPath := filepath.Join(r.deployRoot, sanctumID)
return os.RemoveAll(sanctumPath)
}
// Exec runs a command in the sanctum's chroot using chroot(2).
func (r *BareMetalRuntime) Exec(ctx context.Context, sanctumID string, command []string, stdin []byte) (*ExecResult, error) {
sanctumPath := filepath.Join(r.deployRoot, sanctumID)
if len(command) == 0 {
return &ExecResult{}, nil
}
// Use chroot to execute the command in the sanctum environment.
// This requires CAP_SYS_CHROOT.
cmd := exec.CommandContext(ctx, "chroot", sanctumPath, command[0])
cmd.Args = append([]string{command[0]}, command[1:]...)
if len(stdin) > 0 {
cmd.Stdin = strings.NewReader(string(stdin))
}
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return &ExecResult{
ExitCode: exitCode(err),
Stdout: []byte(stdout.String()),
Stderr: []byte(stderr.String()),
}, err
}
// Status returns the status of a baremetal sanctum.
func (r *BareMetalRuntime) Status(ctx context.Context, sanctumID string) (*SanctumInfo, error) {
sanctumPath := filepath.Join(r.deployRoot, sanctumID)
info := &SanctumInfo{
ID: sanctumID,
Name: sanctumID,
Runtime: RuntimeBareMetal,
Status: StatusStopped,
RootFS: sanctumPath,
}
st, err := os.Stat(sanctumPath)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("baremetal: sanctum %s does not exist", sanctumID)
}
return nil, err
}
// Check if any processes are running from this directory (heuristic).
info.Created = time.Unix(st.Sys().(*syscall.Stat_t).Ctim.Unix(), 0)
info.Status = StatusRunning // baremetal is always "running" if it exists
info.Cgroup = r.CgroupPath(sanctumID)
return info, nil
}
// List returns all baremetal sanctums.
func (r *BareMetalRuntime) List(ctx context.Context) ([]*SanctumInfo, error) {
entries, err := os.ReadDir(r.deployRoot)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var infos []*SanctumInfo
for _, e := range entries {
if !e.IsDir() {
continue
}
info, err := r.Status(ctx, e.Name())
if err != nil {
continue
}
infos = append(infos, info)
}
return infos, nil
}
// CgroupPath returns the cgroup path for baremetal processes.
// For baremetal, this typically falls back to the system.slice.
func (r *BareMetalRuntime) CgroupPath(sanctumID string) string {
// Baremetal doesn't have a dedicated cgroup.
// Return empty — the eBPF LSM hook still protects the Tomb at the
// kernel level regardless of cgroup attachment.
return ""
}
// --- shared cgroup helpers ---
// cgroupV2PathByPID tries to find the cgroup v2 path for a container
// by inspecting /proc/<pid>/cgroup. This is a fallback when the
// runtime-specific cgroup path detection fails.
func cgroupV2PathByPID(sanctumID string) string {
// This is a simplified implementation. A production version would:
// 1. Find the init PID of the container (via runtime-specific methods)
// 2. Read /proc/<pid>/cgroup
// 3. Parse the cgroup v2 hierarchy path
// For now, return empty — the runtime-specific paths should handle
// the common cases.
return ""
}