Merge pull request #1862 from AkihiroSuda/decompose-rootless-pr

Disable rootless mode except RootlessCgMgr when executed as the root in userns (fix Docker-in-LXD regression)
This commit is contained in:
Mrunal Patel 2018-10-15 17:32:15 -07:00 committed by GitHub
commit a00bf01908
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 231 additions and 198 deletions

View File

@ -4,11 +4,14 @@ package main
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
@ -44,12 +47,8 @@ checkpointed.`,
return err
}
// XXX: Currently this is untested with rootless containers.
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc checkpoint requires root")
if os.Geteuid() != 0 || system.RunningInUserNS() {
logrus.Warn("runc checkpoint is untested with rootless containers")
}
container, err := getContainer(context)

View File

@ -65,7 +65,7 @@ type subsystem interface {
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup
Rootless bool
Rootless bool // ignore permission-related errors
Paths map[string]string
}
@ -174,7 +174,7 @@ func (m *Manager) Apply(pid int) (err error) {
m.Paths[sys.Name()] = p
if err := sys.Apply(d); err != nil {
// In the case of rootless, where an explicit cgroup path hasn't
// In the case of rootless (including euid=0 in userns), where an explicit cgroup path hasn't
// been set, we don't bail on error in case of permission problems.
// Cases where limits have been set (and we couldn't create our own
// cgroup) are handled by Set.
@ -236,6 +236,12 @@ func (m *Manager) Set(container *configs.Config) error {
for _, sys := range subsystems {
path := paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil {
if m.Rootless && sys.Name() == "devices" {
continue
}
// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// However, errors from other subsystems are not ignored.
// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
if path == "" {
// We never created a path for this cgroup, so we cannot set
// limits for it (though we have already tried at this point).

View File

@ -186,12 +186,19 @@ type Config struct {
// callers keyring in this case.
NoNewKeyring bool `json:"no_new_keyring"`
// Rootless specifies whether the container is a rootless container.
Rootless bool `json:"rootless"`
// IntelRdt specifies settings for Intel RDT/CAT group that the container is placed into
// to limit the resources (e.g., L3 cache) the container has available
IntelRdt *IntelRdt `json:"intel_rdt,omitempty"`
// RootlessEUID is set when the runc was launched with non-zero EUID.
// Note that RootlessEUID is set to false when launched with EUID=0 in userns.
// When RootlessEUID is set, runc creates a new userns for the container.
// (config.json needs to contain userns settings)
RootlessEUID bool `json:"rootless_euid,omitempty"`
// RootlessCgroups is set when unlikely to have the full access to cgroups.
// When RootlessCgroups is set, cgroups errors are ignored.
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
}
type Hooks struct {

View File

@ -2,23 +2,18 @@ package validate
import (
"fmt"
"os"
"reflect"
"strings"
"github.com/opencontainers/runc/libcontainer/configs"
)
var (
geteuid = os.Geteuid
getegid = os.Getegid
)
func (v *ConfigValidator) rootless(config *configs.Config) error {
if err := rootlessMappings(config); err != nil {
// rootlessEUID makes sure that the config can be applied when runc
// is being executed as a non-root user (euid != 0) in the current user namespace.
func (v *ConfigValidator) rootlessEUID(config *configs.Config) error {
if err := rootlessEUIDMappings(config); err != nil {
return err
}
if err := rootlessMount(config); err != nil {
if err := rootlessEUIDMount(config); err != nil {
return err
}
@ -38,46 +33,24 @@ func hasIDMapping(id int, mappings []configs.IDMap) bool {
return false
}
func rootlessMappings(config *configs.Config) error {
if euid := geteuid(); euid != 0 {
if !config.Namespaces.Contains(configs.NEWUSER) {
return fmt.Errorf("rootless containers require user namespaces")
}
if len(config.UidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
}
if len(config.GidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one GID mapping")
}
func rootlessEUIDMappings(config *configs.Config) error {
if !config.Namespaces.Contains(configs.NEWUSER) {
return fmt.Errorf("rootless container requires user namespaces")
}
return nil
}
// cgroup verifies that the user isn't trying to set any cgroup limits or paths.
func rootlessCgroup(config *configs.Config) error {
// Nothing set at all.
if config.Cgroups == nil || config.Cgroups.Resources == nil {
return nil
if len(config.UidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
}
// Used for comparing to the zero value.
left := reflect.ValueOf(*config.Cgroups.Resources)
right := reflect.Zero(left.Type())
// This is all we need to do, since specconv won't add cgroup options in
// rootless mode.
if !reflect.DeepEqual(left.Interface(), right.Interface()) {
return fmt.Errorf("cannot specify resource limits in rootless container")
if len(config.GidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one GID mapping")
}
return nil
}
// mount verifies that the user isn't trying to set up any mounts they don't have
// the rights to do. In addition, it makes sure that no mount has a `uid=` or
// `gid=` option that doesn't resolve to root.
func rootlessMount(config *configs.Config) error {
func rootlessEUIDMount(config *configs.Config) error {
// XXX: We could whitelist allowed devices at this point, but I'm not
// convinced that's a good idea. The kernel is the best arbiter of
// access control.

View File

@ -6,15 +6,11 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
func init() {
geteuid = func() int { return 1337 }
getegid = func() int { return 7331 }
}
func rootlessConfig() *configs.Config {
func rootlessEUIDConfig() *configs.Config {
return &configs.Config{
Rootfs: "/var",
Rootless: true,
Rootfs: "/var",
RootlessEUID: true,
RootlessCgroups: true,
Namespaces: configs.Namespaces(
[]configs.Namespace{
{Type: configs.NEWUSER},
@ -22,14 +18,14 @@ func rootlessConfig() *configs.Config {
),
UidMappings: []configs.IDMap{
{
HostID: geteuid(),
HostID: 1337,
ContainerID: 0,
Size: 1,
},
},
GidMappings: []configs.IDMap{
{
HostID: getegid(),
HostID: 7331,
ContainerID: 0,
Size: 1,
},
@ -37,51 +33,51 @@ func rootlessConfig() *configs.Config {
}
}
func TestValidateRootless(t *testing.T) {
func TestValidateRootlessEUID(t *testing.T) {
validator := New()
config := rootlessConfig()
config := rootlessEUIDConfig()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur: %+v", err)
}
}
/* rootlessMappings() */
/* rootlessEUIDMappings */
func TestValidateRootlessUserns(t *testing.T) {
func TestValidateRootlessEUIDUserns(t *testing.T) {
validator := New()
config := rootlessConfig()
config := rootlessEUIDConfig()
config.Namespaces = nil
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if user namespaces not set")
}
}
func TestValidateRootlessMappingUid(t *testing.T) {
func TestValidateRootlessEUIDMappingUid(t *testing.T) {
validator := New()
config := rootlessConfig()
config := rootlessEUIDConfig()
config.UidMappings = nil
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no uid mappings provided")
}
}
func TestValidateRootlessMappingGid(t *testing.T) {
func TestValidateNonZeroEUIDMappingGid(t *testing.T) {
validator := New()
config := rootlessConfig()
config := rootlessEUIDConfig()
config.GidMappings = nil
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no gid mappings provided")
}
}
/* rootlessMount() */
/* rootlessEUIDMount() */
func TestValidateRootlessMountUid(t *testing.T) {
config := rootlessConfig()
func TestValidateRootlessEUIDMountUid(t *testing.T) {
config := rootlessEUIDConfig()
validator := New()
config.Mounts = []*configs.Mount{
@ -119,8 +115,8 @@ func TestValidateRootlessMountUid(t *testing.T) {
}
}
func TestValidateRootlessMountGid(t *testing.T) {
config := rootlessConfig()
func TestValidateRootlessEUIDMountGid(t *testing.T) {
config := rootlessEUIDConfig()
validator := New()
config.Mounts = []*configs.Mount{

View File

@ -44,8 +44,8 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
if err := v.intelrdt(config); err != nil {
return err
}
if config.Rootless {
if err := v.rootless(config); err != nil {
if config.RootlessEUID {
if err := v.rootlessEUID(config); err != nil {
return err
}
}

View File

@ -59,7 +59,8 @@ type State struct {
// Platform specific fields below here
// Specifies if the container was started under the rootless mode.
// Specified if the container was started under the rootless mode.
// Set to true if BaseState.Config.RootlessEUID && BaseState.Config.RootlessCgroups
Rootless bool `json:"rootless"`
// Path to all the cgroups setup for a container. Key is cgroup subsystem name
@ -522,14 +523,15 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
return nil, err
}
return &setnsProcess{
cmd: cmd,
cgroupPaths: c.cgroupManager.GetPaths(),
intelRdtPath: state.IntelRdtPath,
childPipe: childPipe,
parentPipe: parentPipe,
config: c.newInitConfig(p),
process: p,
bootstrapData: data,
cmd: cmd,
cgroupPaths: c.cgroupManager.GetPaths(),
rootlessCgroups: c.config.RootlessCgroups,
intelRdtPath: state.IntelRdtPath,
childPipe: childPipe,
parentPipe: parentPipe,
config: c.newInitConfig(p),
process: p,
bootstrapData: data,
}, nil
}
@ -545,7 +547,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
PassedFilesCount: len(process.ExtraFiles),
ContainerId: c.ID(),
NoNewPrivileges: c.config.NoNewPrivileges,
Rootless: c.config.Rootless,
RootlessEUID: c.config.RootlessEUID,
RootlessCgroups: c.config.RootlessCgroups,
AppArmorProfile: c.config.AppArmorProfile,
ProcessLabel: c.config.ProcessLabel,
Rlimits: c.config.Rlimits,
@ -613,16 +616,16 @@ func (c *linuxContainer) Resume() error {
func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
// XXX(cyphar): This requires cgroups.
if c.config.Rootless {
return nil, fmt.Errorf("cannot get OOM notifications from rootless container")
if c.config.RootlessCgroups {
logrus.Warn("getting OOM notifications may fail if you don't have the full access to cgroups")
}
return notifyOnOOM(c.cgroupManager.GetPaths())
}
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
// XXX(cyphar): This requires cgroups.
if c.config.Rootless {
return nil, fmt.Errorf("cannot get memory pressure notifications from rootless container")
if c.config.RootlessCgroups {
logrus.Warn("getting memory pressure notifications may fail if you don't have the full access to cgroups")
}
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
}
@ -866,12 +869,11 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
c.m.Lock()
defer c.m.Unlock()
// Checkpoint is unlikely to work if os.Geteuid() != 0 || system.RunningInUserNS().
// (CLI prints a warning)
// TODO(avagin): Figure out how to make this work nicely. CRIU 2.0 has
// support for doing unprivileged dumps, but the setup of
// rootless containers might make this complicated.
if c.config.Rootless {
return fmt.Errorf("cannot checkpoint a rootless container")
}
// criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil {
@ -1105,11 +1107,10 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
var extraFiles []*os.File
// Restore is unlikely to work if os.Geteuid() != 0 || system.RunningInUserNS().
// (CLI prints a warning)
// TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have
// support for unprivileged restore at the moment.
if c.config.Rootless {
return fmt.Errorf("cannot restore a rootless container")
}
// criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil {
@ -1717,7 +1718,7 @@ func (c *linuxContainer) currentState() (*State, error) {
InitProcessStartTime: startTime,
Created: c.created,
},
Rootless: c.config.Rootless,
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
CgroupPaths: c.cgroupManager.GetPaths(),
IntelRdtPath: intelRdtPath,
NamespacePaths: make(map[configs.NamespaceType]string),
@ -1818,7 +1819,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
if !joinExistingUser {
// write uid mappings
if len(c.config.UidMappings) > 0 {
if c.config.Rootless && c.newuidmapPath != "" {
if c.config.RootlessEUID && c.newuidmapPath != "" {
r.AddData(&Bytemsg{
Type: UidmapPathAttr,
Value: []byte(c.newuidmapPath),
@ -1844,7 +1845,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
Type: GidmapAttr,
Value: b,
})
if c.config.Rootless && c.newgidmapPath != "" {
if c.config.RootlessEUID && c.newgidmapPath != "" {
r.AddData(&Bytemsg{
Type: GidmapPathAttr,
Value: []byte(c.newgidmapPath),
@ -1869,8 +1870,8 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
// write rootless
r.AddData(&Boolmsg{
Type: RootlessAttr,
Value: c.config.Rootless,
Type: RootlessEUIDAttr,
Value: c.config.RootlessEUID,
})
return bytes.NewReader(r.Serialize()), nil

View File

@ -77,7 +77,7 @@ func Cgroupfs(l *LinuxFactory) error {
// containers that use the native cgroups filesystem implementation to create
// and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is
// that RootlessCgroupfs can transparently handle permission errors that occur
// during rootless container setup (while still allowing cgroup usage if
// during rootless container (including euid=0 in userns) setup (while still allowing cgroup usage if
// they've been set up properly).
func RootlessCgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {

View File

@ -65,7 +65,8 @@ type initConfig struct {
CreateConsole bool `json:"create_console"`
ConsoleWidth uint16 `json:"console_width"`
ConsoleHeight uint16 `json:"console_height"`
Rootless bool `json:"rootless"`
RootlessEUID bool `json:"rootless_euid,omitempty"`
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
}
type initer interface {
@ -283,7 +284,7 @@ func setupUser(config *initConfig) error {
return fmt.Errorf("cannot set gid to unmapped user in user namespace")
}
if config.Rootless {
if config.RootlessEUID {
// We cannot set any additional groups in a rootless container and thus
// we bail if the user asked us to do so. TODO: We currently can't do
// this check earlier, but if libcontainer.Process.User was typesafe
@ -303,7 +304,7 @@ func setupUser(config *initConfig) error {
// There's nothing we can do about /etc/group entries, so we silently
// ignore setting groups here (since the user didn't explicitly ask us to
// set the group).
if !config.Rootless {
if !config.RootlessEUID {
suppGroups := append(execUser.Sgids, addGroups...)
if err := unix.Setgroups(suppGroups); err != nil {
return err

View File

@ -10,16 +10,16 @@ import (
// list of known message types we want to send to bootstrap program
// The number is randomly chosen to not conflict with known netlink types
const (
InitMsg uint16 = 62000
CloneFlagsAttr uint16 = 27281
NsPathsAttr uint16 = 27282
UidmapAttr uint16 = 27283
GidmapAttr uint16 = 27284
SetgroupAttr uint16 = 27285
OomScoreAdjAttr uint16 = 27286
RootlessAttr uint16 = 27287
UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289
InitMsg uint16 = 62000
CloneFlagsAttr uint16 = 27281
NsPathsAttr uint16 = 27282
UidmapAttr uint16 = 27283
GidmapAttr uint16 = 27284
SetgroupAttr uint16 = 27285
OomScoreAdjAttr uint16 = 27286
RootlessEUIDAttr uint16 = 27287
UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289
)
type Int32msg struct {

View File

@ -82,7 +82,7 @@ struct nlconfig_t {
uint8_t is_setgroup;
/* Rootless container settings. */
uint8_t is_rootless;
uint8_t is_rootless_euid; /* boolean */
char *uidmappath;
size_t uidmappath_len;
char *gidmappath;
@ -100,7 +100,7 @@ struct nlconfig_t {
#define GIDMAP_ATTR 27284
#define SETGROUP_ATTR 27285
#define OOM_SCORE_ADJ_ATTR 27286
#define ROOTLESS_ATTR 27287
#define ROOTLESS_EUID_ATTR 27287
#define UIDMAPPATH_ATTR 27288
#define GIDMAPPATH_ATTR 27289
@ -419,8 +419,8 @@ static void nl_parse(int fd, struct nlconfig_t *config)
case CLONE_FLAGS_ATTR:
config->cloneflags = readint32(current);
break;
case ROOTLESS_ATTR:
config->is_rootless = readint8(current);
case ROOTLESS_EUID_ATTR:
config->is_rootless_euid = readint8(current); /* boolean */
break;
case OOM_SCORE_ADJ_ATTR:
config->oom_score_adj = current;
@ -687,7 +687,7 @@ void nsexec(void)
* newuidmap/newgidmap shall be used.
*/
if (config.is_rootless && !config.is_setgroup)
if (config.is_rootless_euid && !config.is_setgroup)
update_setgroups(child, SETGROUPS_DENY);
/* Set up mappings. */
@ -953,7 +953,7 @@ void nsexec(void)
if (setgid(0) < 0)
bail("setgid failed");
if (!config.is_rootless && config.is_setgroup) {
if (!config.is_rootless_euid && config.is_setgroup) {
if (setgroups(0, NULL) < 0)
bail("setgroups failed");
}

View File

@ -46,15 +46,16 @@ type parentProcess interface {
}
type setnsProcess struct {
cmd *exec.Cmd
parentPipe *os.File
childPipe *os.File
cgroupPaths map[string]string
intelRdtPath string
config *initConfig
fds []string
process *Process
bootstrapData io.Reader
cmd *exec.Cmd
parentPipe *os.File
childPipe *os.File
cgroupPaths map[string]string
rootlessCgroups bool
intelRdtPath string
config *initConfig
fds []string
process *Process
bootstrapData io.Reader
}
func (p *setnsProcess) startTime() (uint64, error) {
@ -86,7 +87,7 @@ func (p *setnsProcess) start() (err error) {
return newSystemErrorWithCause(err, "executing setns process")
}
if len(p.cgroupPaths) > 0 {
if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil {
if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil && !p.rootlessCgroups {
return newSystemErrorWithCausef(err, "adding pid %d to cgroups", p.pid())
}
}

View File

@ -156,7 +156,7 @@ func Example() *specs.Spec {
}
// ToRootless converts the given spec file into one that should work with
// rootless containers, by removing incompatible options and adding others that
// rootless containers (euid != 0), by removing incompatible options and adding others that
// are needed.
func ToRootless(spec *specs.Spec) {
var namespaces []specs.LinuxNamespace

View File

@ -148,7 +148,8 @@ type CreateOpts struct {
NoPivotRoot bool
NoNewKeyring bool
Spec *specs.Spec
Rootless bool
RootlessEUID bool
RootlessCgroups bool
}
// CreateLibcontainerConfig creates a new libcontainer configuration from a
@ -176,13 +177,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
}
config := &configs.Config{
Rootfs: rootfsPath,
NoPivotRoot: opts.NoPivotRoot,
Readonlyfs: spec.Root.Readonly,
Hostname: spec.Hostname,
Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)),
NoNewKeyring: opts.NoNewKeyring,
Rootless: opts.Rootless,
Rootfs: rootfsPath,
NoPivotRoot: opts.NoPivotRoot,
Readonlyfs: spec.Root.Readonly,
Hostname: spec.Hostname,
Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)),
NoNewKeyring: opts.NoNewKeyring,
RootlessEUID: opts.RootlessEUID,
RootlessCgroups: opts.RootlessCgroups,
}
exists := false
@ -332,12 +334,9 @@ func createCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
c.Path = myCgroupPath
}
// In rootless containers, any attempt to make cgroup changes will fail.
// libcontainer will validate this and we shouldn't add any cgroup options
// the user didn't specify.
if !opts.Rootless {
c.Resources.AllowedDevices = allowedDevices
}
// In rootless containers, any attempt to make cgroup changes is likely to fail.
// libcontainer will validate this but ignores the error.
c.Resources.AllowedDevices = allowedDevices
if spec.Linux != nil {
r := spec.Linux.Resources
if r == nil {
@ -490,10 +489,8 @@ func createCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
}
}
}
if !opts.Rootless {
// append the default allowed devices to the end of the list
c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
}
// append the default allowed devices to the end of the list
c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
return c, nil
}

View File

@ -417,7 +417,7 @@ func TestDupNamespaces(t *testing.T) {
}
}
func TestRootlessSpecconvValidate(t *testing.T) {
func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported")
}
@ -430,7 +430,8 @@ func TestRootlessSpecconvValidate(t *testing.T) {
CgroupName: "ContainerID",
UseSystemdCgroup: false,
Spec: spec,
Rootless: true,
RootlessEUID: true,
RootlessCgroups: true,
}
config, err := CreateLibcontainerConfig(opts)

11
main.go
View File

@ -63,13 +63,8 @@ func main() {
app.Version = strings.Join(v, "\n")
root := "/run/runc"
rootless, err := isRootless(nil)
if err != nil {
fatal(err)
}
if rootless {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
if shouldHonorXDGRuntimeDir() {
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
root = runtimeDir + "/runc"
// According to the XDG specification, we need to set anything in
// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
@ -115,7 +110,7 @@ func main() {
cli.StringFlag{
Name: "rootless",
Value: "auto",
Usage: "enable rootless mode ('true', 'false', or 'auto')",
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
},
}
app.Commands = []cli.Command{

View File

@ -2,7 +2,10 @@
package main
import "github.com/urfave/cli"
import (
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var pauseCommand = cli.Command{
Name: "pause",
@ -18,6 +21,13 @@ Use runc list to identiy instances of containers and their current status.`,
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return err
}
if rootlessCg {
logrus.Warnf("runc pause may fail if you don't have the full access to cgroups")
}
container, err := getContainer(context)
if err != nil {
return err
@ -44,6 +54,13 @@ Use runc list to identiy instances of containers and their current status.`,
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return err
}
if rootlessCg {
logrus.Warn("runc resume may fail if you don't have the full access to cgroups")
}
container, err := getContainer(context)
if err != nil {
return err

8
ps.go
View File

@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@ -28,13 +29,12 @@ var psCommand = cli.Command{
if err := checkArgs(context, 1, minArgs); err != nil {
return err
}
// XXX: Currently not supported with rootless containers.
rootless, err := isRootless(context)
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc ps requires root")
if rootlessCg {
logrus.Warn("runc ps may fail if you don't have the full access to cgroups")
}
container, err := getContainer(context)

View File

@ -3,10 +3,11 @@
package main
import (
"fmt"
"os"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@ -96,12 +97,8 @@ using the runc checkpoint command.`,
return err
}
// XXX: Currently this is untested with rootless containers.
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc restore requires root")
if os.Geteuid() != 0 || system.RunningInUserNS() {
logrus.Warn("runc checkpoint is untested with rootless containers")
}
spec, err := setupSpec(context)

