Skip to content

Commit d04cbc4

Browse files
committed
rootless: add autogenerated rootless config from runc spec
Since this is a runC-specific feature, this belongs here over in opencontainers/ocitools (which is for generic OCI runtimes). In addition, we don't create a new network namespace. This is because currently if you want to set up a veth bridge you need CAP_NET_ADMIN in both network namespaces' pinned user namespace to create the necessary interfaces in each network namespace. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 76aeaf8 commit d04cbc4

File tree

3 files changed

+85
-29
lines changed

3 files changed

+85
-29
lines changed

libcontainer/specconv/example.go

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package specconv
22

33
import (
4+
"os"
45
"runtime"
6+
"strings"
57

68
"github.com/opencontainers/runtime-spec/specs-go"
79
)
810

911
func sPtr(s string) *string { return &s }
1012

11-
// ExampleSpec returns an example spec file, with many options set so a user
12-
// can see what a standard spec file looks like.
13-
func ExampleSpec() *specs.Spec {
13+
// Example returns an example spec file, with many options set so a user can
14+
// see what a standard spec file looks like.
15+
func Example() *specs.Spec {
1416
return &specs.Spec{
1517
Version: specs.Version,
1618
Platform: specs.Platform{
@@ -158,3 +160,68 @@ func ExampleSpec() *specs.Spec {
158160
},
159161
}
160162
}
163+
164+
// ExampleRootless returns an example spec file that works with rootless
165+
// containers. It's essentially a modified version of the specfile from
166+
// Example().
167+
func ToRootless(spec *specs.Spec) {
168+
var namespaces []specs.LinuxNamespace
169+
170+
// Remove networkns from the spec.
171+
for _, ns := range spec.Linux.Namespaces {
172+
switch ns.Type {
173+
case specs.NetworkNamespace, specs.UserNamespace:
174+
// Do nothing.
175+
default:
176+
namespaces = append(namespaces, ns)
177+
}
178+
}
179+
// Add userns to the spec.
180+
namespaces = append(namespaces, specs.LinuxNamespace{
181+
Type: specs.UserNamespace,
182+
})
183+
spec.Linux.Namespaces = namespaces
184+
185+
// Add mappings for the current user.
186+
spec.Linux.UIDMappings = []specs.LinuxIDMapping{{
187+
HostID: uint32(os.Geteuid()),
188+
ContainerID: 0,
189+
Size: 1,
190+
}}
191+
spec.Linux.GIDMappings = []specs.LinuxIDMapping{{
192+
HostID: uint32(os.Getegid()),
193+
ContainerID: 0,
194+
Size: 1,
195+
}}
196+
197+
// Fix up mounts.
198+
var mounts []specs.Mount
199+
for _, mount := range spec.Mounts {
200+
// Ignore all mounts that are under /sys.
201+
if strings.HasPrefix(mount.Destination, "/sys") {
202+
continue
203+
}
204+
205+
// Remove all gid= and uid= mappings.
206+
var options []string
207+
for _, option := range mount.Options {
208+
if !strings.HasPrefix(option, "gid=") && !strings.HasPrefix(option, "uid=") {
209+
options = append(options, option)
210+
}
211+
}
212+
213+
mount.Options = options
214+
mounts = append(mounts, mount)
215+
}
216+
// Add the sysfs mount as an rbind.
217+
mounts = append(mounts, specs.Mount{
218+
Source: "/sys",
219+
Destination: "/sys",
220+
Type: "none",
221+
Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"},
222+
})
223+
spec.Mounts = mounts
224+
225+
// Remove cgroup settings.
226+
spec.Linux.Resources = nil
227+
}

libcontainer/specconv/spec_linux_test.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package specconv
44

55
import (
6-
"os"
76
"testing"
87

98
"github.com/opencontainers/runc/libcontainer/configs/validate"
@@ -53,8 +52,9 @@ func TestLinuxCgroupsPathNotSpecified(t *testing.T) {
5352
}
5453

5554
func TestSpecconvExampleValidate(t *testing.T) {
56-
spec := ExampleSpec()
55+
spec := Example()
5756
spec.Root.Path = "/"
57+
5858
opts := &CreateOpts{
5959
CgroupName: "ContainerID",
6060
UseSystemdCgroup: false,
@@ -97,29 +97,9 @@ func TestDupNamespaces(t *testing.T) {
9797
}
9898

9999
func TestRootlessSpecconvValidate(t *testing.T) {
100-
spec := &specs.Spec{
101-
Linux: specs.Linux{
102-
Namespaces: []specs.Namespace{
103-
{
104-
Type: specs.UserNamespace,
105-
},
106-
},
107-
UIDMappings: []specs.IDMapping{
108-
{
109-
HostID: uint32(os.Geteuid()),
110-
ContainerID: 0,
111-
Size: 1,
112-
},
113-
},
114-
GIDMappings: []specs.IDMapping{
115-
{
116-
HostID: uint32(os.Getegid()),
117-
ContainerID: 0,
118-
Size: 1,
119-
},
120-
},
121-
},
122-
}
100+
spec := Example()
101+
spec.Root.Path = "/"
102+
ToRootless(spec)
123103

124104
opts := &CreateOpts{
125105
CgroupName: "ContainerID",

spec.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,21 @@ container on your host.`,
6464
Value: "",
6565
Usage: "path to the root of the bundle directory",
6666
},
67+
cli.BoolFlag{
68+
Name: "rootless",
69+
Usage: "generate a configuration for a rootless container",
70+
},
6771
},
6872
Action: func(context *cli.Context) error {
6973
if err := checkArgs(context, 0, exactArgs); err != nil {
7074
return err
7175
}
72-
spec := specconv.ExampleSpec()
76+
spec := specconv.Example()
77+
78+
rootless := context.Bool("rootless")
79+
if rootless {
80+
specconv.ToRootless(spec)
81+
}
7382

7483
checkNoFile := func(name string) error {
7584
_, err := os.Stat(name)

0 commit comments

Comments
 (0)