Skip to content

Commit

Permalink
fix(remount): ensure mountpoint is a file for files (#249)
Browse files Browse the repository at this point in the history
Ensures that read-only bind mounts of single files are created properly.

Co-authored-by: Cian Johnston <[email protected]>
  • Loading branch information
maxbrunet and johnstcn authored Jun 26, 2024
1 parent b065656 commit 8c5c50c
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 13 deletions.
9 changes: 7 additions & 2 deletions envbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,15 @@ func Run(ctx context.Context, options Options) error {
// https://github.com/GoogleContainerTools/kaniko/blob/63be4990ca5a60bdf06ddc4d10aa4eca0c0bc714/cmd/executor/cmd/root.go#L136
ignorePaths := append([]string{
MagicDir,
options.LayerCacheDir,
options.WorkspaceFolder,
// See: https://github.com/coder/envbuilder/issues/37
"/etc/resolv.conf",
}, options.IgnorePaths...)

if options.LayerCacheDir != "" {
ignorePaths = append(ignorePaths, options.LayerCacheDir)
}

for _, ignorePath := range ignorePaths {
util.AddToDefaultIgnoreList(util.IgnoreListEntry{
Path: ignorePath,
Expand Down Expand Up @@ -433,7 +436,9 @@ ENTRYPOINT [%q]`, exePath, exePath, exePath)

// temp move of all ro mounts
tempRemountDest := filepath.Join("/", MagicDir, "mnt")
ignorePrefixes := []string{tempRemountDest, "/proc", "/sys"}
// ignorePrefixes is a superset of ignorePaths that we pass to kaniko's
// IgnoreList.
ignorePrefixes := append([]string{"/proc", "/sys"}, ignorePaths...)
restoreMounts, err := ebutil.TempRemount(options.Logger, tempRemountDest, ignorePrefixes...)
defer func() { // restoreMounts should never be nil
if err := restoreMounts(); err != nil {
Expand Down
39 changes: 29 additions & 10 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -402,19 +403,37 @@ func TestBuildIgnoreVarRunSecrets(t *testing.T) {
},
})
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "secret"), []byte("test"), 0o644)
secretVal := uuid.NewString()
err := os.WriteFile(filepath.Join(dir, "secret"), []byte(secretVal), 0o644)
require.NoError(t, err)
ctr, err := runEnvbuilder(t, options{
env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
},
binds: []string{fmt.Sprintf("%s:/var/run/secrets", dir)},

t.Run("ReadWrite", func(t *testing.T) {
ctr, err := runEnvbuilder(t, options{
env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
},
binds: []string{fmt.Sprintf("%s:/var/run/secrets:rw", dir)},
})
require.NoError(t, err)

output := execContainer(t, ctr, "cat /var/run/secrets/secret")
require.Equal(t, secretVal, strings.TrimSpace(output))
})
require.NoError(t, err)

output := execContainer(t, ctr, "echo hello")
require.Equal(t, "hello", strings.TrimSpace(output))
t.Run("ReadOnly", func(t *testing.T) {
ctr, err := runEnvbuilder(t, options{
env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
},
binds: []string{fmt.Sprintf("%s:/var/run/secrets:ro", dir)},
})
require.NoError(t, err)

output := execContainer(t, ctr, "cat /var/run/secrets/secret")
require.Equal(t, secretVal, strings.TrimSpace(output))
})
}

func TestBuildWithSetupScript(t *testing.T) {
Expand Down
30 changes: 30 additions & 0 deletions internal/ebutil/mock_mounter_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 30 additions & 1 deletion internal/ebutil/remount.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,26 @@ outer:
}

func remount(m mounter, src, dest string) error {
if err := m.MkdirAll(dest, 0o750); err != nil {
stat, err := m.Stat(src)
if err != nil {
return fmt.Errorf("stat %s: %w", src, err)
}
var destDir string
if stat.IsDir() {
destDir = dest
} else {
destDir = filepath.Dir(dest)
}
if err := m.MkdirAll(destDir, 0o750); err != nil {
return fmt.Errorf("ensure path: %w", err)
}
if !stat.IsDir() {
f, err := m.OpenFile(dest, os.O_CREATE, 0o640)
if err != nil {
return fmt.Errorf("ensure file path: %w", err)
}
defer f.Close()
}
if err := m.Mount(src, dest, "bind", syscall.MS_BIND, ""); err != nil {
return fmt.Errorf("bind mount %s => %s: %w", src, dest, err)
}
Expand All @@ -104,8 +121,12 @@ func remount(m mounter, src, dest string) error {
type mounter interface {
// GetMounts wraps procfs.GetMounts
GetMounts() ([]*procfs.MountInfo, error)
// Stat wraps os.Stat
Stat(string) (os.FileInfo, error)
// MkdirAll wraps os.MkdirAll
MkdirAll(string, os.FileMode) error
// OpenFile wraps os.OpenFile
OpenFile(string, int, os.FileMode) (*os.File, error)
// Mount wraps syscall.Mount
Mount(string, string, string, uintptr, string) error
// Unmount wraps syscall.Unmount
Expand All @@ -132,3 +153,11 @@ func (m *realMounter) GetMounts() ([]*procfs.MountInfo, error) {
func (m *realMounter) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}

func (m *realMounter) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
return os.OpenFile(name, flag, perm)
}

func (m *realMounter) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
52 changes: 52 additions & 0 deletions internal/ebutil/remount_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"
"syscall"
"testing"
time "time"

"github.com/coder/envbuilder/internal/notcodersdk"
"github.com/stretchr/testify/assert"
Expand All @@ -25,9 +26,11 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil)
mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/.test/var/lib/modules", 0).Times(1).Return(nil)
Expand All @@ -40,6 +43,33 @@ func Test_tempRemount(t *testing.T) {
_ = remount()
})

t.Run("OKFile", func(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
mm := NewMockmounter(ctrl)
mounts := fakeMounts("/home", "/usr/bin/utility:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/usr/bin/utility").Return(&fakeFileInfo{isDir: false}, nil)
mm.EXPECT().MkdirAll("/.test/usr/bin", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().OpenFile("/.test/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil)
mm.EXPECT().Mount("/usr/bin/utility", "/.test/usr/bin/utility", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/usr/bin/utility", 0).Times(1).Return(nil)
mm.EXPECT().Stat("/.test/usr/bin/utility").Return(&fakeFileInfo{isDir: false}, nil)
mm.EXPECT().MkdirAll("/usr/bin", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().OpenFile("/usr/bin/utility", os.O_CREATE, os.FileMode(0o640)).Times(1).Return(new(os.File), nil)
mm.EXPECT().Mount("/.test/usr/bin/utility", "/usr/bin/utility", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/.test/usr/bin/utility", 0).Times(1).Return(nil)

remount, err := tempRemount(mm, fakeLog(t), "/.test")
require.NoError(t, err)
err = remount()
require.NoError(t, err)
// sync.Once should handle multiple remount calls
_ = remount()
})

t.Run("IgnorePrefixes", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -75,6 +105,7 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(assert.AnError)

remount, err := tempRemount(mm, fakeLog(t), "/.test")
Expand All @@ -91,6 +122,7 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(assert.AnError)

Expand All @@ -108,6 +140,7 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(assert.AnError)
Expand All @@ -126,9 +159,11 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil)
mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(assert.AnError)

remount, err := tempRemount(mm, fakeLog(t), "/.test")
Expand All @@ -145,9 +180,11 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil)
mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(assert.AnError)

Expand All @@ -165,9 +202,11 @@ func Test_tempRemount(t *testing.T) {
mounts := fakeMounts("/home", "/var/lib/modules:ro", "/proc", "/sys")

mm.EXPECT().GetMounts().Return(mounts, nil)
mm.EXPECT().Stat("/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/.test/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/var/lib/modules", "/.test/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/var/lib/modules", 0).Times(1).Return(nil)
mm.EXPECT().Stat("/.test/var/lib/modules").Return(&fakeFileInfo{isDir: true}, nil)
mm.EXPECT().MkdirAll("/var/lib/modules", os.FileMode(0o750)).Times(1).Return(nil)
mm.EXPECT().Mount("/.test/var/lib/modules", "/var/lib/modules", "bind", uintptr(syscall.MS_BIND), "").Times(1).Return(nil)
mm.EXPECT().Unmount("/.test/var/lib/modules", 0).Times(1).Return(assert.AnError)
Expand Down Expand Up @@ -200,3 +239,16 @@ func fakeLog(t *testing.T) func(notcodersdk.LogLevel, string, ...any) {
t.Logf(s, a...)
}
}

type fakeFileInfo struct {
isDir bool
}

func (fi *fakeFileInfo) Name() string { return "" }
func (fi *fakeFileInfo) Size() int64 { return 0 }
func (fi *fakeFileInfo) Mode() os.FileMode { return 0 }
func (fi *fakeFileInfo) ModTime() time.Time { return time.Time{} }
func (fi *fakeFileInfo) IsDir() bool { return fi.isDir }
func (fi *fakeFileInfo) Sys() any { return nil }

var _ os.FileInfo = &fakeFileInfo{}

0 comments on commit 8c5c50c

Please sign in to comment.