57
rootless_linux.go Normal file
View File

@ -0,0 +1,57 @@
// +build linux
package main
import (
"os"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/urfave/cli"
)
func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
if context != nil {
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
if err != nil {
return false, err
}
if b != nil {
return *b, nil
}
// nil b stands for "auto detect"
}
if context.GlobalBool("systemd-cgroup") {
return false, nil
}
if os.Geteuid() != 0 {
return true, nil
}
if !system.RunningInUserNS() {
// euid == 0 , in the initial ns (i.e. the real root)
return false, nil
}
// euid = 0, in a userns.
// As we are unaware of cgroups path, we can't determine whether we have the full
// access to the cgroups path.
// Either way, we can safely decide to use the rootless cgroups manager.
return true, nil
}
func shouldHonorXDGRuntimeDir() bool {
if os.Getenv("XDG_RUNTIME_DIR") == "" {
return false
}
if os.Geteuid() != 0 {
return true
}
if !system.RunningInUserNS() {
// euid == 0 , in the initial ns (i.e. the real root)
// in this case, we should use /run/runc and ignore
// $XDG_RUNTIME_DIR (e.g. /run/user/0) for backward
// compatibility.
return false
}
// euid = 0, in a userns.
u, ok := os.LookupEnv("USER")
return !ok || u != "root"
}

