Skip to content

Commit

Permalink
rootless: optional support for generating config with subuid map
Browse files Browse the repository at this point in the history
Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Jan 15, 2018
1 parent ab4a819 commit 298d6cd
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 16 deletions.
36 changes: 35 additions & 1 deletion libcontainer/specconv/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"strings"

"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -155,10 +156,20 @@ func Example() *specs.Spec {
}
}

// RootlessOpts is an optional spec for ToRootless
type RootlessOpts struct {
// Add sub{u,g}id to spec.Linux.{U,G}IDMappings.
// Requires newuidmap(1) and newgidmap(1) with suid bit.
MapSubUIDGID bool
}

// ExampleRootless returns an example spec file that works with rootless
// containers. It's essentially a modified version of the specfile from
// Example().
func ToRootless(spec *specs.Spec) {
func ToRootless(spec *specs.Spec, opts *RootlessOpts) error {
if opts == nil {
opts = &RootlessOpts{}
}
var namespaces []specs.LinuxNamespace

// Remove networkns from the spec.
Expand Down Expand Up @@ -187,6 +198,28 @@ func ToRootless(spec *specs.Spec) {
ContainerID: 0,
Size: 1,
}}
if opts.MapSubUIDGID {
subuid, err := user.CurrentSubUID()
if err != nil {
return err
}
spec.Linux.UIDMappings = append(spec.Linux.UIDMappings,
specs.LinuxIDMapping{
HostID: uint32(subuid.SubID),
ContainerID: 1,
Size: uint32(subuid.Count),
})
subgid, err := user.CurrentSubGID()
if err != nil {
return err
}
spec.Linux.GIDMappings = append(spec.Linux.GIDMappings,
specs.LinuxIDMapping{
HostID: uint32(subgid.SubID),
ContainerID: 1,
Size: uint32(subgid.Count),
})
}

// Fix up mounts.
var mounts []specs.Mount
Expand Down Expand Up @@ -218,4 +251,5 @@ func ToRootless(spec *specs.Spec) {

// Remove cgroup settings.
spec.Linux.Resources = nil
return nil
}
34 changes: 20 additions & 14 deletions libcontainer/specconv/spec_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,22 +424,28 @@ func TestRootlessSpecconvValidate(t *testing.T) {

spec := Example()
spec.Root.Path = "/"
ToRootless(spec)
optsCases := []*RootlessOpts{nil, {MapSubUIDGID: true}}
for _, opts := range optsCases {
err := ToRootless(spec, opts)
if err != nil {
t.Errorf("Couldn't convert a rootful spec to rootless: %v", err)
}

opts := &CreateOpts{
CgroupName: "ContainerID",
UseSystemdCgroup: false,
Spec: spec,
Rootless: true,
}
opts := &CreateOpts{
CgroupName: "ContainerID",
UseSystemdCgroup: false,
Spec: spec,
Rootless: true,
}

config, err := CreateLibcontainerConfig(opts)
if err != nil {
t.Errorf("Couldn't create libcontainer config: %v", err)
}
config, err := CreateLibcontainerConfig(opts)
if err != nil {
t.Errorf("Couldn't create libcontainer config: %v", err)
}

validator := validate.New()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
validator := validate.New()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
}
}
}
2 changes: 2 additions & 0 deletions libcontainer/user/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var (
// No matching entries found in file.
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
ErrNoGroupEntries = errors.New("no matching entries in group file")
ErrNoSubUIDEntries = errors.New("no matching entries in subuid file")
ErrNoSubGIDEntries = errors.New("no matching entries in subgid file")
)

func lookupUser(filter func(u User) bool) (User, error) {
Expand Down
32 changes: 32 additions & 0 deletions libcontainer/user/lookup_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,35 @@ func CurrentUser() (User, error) {
func CurrentGroup() (Group, error) {
return LookupGid(unix.Getgid())
}

func CurrentSubUID() (SubID, error) {
u, err := CurrentUser()
if err != nil {
return SubID{}, err
}
entries, err := ParseSubIDFileFilter("/etc/subuid",
func(entry SubID) bool { return entry.Name == u.Name })
if err != nil {
return SubID{}, err
}
if len(entries) == 0 {
return SubID{}, ErrNoSubUIDEntries
}
return entries[0], nil
}

func CurrentSubGID() (SubID, error) {
g, err := CurrentGroup()
if err != nil {
return SubID{}, err
}
entries, err := ParseSubIDFileFilter("/etc/subgid",
func(entry SubID) bool { return entry.Name == g.Name })
if err != nil {
return SubID{}, err
}
if len(entries) == 0 {
return SubID{}, ErrNoSubGIDEntries
}
return entries[0], nil
}
61 changes: 61 additions & 0 deletions libcontainer/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ type Group struct {
List []string
}

// SubID represents an entry in /etc/sub{u,g}id
type SubID struct {
Name string
SubID int
Count int
}

func parseLine(line string, v ...interface{}) {
if line == "" {
return
Expand Down Expand Up @@ -439,3 +446,57 @@ func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int
}
return GetAdditionalGroups(additionalGroups, group)
}

func ParseSubIDFile(path string) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubID(subid)
}

func ParseSubID(subid io.Reader) ([]SubID, error) {
return ParseSubIDFilter(subid, nil)
}

func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubIDFilter(subid, filter)
}

func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
if r == nil {
return nil, fmt.Errorf("nil source for subid-formatted data")
}

var (
s = bufio.NewScanner(r)
out = []SubID{}
)

for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}

line := strings.TrimSpace(s.Text())
if line == "" {
continue
}

// see: man 5 subuid
p := SubID{}
parseLine(line, &p.Name, &p.SubID, &p.Count)

if filter == nil || filter(p) {
out = append(out, p)
}
}

return out, nil
}
11 changes: 10 additions & 1 deletion spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ generate a proper rootless spec file.`,
Name: "rootless",
Usage: "generate a configuration for a rootless container",
},
cli.BoolFlag{
Name: "rootless-subuid",
Usage: "add subuid and subgid entries to the mapping configuration. requires rootless to be true and newuidmap/newgidmap binaries with suid bit.",
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 0, exactArgs); err != nil {
Expand All @@ -81,7 +85,12 @@ generate a proper rootless spec file.`,

rootless := context.Bool("rootless")
if rootless {
specconv.ToRootless(spec)
opts := &specconv.RootlessOpts{
MapSubUIDGID: context.Bool("rootless-subuid"),
}
if err := specconv.ToRootless(spec, opts); err != nil {
return err
}
}

checkNoFile := func(name string) error {
Expand Down

0 comments on commit 298d6cd

Please sign in to comment.