package specconv import ( "os" "sort" "strings" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/user" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) // ToRootless converts spec to be compatible with "rootless" runc. // * Adds userns (Note: since we are already in userns, ideally we should not need to do this. runc-side issue is tracked at https://github.com/opencontainers/runc/issues/1837) // * Fix up mount flags (same as above) // * Replace /sys with bind-mount (FIXME: we don't need to do this if netns is unshared) func ToRootless(spec *specs.Spec) error { if !system.RunningInUserNS() { return errors.New("needs to be in user namespace") } uidMap, err := user.CurrentProcessUIDMap() if err != nil && !os.IsNotExist(err) { return err } gidMap, err := user.CurrentProcessUIDMap() if err != nil && !os.IsNotExist(err) { return err } return toRootless(spec, uidMap, gidMap) } // toRootless was forked from github.com/opencontainers/runc/libcontainer/specconv func toRootless(spec *specs.Spec, uidMap, gidMap []user.IDMap) error { if err := configureUserNS(spec, uidMap, gidMap); err != nil { return err } if err := configureMounts(spec); err != nil { return err } // Remove cgroup settings. spec.Linux.Resources = nil spec.Linux.CgroupsPath = "" return nil } // configureUserNS add suserns and the current ID map to the spec. // Since we are already in userns, ideally we should not need to add userns. // However, currently rootless runc always requires userns to be added. // https://github.com/opencontainers/runc/issues/1837 func configureUserNS(spec *specs.Spec, uidMap, gidMap []user.IDMap) error { spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{ Type: specs.UserNamespace, }) sort.Slice(uidMap, func(i, j int) bool { return uidMap[i].ID < uidMap[j].ID }) uNextContainerID := int64(0) for _, u := range uidMap { spec.Linux.UIDMappings = append(spec.Linux.UIDMappings, specs.LinuxIDMapping{ HostID: uint32(u.ID), ContainerID: uint32(uNextContainerID), Size: uint32(u.Count), }) uNextContainerID += int64(u.Count) } sort.Slice(gidMap, func(i, j int) bool { return gidMap[i].ID < gidMap[j].ID }) gNextContainerID := int64(0) for _, g := range gidMap { spec.Linux.GIDMappings = append(spec.Linux.GIDMappings, specs.LinuxIDMapping{ HostID: uint32(g.ID), ContainerID: uint32(gNextContainerID), Size: uint32(g.Count), }) gNextContainerID += int64(g.Count) } return nil } func configureMounts(spec *specs.Spec) error { var mounts []specs.Mount for _, mount := range spec.Mounts { // Ignore all mounts that are under /sys, because we add /sys later. if strings.HasPrefix(mount.Destination, "/sys") { continue } // Remove all gid= and uid= mappings. // Since we are already in userns, ideally we should not need to do this. // https://github.com/opencontainers/runc/issues/1837 var options []string for _, option := range mount.Options { if !strings.HasPrefix(option, "gid=") && !strings.HasPrefix(option, "uid=") { options = append(options, option) } } mount.Options = options mounts = append(mounts, mount) } // Add the sysfs mount as an rbind, because we can't mount /sys unless we have netns. // TODO: keep original /sys mount when we have netns. mounts = append(mounts, specs.Mount{ Source: "/sys", Destination: "/sys", Type: "none", Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"}, }) spec.Mounts = mounts return nil }