Skip to content

Commit

Permalink
feat: implement userns=host and userns=keep-id
Browse files Browse the repository at this point in the history
Signed-off-by: Robert Günzler <[email protected]>
  • Loading branch information
robertgzr committed Sep 17, 2023
1 parent 60a6e76 commit 2b5d124
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cmd/nerdctl/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ func processContainerCreateOptions(cmd *cobra.Command) (opt types.ContainerCreat
if err != nil {
return
}
opt.UserNS, err = cmd.Flags().GetString("userns")
if err != nil {
return
}
// #endregion

// #region for security flags
Expand Down
1 change: 1 addition & 0 deletions cmd/nerdctl/container_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func setCreateFlags(cmd *cobra.Command) {
cmd.Flags().StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
cmd.Flags().String("umask", "", "Set the umask inside the container. Defaults to 0022")
cmd.Flags().StringSlice("group-add", []string{}, "Add additional groups to join")
cmd.Flags().String("userns", "host", `Set the user namespace mode for the container (auto|host|keep-id|nomap). Defaults to "host"`)

// #region security flags
cmd.Flags().StringArray("security-opt", []string{}, "Security options")
Expand Down
17 changes: 17 additions & 0 deletions cmd/nerdctl/container_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,20 @@ func TestRunWithTtyAndDetached(t *testing.T) {
withTtyContainer := base.InspectContainer(withTtyContainerName)
assert.Equal(base.T, 0, withTtyContainer.State.ExitCode)
}

func TestRunUserNSHost(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

uid := fmt.Sprintf("%d\n", 0)
base.Cmd("run", "--rm", "--userns=host", testutil.CommonImage, "id", "-u").AssertOutExactly(uid)
}


func TestRunUserNSKeepID(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

uid := fmt.Sprintf("%d\n", os.Getuid())
base.Cmd("run", "--rm", "--userns=keep-id", testutil.CommonImage, "id", "-u").AssertOutExactly(uid)
}
2 changes: 2 additions & 0 deletions pkg/api/types/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ type ContainerCreateOptions struct {
Umask string
// GroupAdd specifies additional groups to join
GroupAdd []string
// UserNS specifies the user namespace mode for the container
UserNS string
// #endregion

// #region for security flags
Expand Down
6 changes: 6 additions & 0 deletions pkg/cmd/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
}
opts = append(opts, umaskOpts...)

unsOpts, err := generateUserNSOpts(options.UserNS)
if err != nil {
return nil, nil, err
}
opts = append(opts, unsOpts...)

rtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime)
if err != nil {
return nil, nil, err
Expand Down
81 changes: 81 additions & 0 deletions pkg/cmd/container/run_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ package container
import (
"context"
"fmt"
"os/user"
"strconv"

"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
"github.com/containerd/nerdctl/pkg/rootlessutil"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/rootless-containers/rootlesskit/pkg/parent/idtools"
)

func generateUserOpts(user string) ([]oci.SpecOpts, error) {
Expand Down Expand Up @@ -68,3 +72,80 @@ func withAdditionalUmask(umask uint32) oci.SpecOpts {
return nil
}
}

func generateUserNSOpts(userns string) ([]oci.SpecOpts, error) {
switch userns {
case "host":
return []oci.SpecOpts{withResetUserNamespace()}, nil
case "keep-id":
min := func(a, b int) int {
if a < b {
return a
}
return b
}

uid := rootlessutil.ParentEUID()
gid := rootlessutil.ParentEGID()

Check failure on line 89 in pkg/cmd/container/run_user.go

View workflow job for this annotation

GitHub Actions / cross (1.20.x)

undefined: rootlessutil.ParentEGID

Check failure on line 89 in pkg/cmd/container/run_user.go

View workflow job for this annotation

GitHub Actions / test-integration-windows

undefined: rootlessutil.ParentEGID

u, err := user.LookupId(fmt.Sprintf("%d", uid))
if err != nil {
return nil, err
}
uids, gids, err := idtools.GetSubIDRanges(uid, u.Username)
if err != nil {
return nil, err
}

maxUID, maxGID := 0, 0
for _, u := range uids {
maxUID += u.Length
}
for _, g := range gids {
maxGID += g.Length
}

uidmap := []specs.LinuxIDMapping{{
ContainerID: uint32(uid),
HostID: 0,
Size: 1,
}}
if len(uids) > 0 {
uidmap = append(uidmap, specs.LinuxIDMapping{
ContainerID: 0,
HostID: 1,
Size: uint32(min(uid, maxUID)),
})
}

gidmap := []specs.LinuxIDMapping{{
ContainerID: uint32(gid),
HostID: 0,
Size: 1,
}}
if len(gids) > 0 {
gidmap = append(gidmap, specs.LinuxIDMapping{
ContainerID: 0,
HostID: 1,
Size: uint32(min(gid, maxGID)),
})
}
return []oci.SpecOpts{
oci.WithUserNamespace(uidmap, gidmap),
oci.WithUIDGID(uint32(uid), uint32(gid)),
}, nil
default:
return nil, fmt.Errorf("invalid UserNS Value:%s", userns)
}
}

func withResetUserNamespace() oci.SpecOpts {
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
for i, ns := range s.Linux.Namespaces {
if ns.Type == specs.UserNamespace {
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
}
}
return nil
}
}

0 comments on commit 2b5d124

Please sign in to comment.