View File

@ -61,7 +61,11 @@ container on your host.
Alternatively, you can start a rootless container, which has the ability to run
without root privileges. For this to work, the specification file needs to be
adjusted accordingly. You can pass the parameter --rootless to this command to
generate a proper rootless spec file.`,
generate a proper rootless spec file.
Note that --rootless is not needed when you execute runc as the root in a user namespace
created by an unprivileged user.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bundle, b",

View File

@ -16,7 +16,6 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/intelrdt"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
@ -39,11 +38,11 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
// We default to cgroupfs, and can only use systemd if the system is a
// systemd box.
cgroupManager := libcontainer.Cgroupfs
rootless, err := isRootless(context)
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return nil, err
}
if rootless {
if rootlessCg {
cgroupManager = libcontainer.RootlessCgroupfs
}
if context.GlobalBool("systemd-cgroup") {
@ -226,27 +225,8 @@ func createPidFile(path string, process *libcontainer.Process) error {
return os.Rename(tmpName, path)
}
func isRootless(context *cli.Context) (bool, error) {
if context != nil {
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
if err != nil {
return false, err
}
if b != nil {
return *b, nil
}
// nil b stands for "auto detect"
}
// Even if os.Geteuid() == 0, it might still require rootless mode,
// especially when running within userns.
// So we use system.GetParentNSeuid() here.
//
// TODO(AkihiroSuda): how to support nested userns?
return system.GetParentNSeuid() != 0 || system.RunningInUserNS(), nil
}
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
rootless, err := isRootless(context)
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return nil, err
}
@ -256,7 +236,8 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcont
NoPivotRoot: context.Bool("no-pivot"),
NoNewKeyring: context.Bool("no-new-keyring"),
Spec: spec,
Rootless: rootless,
RootlessEUID: os.Geteuid() != 0,
RootlessCgroups: rootlessCg,
})
if err != nil {
return nil, err