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:
commit
a00bf01908
|
@ -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)
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
11
main.go
|
@ -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{
|
||||
|
|
19
pause.go
19
pause.go
|
@ -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
8
ps.go
|
@ -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)
|
||||
|
|
11
restore.go
11
restore.go
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
6
spec.go
6
spec.go
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue