diff --git a/pkg/csi/materializer.go b/pkg/csi/materializer.go index f739805..3717fda 100644 --- a/pkg/csi/materializer.go +++ b/pkg/csi/materializer.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "os" "path/filepath" "sort" @@ -201,6 +202,9 @@ func (m *Materializer) materialize(d digest.Digest) error { m.logf("materializing %s failed after %s: %v", d, time.Since(start).Round(time.Millisecond), err) return fmt.Errorf("pull %s: %w", d, err) } + if err := readableByAll(tmp); err != nil { + return err + } if err := os.Rename(tmp, dir); err != nil { if isDir(dir) { // published by someone else meanwhile; same content return nil @@ -401,3 +405,30 @@ func (m *Materializer) busy(d digest.Digest) bool { n, err := m.users(d) return err != nil || n > 0 } + +// readableByAll lets every user read the files of an artifact and list its directories (#62). +// The plugin runs as root and keeps each file's mode from the manifest, so a 0600 file would be +// unreadable for an engine that runs as another user; the volume is a read-only bind, so reading +// is all a pod gets. +func readableByAll(root string) error { + return filepath.WalkDir(root, func(p string, e fs.DirEntry, err error) error { + if err != nil { + return err + } + if e.Type()&fs.ModeSymlink != 0 { + return nil + } + fi, err := e.Info() + if err != nil { + return err + } + want := fi.Mode().Perm() | 0o444 + if e.IsDir() { + want |= 0o111 + } + if want == fi.Mode().Perm() { + return nil + } + return os.Chmod(p, want) + }) +} diff --git a/pkg/csi/materializer_test.go b/pkg/csi/materializer_test.go index 2987fbb..08f09c5 100644 --- a/pkg/csi/materializer_test.go +++ b/pkg/csi/materializer_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "io/fs" "math/rand" "os" "path/filepath" @@ -291,3 +292,42 @@ func TestLeases(t *testing.T) { } sameTree(t, f.srcA, dir) } + +// Files stored with mode 0600 become readable by everyone once materialized (#62). +func TestMaterializedFilesAreReadableByAll(t *testing.T) { + ctx := context.Background() + f := newFixture(t) + src := t.TempDir() + if err := os.WriteFile(filepath.Join(src, "model-00001.safetensors"), random(5, 1<<20), 0o600); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(src, "private"), 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(src, "private", "params.bin"), random(6, 4096), 0o600); err != nil { + t.Fatal(err) + } + d, _, err := f.s.Push(ctx, src, store.PushOptions{}) + if err != nil { + t.Fatal(err) + } + m, _ := newMaterializer(t, f) + dir, err := m.Ensure(ctx, d) + if err != nil { + t.Fatal(err) + } + sameTree(t, src, dir) + for _, p := range []string{"model-00001.safetensors", "private", "private/params.bin"} { + fi, err := os.Stat(filepath.Join(dir, p)) + if err != nil { + t.Fatal(err) + } + want := fs.FileMode(0o444) + if fi.IsDir() { + want = 0o555 + } + if fi.Mode().Perm()&want != want { + t.Errorf("%s: mode %v, want at least %v", p, fi.Mode().Perm(), want) + } + } +}