1package specconv 2 3import ( 4 "os" 5 "sort" 6 "strings" 7 8 "github.com/opencontainers/runc/libcontainer/system" 9 "github.com/opencontainers/runc/libcontainer/user" 10 "github.com/opencontainers/runtime-spec/specs-go" 11) 12 13// RootlessOpts is an optional spec for ToRootless 14type RootlessOpts struct { 15 // Add sub{u,g}id to spec.Linux.{U,G}IDMappings. 16 // Requires newuidmap(1) and newgidmap(1) with suid bit. 17 // Ignored when running in userns. 18 MapSubUIDGID bool 19} 20 21// Run-time context for ToRootless. 22type RootlessContext struct { 23 EUID uint32 24 EGID uint32 25 SubUIDs []user.SubID 26 SubGIDs []user.SubID 27 UIDMap []user.IDMap 28 GIDMap []user.IDMap 29 InUserNS bool 30} 31 32// ToRootless converts the given spec file into one that should work with 33// rootless containers, by removing incompatible options and adding others that 34// are needed. 35func ToRootless(spec *specs.Spec, opts *RootlessOpts) error { 36 var err error 37 ctx := RootlessContext{} 38 ctx.EUID = uint32(os.Geteuid()) 39 ctx.EGID = uint32(os.Getegid()) 40 ctx.SubUIDs, err = user.CurrentUserSubUIDs() 41 if err != nil && !os.IsNotExist(err) { 42 return err 43 } 44 ctx.SubGIDs, err = user.CurrentGroupSubGIDs() 45 if err != nil && !os.IsNotExist(err) { 46 return err 47 } 48 ctx.UIDMap, err = user.CurrentProcessUIDMap() 49 if err != nil && !os.IsNotExist(err) { 50 return err 51 } 52 uidMapExists := !os.IsNotExist(err) 53 ctx.GIDMap, err = user.CurrentProcessUIDMap() 54 if err != nil && !os.IsNotExist(err) { 55 return err 56 } 57 ctx.InUserNS = uidMapExists && system.UIDMapInUserNS(ctx.UIDMap) 58 return ToRootlessWithContext(ctx, spec, opts) 59} 60 61// ToRootlessWithContext converts the spec with the run-time context. 62// ctx can be internally modified for sorting. 63func ToRootlessWithContext(ctx RootlessContext, spec *specs.Spec, opts *RootlessOpts) error { 64 if opts == nil { 65 opts = &RootlessOpts{} 66 } 67 var namespaces []specs.LinuxNamespace 68 69 // Remove networkns from the spec. 70 for _, ns := range spec.Linux.Namespaces { 71 switch ns.Type { 72 case specs.NetworkNamespace, specs.UserNamespace: 73 // Do nothing. 74 default: 75 namespaces = append(namespaces, ns) 76 } 77 } 78 // Add userns to the spec. 79 namespaces = append(namespaces, specs.LinuxNamespace{ 80 Type: specs.UserNamespace, 81 }) 82 spec.Linux.Namespaces = namespaces 83 84 // Add mappings for the current user. 85 if ctx.InUserNS { 86 uNextContainerID := int64(0) 87 sort.Sort(idmapSorter(ctx.UIDMap)) 88 for _, uidmap := range ctx.UIDMap { 89 spec.Linux.UIDMappings = append(spec.Linux.UIDMappings, 90 specs.LinuxIDMapping{ 91 HostID: uint32(uidmap.ID), 92 ContainerID: uint32(uNextContainerID), 93 Size: uint32(uidmap.Count), 94 }) 95 uNextContainerID += uidmap.Count 96 } 97 gNextContainerID := int64(0) 98 sort.Sort(idmapSorter(ctx.GIDMap)) 99 for _, gidmap := range ctx.GIDMap { 100 spec.Linux.GIDMappings = append(spec.Linux.GIDMappings, 101 specs.LinuxIDMapping{ 102 HostID: uint32(gidmap.ID), 103 ContainerID: uint32(gNextContainerID), 104 Size: uint32(gidmap.Count), 105 }) 106 gNextContainerID += gidmap.Count 107 } 108 // opts.MapSubUIDGID is ignored in userns 109 } else { 110 spec.Linux.UIDMappings = []specs.LinuxIDMapping{{ 111 HostID: ctx.EUID, 112 ContainerID: 0, 113 Size: 1, 114 }} 115 spec.Linux.GIDMappings = []specs.LinuxIDMapping{{ 116 HostID: ctx.EGID, 117 ContainerID: 0, 118 Size: 1, 119 }} 120 if opts.MapSubUIDGID { 121 uNextContainerID := int64(1) 122 sort.Sort(subIDSorter(ctx.SubUIDs)) 123 for _, subuid := range ctx.SubUIDs { 124 spec.Linux.UIDMappings = append(spec.Linux.UIDMappings, 125 specs.LinuxIDMapping{ 126 HostID: uint32(subuid.SubID), 127 ContainerID: uint32(uNextContainerID), 128 Size: uint32(subuid.Count), 129 }) 130 uNextContainerID += subuid.Count 131 } 132 gNextContainerID := int64(1) 133 sort.Sort(subIDSorter(ctx.SubGIDs)) 134 for _, subgid := range ctx.SubGIDs { 135 spec.Linux.GIDMappings = append(spec.Linux.GIDMappings, 136 specs.LinuxIDMapping{ 137 HostID: uint32(subgid.SubID), 138 ContainerID: uint32(gNextContainerID), 139 Size: uint32(subgid.Count), 140 }) 141 gNextContainerID += subgid.Count 142 } 143 } 144 } 145 146 // Fix up mounts. 147 var mounts []specs.Mount 148 for _, mount := range spec.Mounts { 149 // Ignore all mounts that are under /sys. 150 if strings.HasPrefix(mount.Destination, "/sys") { 151 continue 152 } 153 154 // Remove all gid= and uid= mappings. 155 var options []string 156 for _, option := range mount.Options { 157 if !strings.HasPrefix(option, "gid=") && !strings.HasPrefix(option, "uid=") { 158 options = append(options, option) 159 } 160 } 161 162 mount.Options = options 163 mounts = append(mounts, mount) 164 } 165 // Add the sysfs mount as an rbind. 166 mounts = append(mounts, specs.Mount{ 167 Source: "/sys", 168 Destination: "/sys", 169 Type: "none", 170 Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"}, 171 }) 172 spec.Mounts = mounts 173 174 // Remove cgroup settings. 175 spec.Linux.Resources = nil 176 return nil 177} 178 179// subIDSorter is required for Go <= 1.7 180type subIDSorter []user.SubID 181 182func (x subIDSorter) Len() int { return len(x) } 183func (x subIDSorter) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 184func (x subIDSorter) Less(i, j int) bool { return x[i].SubID < x[j].SubID } 185 186type idmapSorter []user.IDMap 187 188func (x idmapSorter) Len() int { return len(x) } 189func (x idmapSorter) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 190func (x idmapSorter) Less(i, j int) bool { return x[i].ID < x[j].ID } 191