controllerswap: stdin tee write + narrow FELHOM_CONTROLLERSWAP grants (non-root, v0.45.0)
writeImage drops bash -c/printf for GuestExecStdin(img+\n -> tee /etc/felhom-controller-image); new Runner.RunStdin/GuestExecStdin route stdin through the fenced sudo -n runner. 5 narrow, auditable sudoers grants (no general pct exec, no bash -c) + capability manifest entries (Critical) so the self-probe watches them and the build-test asserts coverage (companion red-proof). No controller change; swap orchestration/rollback/state unchanged. Spike GO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
@@ -26,6 +27,10 @@ import (
|
||||
// production implementation; tests inject a mock to assert which commands ran.
|
||||
type Runner interface {
|
||||
Run(ctx context.Context, name string, args ...string) (stdout, stderr []byte, err error)
|
||||
// RunStdin is Run with the command's stdin fed from r (nil = no stdin). Used by the
|
||||
// controller-swap write (image piped into an in-guest `tee`), so the swap needs no shell
|
||||
// vector. Keeps the sudo-prefix/mode handling in the same place as Run.
|
||||
RunStdin(ctx context.Context, stdin io.Reader, name string, args ...string) (stdout, stderr []byte, err error)
|
||||
}
|
||||
|
||||
// RunnerMode selects how privileged commands are executed.
|
||||
@@ -48,6 +53,12 @@ type ExecRunner struct {
|
||||
|
||||
// Run implements Runner.
|
||||
func (r *ExecRunner) Run(ctx context.Context, name string, args ...string) ([]byte, []byte, error) {
|
||||
return r.RunStdin(ctx, nil, name, args...)
|
||||
}
|
||||
|
||||
// RunStdin is Run with the process stdin fed from stdin (nil = no stdin). The sudo-prefix/mode
|
||||
// handling is identical to Run — kept here so both paths share one place.
|
||||
func (r *ExecRunner) RunStdin(ctx context.Context, stdin io.Reader, name string, args ...string) ([]byte, []byte, error) {
|
||||
var cmd *exec.Cmd
|
||||
if r.Mode == RunnerSudo {
|
||||
sudo := r.SudoPath
|
||||
@@ -59,6 +70,7 @@ func (r *ExecRunner) Run(ctx context.Context, name string, args ...string) ([]by
|
||||
cmd = exec.CommandContext(ctx, name, args...)
|
||||
}
|
||||
var stdout, stderr capBuf
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
Reference in New Issue
Block a user