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