diff --git a/solver/llbsolver/file/backend.go b/solver/llbsolver/file/backend.go index 6656050b9038..311fc5043d35 100644 --- a/solver/llbsolver/file/backend.go +++ b/solver/llbsolver/file/backend.go @@ -27,46 +27,6 @@ func timestampToTime(ts int64) *time.Time { return &tm } -func mapUserToChowner(user *copy.User, idmap *idtools.IdentityMapping) (copy.Chowner, error) { - if user == nil { - return func(old *copy.User) (*copy.User, error) { - if old == nil { - if idmap == nil { - return nil, nil - } - old = ©.User{} // root - // non-nil old is already mapped - if idmap != nil { - identity, err := idmap.ToHost(idtools.Identity{ - UID: old.UID, - GID: old.GID, - }) - if err != nil { - return nil, err - } - return ©.User{UID: identity.UID, GID: identity.GID}, nil - } - } - return old, nil - }, nil - } - u := *user - if idmap != nil { - identity, err := idmap.ToHost(idtools.Identity{ - UID: user.UID, - GID: user.GID, - }) - if err != nil { - return nil, err - } - u.UID = identity.UID - u.GID = identity.GID - } - return func(*copy.User) (*copy.User, error) { - return &u, nil - }, nil -} - func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *copy.User, idmap *idtools.IdentityMapping) error { p, err := fs.RootPath(d, action.Path) if err != nil { @@ -250,7 +210,21 @@ func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u * return nil } +// NewFileOpBackend returns a new file operation backend. The executor is currently only used for Windows, +// and it is used to construct the readUserFn field set in the returned Backend. +func NewFileOpBackend(readUser ReadUserCallback) (*Backend, error) { + if readUser == nil { + return nil, errors.New("readUser callback must be provided") + } + return &Backend{ + readUser: readUser, + }, nil +} + +type ReadUserCallback func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) + type Backend struct { + readUser ReadUserCallback } func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkDir) error { @@ -266,7 +240,7 @@ func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, } defer lm.Unmount() - u, err := readUser(action.Owner, user, group) + u, err := fb.readUserWrapper(action.Owner, user, group) if err != nil { return err } @@ -287,7 +261,7 @@ func (fb *Backend) Mkfile(ctx context.Context, m, user, group fileoptypes.Mount, } defer lm.Unmount() - u, err := readUser(action.Owner, user, group) + u, err := fb.readUserWrapper(action.Owner, user, group) if err != nil { return err } @@ -335,7 +309,7 @@ func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mou } defer lm2.Unmount() - u, err := readUser(action.Owner, user, group) + u, err := fb.readUserWrapper(action.Owner, user, group) if err != nil { return err } @@ -343,6 +317,33 @@ func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mou return docopy(ctx, src, dest, action, u, mnt2.m.IdentityMapping()) } +func (fb *Backend) readUserWrapper(owner *pb.ChownOpt, user, group fileoptypes.Mount) (*copy.User, error) { + var userMountable, groupMountable snapshot.Mountable + if user != nil { + usr, ok := user.(*Mount) + if !ok { + return nil, errors.Errorf("invalid mount type %T", user) + } + userMountable = usr.Mountable() + } + + if group != nil { + grp, ok := group.(*Mount) + if !ok { + return nil, errors.Errorf("invalid mount type %T", group) + } + groupMountable = grp.Mountable() + } + + // We don't check the mountables for nil here. Depending on the ChownOpt value, + // one of them may be nil. Allow the readUser function to handle this. + u, err := fb.readUser(owner, userMountable, groupMountable) + if err != nil { + return nil, err + } + return u, nil +} + func cleanPath(s string) (string, error) { s, err := system.CheckSystemDriveAndRemoveDriveLetter(s, runtime.GOOS) if err != nil { diff --git a/solver/llbsolver/file/backend_unix.go b/solver/llbsolver/file/backend_unix.go new file mode 100644 index 000000000000..d01290f300ac --- /dev/null +++ b/solver/llbsolver/file/backend_unix.go @@ -0,0 +1,49 @@ +//go:build !windows +// +build !windows + +package file + +import ( + "github.com/docker/docker/pkg/idtools" + copy "github.com/tonistiigi/fsutil/copy" +) + +func mapUserToChowner(user *copy.User, idmap *idtools.IdentityMapping) (copy.Chowner, error) { + if user == nil { + return func(old *copy.User) (*copy.User, error) { + if old == nil { + if idmap == nil { + return nil, nil + } + old = ©.User{} // root + // non-nil old is already mapped + if idmap != nil { + identity, err := idmap.ToHost(idtools.Identity{ + UID: old.UID, + GID: old.GID, + }) + if err != nil { + return nil, err + } + return ©.User{UID: identity.UID, GID: identity.GID}, nil + } + } + return old, nil + }, nil + } + u := *user + if idmap != nil { + identity, err := idmap.ToHost(idtools.Identity{ + UID: user.UID, + GID: user.GID, + }) + if err != nil { + return nil, err + } + u.UID = identity.UID + u.GID = identity.GID + } + return func(*copy.User) (*copy.User, error) { + return &u, nil + }, nil +} diff --git a/solver/llbsolver/file/backend_windows.go b/solver/llbsolver/file/backend_windows.go new file mode 100644 index 000000000000..03f3bbe7b308 --- /dev/null +++ b/solver/llbsolver/file/backend_windows.go @@ -0,0 +1,22 @@ +package file + +import ( + "github.com/docker/docker/pkg/idtools" + copy "github.com/tonistiigi/fsutil/copy" +) + +func mapUserToChowner(user *copy.User, idmap *idtools.IdentityMapping) (copy.Chowner, error) { + if user == nil || user.SID == "" { + return func(old *copy.User) (*copy.User, error) { + if old == nil || old.SID == "" { + old = ©.User{ + SID: idtools.ContainerAdministratorSidString, + } + } + return old, nil + }, nil + } + return func(*copy.User) (*copy.User, error) { + return user, nil + }, nil +} diff --git a/solver/llbsolver/file/refmanager.go b/solver/llbsolver/file/refmanager.go index b9f3b2ea3ca3..cb26ba288665 100644 --- a/solver/llbsolver/file/refmanager.go +++ b/solver/llbsolver/file/refmanager.go @@ -79,6 +79,10 @@ type Mount struct { readonly bool } +func (m *Mount) Mountable() snapshot.Mountable { + return m.m +} + func (m *Mount) Release(ctx context.Context) error { if m.mr != nil { return m.mr.Release(ctx) diff --git a/solver/llbsolver/file/user_nolinux.go b/solver/llbsolver/file/user_nolinux.go deleted file mode 100644 index 80652fd4abe4..000000000000 --- a/solver/llbsolver/file/user_nolinux.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !linux -// +build !linux - -package file - -import ( - "github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes" - "github.com/moby/buildkit/solver/pb" - "github.com/pkg/errors" - copy "github.com/tonistiigi/fsutil/copy" -) - -func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.User, error) { - if chopt == nil { - return nil, nil - } - return nil, errors.New("only implemented in linux") -} diff --git a/solver/llbsolver/ops/file.go b/solver/llbsolver/ops/file.go index db81201f1ac5..f17869381a90 100644 --- a/solver/llbsolver/ops/file.go +++ b/solver/llbsolver/ops/file.go @@ -167,7 +167,12 @@ func (f *fileOp) Exec(ctx context.Context, g session.Group, inputs []solver.Resu inpRefs = append(inpRefs, workerRef.ImmutableRef) } - fs := NewFileOpSolver(f.w, &file.Backend{}, f.refManager) + backend, err := file.NewFileOpBackend(getReadUserFn(f.w)) + if err != nil { + return nil, err + } + + fs := NewFileOpSolver(f.w, backend, f.refManager) outs, err := fs.Solve(ctx, inpRefs, f.op.Actions, g) if err != nil { return nil, err diff --git a/solver/llbsolver/file/user_linux.go b/solver/llbsolver/ops/user_linux.go similarity index 84% rename from solver/llbsolver/file/user_linux.go rename to solver/llbsolver/ops/user_linux.go index 1f17431f5c32..68daeceab0ba 100644 --- a/solver/llbsolver/file/user_linux.go +++ b/solver/llbsolver/ops/user_linux.go @@ -1,4 +1,4 @@ -package file +package ops import ( "os" @@ -6,14 +6,18 @@ import ( "github.com/containerd/continuity/fs" "github.com/moby/buildkit/snapshot" - "github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/worker" "github.com/opencontainers/runc/libcontainer/user" "github.com/pkg/errors" copy "github.com/tonistiigi/fsutil/copy" ) -func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.User, error) { +func getReadUserFn(worker worker.Worker) func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { + return readUser +} + +func readUser(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { if chopt == nil { return nil, nil } @@ -24,11 +28,8 @@ func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.User, error) if mu == nil { return nil, errors.Errorf("invalid missing user mount") } - mmu, ok := mu.(*Mount) - if !ok { - return nil, errors.Errorf("invalid mount type %T", mu) - } - lm := snapshot.LocalMounter(mmu.m) + + lm := snapshot.LocalMounter(mu) dir, err := lm.Mount() if err != nil { return nil, err @@ -78,11 +79,8 @@ func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.User, error) if mg == nil { return nil, errors.Errorf("invalid missing group mount") } - mmg, ok := mg.(*Mount) - if !ok { - return nil, errors.Errorf("invalid mount type %T", mg) - } - lm := snapshot.LocalMounter(mmg.m) + + lm := snapshot.LocalMounter(mg) dir, err := lm.Mount() if err != nil { return nil, err diff --git a/solver/llbsolver/ops/user_other.go b/solver/llbsolver/ops/user_other.go new file mode 100644 index 000000000000..f173ae38ca44 --- /dev/null +++ b/solver/llbsolver/ops/user_other.go @@ -0,0 +1,23 @@ +//go:build !linux && !windows +// +build !linux,!windows + +package ops + +import ( + "github.com/moby/buildkit/snapshot" + "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/worker" + "github.com/pkg/errors" + copy "github.com/tonistiigi/fsutil/copy" +) + +func getReadUserFn(worker worker.Worker) func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { + return readUser +} + +func readUser(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { + if chopt == nil { + return nil, nil + } + return nil, errors.New("only implemented in linux and windows") +} diff --git a/solver/llbsolver/ops/user_windows.go b/solver/llbsolver/ops/user_windows.go new file mode 100644 index 000000000000..553a2d210d0f --- /dev/null +++ b/solver/llbsolver/ops/user_windows.go @@ -0,0 +1,48 @@ +package ops + +import ( + "context" + + "github.com/docker/docker/pkg/idtools" + "github.com/moby/buildkit/snapshot" + "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/util/windows" + "github.com/moby/buildkit/worker" + "github.com/pkg/errors" + copy "github.com/tonistiigi/fsutil/copy" +) + +func getReadUserFn(worker worker.Worker) func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { + return func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) { + return readUser(chopt, mu, mg, worker) + } +} + +func readUser(chopt *pb.ChownOpt, mu, mg snapshot.Mountable, worker worker.Worker) (*copy.User, error) { + if chopt == nil { + return nil, nil + } + + if chopt.User != nil { + switch u := chopt.User.User.(type) { + case *pb.UserOpt_ByName: + if mu == nil { + return nil, errors.Errorf("invalid missing user mount") + } + + rootMounts, release, err := mu.Mount() + if err != nil { + return nil, err + } + defer release() + ident, err := windows.ResolveUsernameToSID(context.Background(), worker.Executor(), rootMounts, u.ByName.Name) + if err != nil { + return nil, err + } + return ©.User{SID: ident.SID}, nil + default: + return ©.User{SID: idtools.ContainerAdministratorSidString}, nil + } + } + return ©.User{SID: idtools.ContainerAdministratorSidString}, nil +}