Skip to content

Commit 2b5d124

Browse files
committed
feat: implement userns=host and userns=keep-id
Signed-off-by: Robert Günzler <[email protected]>
1 parent 60a6e76 commit 2b5d124

File tree

6 files changed

+111
-0
lines changed

6 files changed

+111
-0
lines changed

cmd/nerdctl/container_create.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ func processContainerCreateOptions(cmd *cobra.Command) (opt types.ContainerCreat
236236
if err != nil {
237237
return
238238
}
239+
opt.UserNS, err = cmd.Flags().GetString("userns")
240+
if err != nil {
241+
return
242+
}
239243
// #endregion
240244

241245
// #region for security flags

cmd/nerdctl/container_run.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ func setCreateFlags(cmd *cobra.Command) {
168168
cmd.Flags().StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
169169
cmd.Flags().String("umask", "", "Set the umask inside the container. Defaults to 0022")
170170
cmd.Flags().StringSlice("group-add", []string{}, "Add additional groups to join")
171+
cmd.Flags().String("userns", "host", `Set the user namespace mode for the container (auto|host|keep-id|nomap). Defaults to "host"`)
171172

172173
// #region security flags
173174
cmd.Flags().StringArray("security-opt", []string{}, "Security options")

cmd/nerdctl/container_run_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,20 @@ func TestRunWithTtyAndDetached(t *testing.T) {
468468
withTtyContainer := base.InspectContainer(withTtyContainerName)
469469
assert.Equal(base.T, 0, withTtyContainer.State.ExitCode)
470470
}
471+
472+
func TestRunUserNSHost(t *testing.T) {
473+
t.Parallel()
474+
base := testutil.NewBase(t)
475+
476+
uid := fmt.Sprintf("%d\n", 0)
477+
base.Cmd("run", "--rm", "--userns=host", testutil.CommonImage, "id", "-u").AssertOutExactly(uid)
478+
}
479+
480+
481+
func TestRunUserNSKeepID(t *testing.T) {
482+
t.Parallel()
483+
base := testutil.NewBase(t)
484+
485+
uid := fmt.Sprintf("%d\n", os.Getuid())
486+
base.Cmd("run", "--rm", "--userns=keep-id", testutil.CommonImage, "id", "-u").AssertOutExactly(uid)
487+
}

pkg/api/types/container_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ type ContainerCreateOptions struct {
158158
Umask string
159159
// GroupAdd specifies additional groups to join
160160
GroupAdd []string
161+
// UserNS specifies the user namespace mode for the container
162+
UserNS string
161163
// #endregion
162164

163165
// #region for security flags

pkg/cmd/container/create.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
225225
}
226226
opts = append(opts, umaskOpts...)
227227

228+
unsOpts, err := generateUserNSOpts(options.UserNS)
229+
if err != nil {
230+
return nil, nil, err
231+
}
232+
opts = append(opts, unsOpts...)
233+
228234
rtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime)
229235
if err != nil {
230236
return nil, nil, err

pkg/cmd/container/run_user.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ package container
1919
import (
2020
"context"
2121
"fmt"
22+
"os/user"
2223
"strconv"
2324

2425
"github.com/containerd/containerd/containers"
2526
"github.com/containerd/containerd/oci"
27+
"github.com/containerd/nerdctl/pkg/rootlessutil"
28+
"github.com/opencontainers/runtime-spec/specs-go"
29+
"github.com/rootless-containers/rootlesskit/pkg/parent/idtools"
2630
)
2731

2832
func generateUserOpts(user string) ([]oci.SpecOpts, error) {
@@ -68,3 +72,80 @@ func withAdditionalUmask(umask uint32) oci.SpecOpts {
6872
return nil
6973
}
7074
}
75+
76+
func generateUserNSOpts(userns string) ([]oci.SpecOpts, error) {
77+
switch userns {
78+
case "host":
79+
return []oci.SpecOpts{withResetUserNamespace()}, nil
80+
case "keep-id":
81+
min := func(a, b int) int {
82+
if a < b {
83+
return a
84+
}
85+
return b
86+
}
87+
88+
uid := rootlessutil.ParentEUID()
89+
gid := rootlessutil.ParentEGID()
90+
91+
u, err := user.LookupId(fmt.Sprintf("%d", uid))
92+
if err != nil {
93+
return nil, err
94+
}
95+
uids, gids, err := idtools.GetSubIDRanges(uid, u.Username)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
maxUID, maxGID := 0, 0
101+
for _, u := range uids {
102+
maxUID += u.Length
103+
}
104+
for _, g := range gids {
105+
maxGID += g.Length
106+
}
107+
108+
uidmap := []specs.LinuxIDMapping{{
109+
ContainerID: uint32(uid),
110+
HostID: 0,
111+
Size: 1,
112+
}}
113+
if len(uids) > 0 {
114+
uidmap = append(uidmap, specs.LinuxIDMapping{
115+
ContainerID: 0,
116+
HostID: 1,
117+
Size: uint32(min(uid, maxUID)),
118+
})
119+
}
120+
121+
gidmap := []specs.LinuxIDMapping{{
122+
ContainerID: uint32(gid),
123+
HostID: 0,
124+
Size: 1,
125+
}}
126+
if len(gids) > 0 {
127+
gidmap = append(gidmap, specs.LinuxIDMapping{
128+
ContainerID: 0,
129+
HostID: 1,
130+
Size: uint32(min(gid, maxGID)),
131+
})
132+
}
133+
return []oci.SpecOpts{
134+
oci.WithUserNamespace(uidmap, gidmap),
135+
oci.WithUIDGID(uint32(uid), uint32(gid)),
136+
}, nil
137+
default:
138+
return nil, fmt.Errorf("invalid UserNS Value:%s", userns)
139+
}
140+
}
141+
142+
func withResetUserNamespace() oci.SpecOpts {
143+
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
144+
for i, ns := range s.Linux.Namespaces {
145+
if ns.Type == specs.UserNamespace {
146+
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
147+
}
148+
}
149+
return nil
150+
}
151+
}

0 commit comments

Comments
 (0)