Phase B-1: ownership fixup via debugfs pass
imagepull.Flatten now captures per-file uid/gid/mode/type from the tar headers as it walks layers, returning a Metadata map alongside the extracted tree. Whiteouts correctly drop the victim's metadata. The returned Metadata feeds the new imagepull.ApplyOwnership, which pipes a batched `set_inode_field` script to `debugfs -w -f -`. Why: mkfs.ext4 -d copies the runner's on-disk uids verbatim, so without this pass setuid binaries become setuid-nonroot and sshd refuses to start on the resulting image. With the pass, a pulled debian:bookworm has /usr/bin/sudo with uid=0 + setuid bit surviving intact. imagepull.BuildExt4 signature unchanged; ownership is applied as a separate step by the daemon orchestrator between BuildExt4 and StageBootArtifacts, keeping each helper focused. The seam (d.pullAndFlatten) now returns (Metadata, error) for test stubs to feed synthetic metadata. StdinRunner is a new duck-typed extension next to CommandRunner; the real system.Runner implements RunStdin, test mocks don't need to unless they exercise stdin. Prevents every existing mock from growing a new method. Tests: - TestFlattenCapturesHeaderMetadata: setuid bit + mode survive the tar-header walk - TestApplyOwnershipRewritesUidGidMode: real debugfs round-trip — create ext4 with runner's uid, apply synthetic metadata setting uid=0 + setuid mode, verify via `debugfs -R stat` that the inode now has uid=0 and mode 04755 - TestBuildOwnershipScriptDeterministic: sorted, well-formed sif script output Debugfs and mkfs.ext4 tests skip if the binaries aren't on PATH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2e4d4b14da
commit
43982a4ae3
7 changed files with 334 additions and 32 deletions
|
|
@ -27,6 +27,14 @@ type CommandRunner interface {
|
|||
RunSudo(ctx context.Context, args ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
// StdinRunner is a duck-typed extension to CommandRunner for callers
|
||||
// that need to pipe stdin into a command (e.g. `debugfs -w -f -`). The
|
||||
// real system.Runner implements it; test doubles don't need to unless
|
||||
// they exercise this path.
|
||||
type StdinRunner interface {
|
||||
RunStdin(ctx context.Context, stdin io.Reader, name string, args ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
func NewRunner() Runner {
|
||||
return Runner{}
|
||||
}
|
||||
|
|
@ -51,6 +59,25 @@ func (r Runner) RunSudo(ctx context.Context, args ...string) ([]byte, error) {
|
|||
return r.Run(ctx, "sudo", all...)
|
||||
}
|
||||
|
||||
// RunStdin executes name with args and pipes stdin in from the provided
|
||||
// reader. Used for commands like debugfs -w that accept a scripted
|
||||
// command stream on stdin.
|
||||
func (Runner) RunStdin(ctx context.Context, stdin io.Reader, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdin = stdin
|
||||
if err := cmd.Run(); err != nil {
|
||||
if stderr.Len() > 0 {
|
||||
return stdout.Bytes(), fmt.Errorf("%w: %s", err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func EnsureSudo(ctx context.Context) error {
|
||||
cmd := exec.CommandContext(ctx, "sudo", "-v")
|
||||
cmd.Stdout = os.Stdout
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue