196 lines
7.0 KiB
Go
Executable File
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 ""
|
|
} |