@@ -27,32 +27,55 @@ import (
2727
2828 "github.com/containerd/containerd/v2/core/containers"
2929 "github.com/containerd/containerd/v2/core/mount"
30+ "github.com/containerd/containerd/v2/internal/userns"
31+
3032 "github.com/containerd/errdefs"
3133 "github.com/opencontainers/image-spec/identity"
34+ "github.com/opencontainers/runtime-spec/specs-go"
3235)
3336
3437// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
3538// filesystem to be used by a container with user namespaces
3639func WithRemappedSnapshot (id string , i Image , uid , gid uint32 ) NewContainerOpts {
37- return withRemappedSnapshotBase (id , i , uid , gid , false )
40+ uidmaps := []specs.LinuxIDMapping {{ContainerID : 0 , HostID : uid , Size : 65536 }}
41+ gidmaps := []specs.LinuxIDMapping {{ContainerID : 0 , HostID : gid , Size : 65536 }}
42+ return withRemappedSnapshotBase (id , i , uidmaps , gidmaps , false )
43+ }
44+
45+ // WithUserNSRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
46+ // filesystem to be used by a container with user namespaces
47+ func WithUserNSRemappedSnapshot (id string , i Image , uidmaps , gidmaps []specs.LinuxIDMapping ) NewContainerOpts {
48+ return withRemappedSnapshotBase (id , i , uidmaps , gidmaps , false )
3849}
3950
4051// WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only.
4152func WithRemappedSnapshotView (id string , i Image , uid , gid uint32 ) NewContainerOpts {
42- return withRemappedSnapshotBase (id , i , uid , gid , true )
53+ uidmaps := []specs.LinuxIDMapping {{ContainerID : 0 , HostID : uid , Size : 65536 }}
54+ gidmaps := []specs.LinuxIDMapping {{ContainerID : 0 , HostID : gid , Size : 65536 }}
55+ return withRemappedSnapshotBase (id , i , uidmaps , gidmaps , true )
4356}
4457
45- func withRemappedSnapshotBase (id string , i Image , uid , gid uint32 , readonly bool ) NewContainerOpts {
58+ // WithUserNSRemappedSnapshotView is similar to WithUserNSRemappedSnapshot but rootfs is mounted as read-only.
59+ func WithUserNSRemappedSnapshotView (id string , i Image , uidmaps , gidmaps []specs.LinuxIDMapping ) NewContainerOpts {
60+ return withRemappedSnapshotBase (id , i , uidmaps , gidmaps , true )
61+ }
62+
63+ func withRemappedSnapshotBase (id string , i Image , uidmaps , gidmaps []specs.LinuxIDMapping , readonly bool ) NewContainerOpts {
4664 return func (ctx context.Context , client * Client , c * containers.Container ) error {
4765 diffIDs , err := i .(* image ).i .RootFS (ctx , client .ContentStore (), client .platform )
4866 if err != nil {
4967 return err
5068 }
5169
52- var (
53- parent = identity .ChainID (diffIDs ).String ()
54- usernsID = fmt .Sprintf ("%s-%d-%d" , parent , uid , gid )
55- )
70+ rsn := remappedSnapshot {
71+ Parent : identity .ChainID (diffIDs ).String (),
72+ IDMap : userns.IDMap {UidMap : uidmaps , GidMap : gidmaps },
73+ }
74+ usernsID , err := rsn .ID ()
75+ if err != nil {
76+ return fmt .Errorf ("failed to remap snapshot: %w" , err )
77+ }
78+
5679 c .Snapshotter , err = client .resolveSnapshotterName (ctx , c .Snapshotter )
5780 if err != nil {
5881 return err
@@ -70,11 +93,11 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
7093 return err
7194 }
7295 }
73- mounts , err := snapshotter .Prepare (ctx , usernsID + "-remap" , parent )
96+ mounts , err := snapshotter .Prepare (ctx , usernsID + "-remap" , rsn . Parent )
7497 if err != nil {
7598 return err
7699 }
77- if err := remapRootFS (ctx , mounts , uid , gid ); err != nil {
100+ if err := remapRootFS (ctx , mounts , rsn . IDMap ); err != nil {
78101 snapshotter .Remove (ctx , usernsID )
79102 return err
80103 }
@@ -95,22 +118,30 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
95118 }
96119}
97120
98- func remapRootFS (ctx context.Context , mounts []mount.Mount , uid , gid uint32 ) error {
121+ func remapRootFS (ctx context.Context , mounts []mount.Mount , idMap userns. IDMap ) error {
99122 return mount .WithTempMount (ctx , mounts , func (root string ) error {
100- return filepath .Walk (root , incrementFS (root , uid , gid ))
123+ return filepath .Walk (root , chown (root , idMap ))
101124 })
102125}
103126
104- func incrementFS (root string , uidInc , gidInc uint32 ) filepath.WalkFunc {
127+ func chown (root string , idMap userns. IDMap ) filepath.WalkFunc {
105128 return func (path string , info os.FileInfo , err error ) error {
106129 if err != nil {
107130 return err
108131 }
109- var (
110- stat = info .Sys ().(* syscall.Stat_t )
111- u , g = int (stat .Uid + uidInc ), int (stat .Gid + gidInc )
112- )
132+ stat := info .Sys ().(* syscall.Stat_t )
133+ h , cerr := idMap .ToHost (userns.User {Uid : stat .Uid , Gid : stat .Gid })
134+ if cerr != nil {
135+ return cerr
136+ }
113137 // be sure the lchown the path as to not de-reference the symlink to a host file
114- return os .Lchown (path , u , g )
138+ if cerr = os .Lchown (path , int (h .Uid ), int (h .Gid )); cerr != nil {
139+ return cerr
140+ }
141+ // we must retain special permissions such as setuid, setgid and sticky bits
142+ if mode := info .Mode (); mode & os .ModeSymlink == 0 && mode & (os .ModeSetuid | os .ModeSetgid | os .ModeSticky ) != 0 {
143+ return os .Chmod (path , mode )
144+ }
145+ return nil
115146 }
116147}
0 commit comments