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
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"banger/internal/api"
|
||||
"banger/internal/imagepull"
|
||||
"banger/internal/model"
|
||||
"banger/internal/paths"
|
||||
"banger/internal/system"
|
||||
|
|
@ -40,14 +41,19 @@ func writeFakeKernelTriple(t *testing.T) (kernelPath, initrdPath, modulesDir str
|
|||
|
||||
// stubPullAndFlatten writes a fixed file tree into destDir, simulating a
|
||||
// successful OCI pull without the network or tarball machinery.
|
||||
func stubPullAndFlatten(_ context.Context, _ string, _ string, destDir string) error {
|
||||
func stubPullAndFlatten(_ context.Context, _ string, _ string, destDir string) (imagepull.Metadata, error) {
|
||||
if err := os.MkdirAll(filepath.Join(destDir, "etc"), 0o755); err != nil {
|
||||
return err
|
||||
return imagepull.Metadata{}, err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(destDir, "etc", "hello"), []byte("world"), 0o644); err != nil {
|
||||
return err
|
||||
return imagepull.Metadata{}, err
|
||||
}
|
||||
return os.WriteFile(filepath.Join(destDir, "marker"), []byte("ok"), 0o644)
|
||||
if err := os.WriteFile(filepath.Join(destDir, "marker"), []byte("ok"), 0o644); err != nil {
|
||||
return imagepull.Metadata{}, err
|
||||
}
|
||||
// Tiny synthetic metadata — daemon-level tests exercise the seam
|
||||
// plumbing, not the ownership pass itself.
|
||||
return imagepull.Metadata{Entries: map[string]imagepull.FileMeta{}}, nil
|
||||
}
|
||||
|
||||
func TestPullImageHappyPath(t *testing.T) {
|
||||
|
|
@ -146,8 +152,8 @@ func TestPullImageRequiresKernel(t *testing.T) {
|
|||
func TestPullImageCleansStagingOnFailure(t *testing.T) {
|
||||
imagesDir := t.TempDir()
|
||||
kernel, _, _ := writeFakeKernelTriple(t)
|
||||
failureSeam := func(_ context.Context, _ string, _ string, _ string) error {
|
||||
return errors.New("network borked")
|
||||
failureSeam := func(_ context.Context, _ string, _ string, _ string) (imagepull.Metadata, error) {
|
||||
return imagepull.Metadata{}, errors.New("network borked")
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue