mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
Disable rootless mode except RootlessCgMgr when executed as the root in userns
This PR decomposes `libcontainer/configs.Config.Rootless bool` into `RootlessEUID bool` and `RootlessCgroups bool`, so as to make "runc-in-userns" to be more compatible with "rootful" runc. `RootlessEUID` denotes that runc is being executed as a non-root user (euid != 0) in the current user namespace. `RootlessEUID` is almost identical to the former `Rootless` except cgroups stuff. `RootlessCgroups` denotes that runc is unlikely to have the full access to cgroups. `RootlessCgroups` is set to false if runc is executed as the root (euid == 0) in the initial namespace. Otherwise `RootlessCgroups` is set to true. (Hint: if `RootlessEUID` is true, `RootlessCgroups` becomes true as well) When runc is executed as the root (euid == 0) in an user namespace (e.g. by Docker-in-LXD, Podman, Usernetes), `RootlessEUID` is set to false but `RootlessCgroups` is set to true. So, "runc-in-userns" behaves almost same as "rootful" runc except that cgroups errors are ignored. This PR does not have any impact on CLI flags and `state.json`. Note about CLI: * Now `runc --rootless=(auto|true|false)` CLI flag is only used for setting `RootlessCgroups`. * Now `runc spec --rootless` is only required when `RootlessEUID` is set to true. For runc-in-userns, `runc spec` without `--rootless` should work, when sufficient numbers of UID/GID are mapped. Note about `$XDG_RUNTIME_DIR` (e.g. `/run/user/1000`): * `$XDG_RUNTIME_DIR` is ignored if runc is being executed as the root (euid == 0) in the initial namespace, for backward compatibility. (`/run/runc` is used) * If runc is executed as the root (euid == 0) in an user namespace, `$XDG_RUNTIME_DIR` is honored if `$USER != "" && $USER != "root"`. This allows unprivileged users to allow execute runc as the root in userns, without mounting writable `/run/runc`. Note about `state.json`: * `rootless` is set to true when `RootlessEUID == true && RootlessCgroups == true`. Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
+5
-6
@@ -4,11 +4,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -44,12 +47,8 @@ checkpointed.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// XXX: Currently this is untested with rootless containers.
|
||||||
rootless, err := isRootless(context)
|
if os.Geteuid() != 0 || system.RunningInUserNS() {
|
||||||
if err != nil {
|
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rootless {
|
|
||||||
return fmt.Errorf("runc checkpoint requires root")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(context)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ type subsystem interface {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
Cgroups *configs.Cgroup
|
Cgroups *configs.Cgroup
|
||||||
Rootless bool
|
Rootless bool // ignore permission-related errors
|
||||||
Paths map[string]string
|
Paths map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ func (m *Manager) Apply(pid int) (err error) {
|
|||||||
m.Paths[sys.Name()] = p
|
m.Paths[sys.Name()] = p
|
||||||
|
|
||||||
if err := sys.Apply(d); err != nil {
|
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.
|
// 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
|
// Cases where limits have been set (and we couldn't create our own
|
||||||
// cgroup) are handled by Set.
|
// cgroup) are handled by Set.
|
||||||
@@ -236,6 +236,12 @@ func (m *Manager) Set(container *configs.Config) error {
|
|||||||
for _, sys := range subsystems {
|
for _, sys := range subsystems {
|
||||||
path := paths[sys.Name()]
|
path := paths[sys.Name()]
|
||||||
if err := sys.Set(path, container.Cgroups); err != nil {
|
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 == "" {
|
if path == "" {
|
||||||
// We never created a path for this cgroup, so we cannot set
|
// We never created a path for this cgroup, so we cannot set
|
||||||
// limits for it (though we have already tried at this point).
|
// limits for it (though we have already tried at this point).
|
||||||
|
|||||||
@@ -186,12 +186,19 @@ type Config struct {
|
|||||||
// callers keyring in this case.
|
// callers keyring in this case.
|
||||||
NoNewKeyring bool `json:"no_new_keyring"`
|
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
|
// 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
|
// to limit the resources (e.g., L3 cache) the container has available
|
||||||
IntelRdt *IntelRdt `json:"intel_rdt,omitempty"`
|
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 {
|
type Hooks struct {
|
||||||
|
|||||||
@@ -2,23 +2,18 @@ package validate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// rootlessEUID makes sure that the config can be applied when runc
|
||||||
geteuid = os.Geteuid
|
// is being executed as a non-root user (euid != 0) in the current user namespace.
|
||||||
getegid = os.Getegid
|
func (v *ConfigValidator) rootlessEUID(config *configs.Config) error {
|
||||||
)
|
if err := rootlessEUIDMappings(config); err != nil {
|
||||||
|
|
||||||
func (v *ConfigValidator) rootless(config *configs.Config) error {
|
|
||||||
if err := rootlessMappings(config); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := rootlessMount(config); err != nil {
|
if err := rootlessEUIDMount(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,46 +33,24 @@ func hasIDMapping(id int, mappings []configs.IDMap) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootlessMappings(config *configs.Config) error {
|
func rootlessEUIDMappings(config *configs.Config) error {
|
||||||
if euid := geteuid(); euid != 0 {
|
if !config.Namespaces.Contains(configs.NEWUSER) {
|
||||||
if !config.Namespaces.Contains(configs.NEWUSER) {
|
return fmt.Errorf("rootless container requires user namespaces")
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if len(config.UidMappings) == 0 {
|
||||||
}
|
return fmt.Errorf("rootless containers requires at least one UID mapping")
|
||||||
|
|
||||||
// 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.GidMappings) == 0 {
|
||||||
// Used for comparing to the zero value.
|
return fmt.Errorf("rootless containers requires at least one GID mapping")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mount verifies that the user isn't trying to set up any mounts they don't have
|
// 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
|
// the rights to do. In addition, it makes sure that no mount has a `uid=` or
|
||||||
// `gid=` option that doesn't resolve to root.
|
// `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
|
// 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
|
// convinced that's a good idea. The kernel is the best arbiter of
|
||||||
// access control.
|
// access control.
|
||||||
|
|||||||
@@ -6,15 +6,11 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func rootlessEUIDConfig() *configs.Config {
|
||||||
geteuid = func() int { return 1337 }
|
|
||||||
getegid = func() int { return 7331 }
|
|
||||||
}
|
|
||||||
|
|
||||||
func rootlessConfig() *configs.Config {
|
|
||||||
return &configs.Config{
|
return &configs.Config{
|
||||||
Rootfs: "/var",
|
Rootfs: "/var",
|
||||||
Rootless: true,
|
RootlessEUID: true,
|
||||||
|
RootlessCgroups: true,
|
||||||
Namespaces: configs.Namespaces(
|
Namespaces: configs.Namespaces(
|
||||||
[]configs.Namespace{
|
[]configs.Namespace{
|
||||||
{Type: configs.NEWUSER},
|
{Type: configs.NEWUSER},
|
||||||
@@ -22,14 +18,14 @@ func rootlessConfig() *configs.Config {
|
|||||||
),
|
),
|
||||||
UidMappings: []configs.IDMap{
|
UidMappings: []configs.IDMap{
|
||||||
{
|
{
|
||||||
HostID: geteuid(),
|
HostID: 1337,
|
||||||
ContainerID: 0,
|
ContainerID: 0,
|
||||||
Size: 1,
|
Size: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
GidMappings: []configs.IDMap{
|
GidMappings: []configs.IDMap{
|
||||||
{
|
{
|
||||||
HostID: getegid(),
|
HostID: 7331,
|
||||||
ContainerID: 0,
|
ContainerID: 0,
|
||||||
Size: 1,
|
Size: 1,
|
||||||
},
|
},
|
||||||
@@ -37,51 +33,51 @@ func rootlessConfig() *configs.Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootless(t *testing.T) {
|
func TestValidateRootlessEUID(t *testing.T) {
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
if err := validator.Validate(config); err != nil {
|
if err := validator.Validate(config); err != nil {
|
||||||
t.Errorf("Expected error to not occur: %+v", err)
|
t.Errorf("Expected error to not occur: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rootlessMappings() */
|
/* rootlessEUIDMappings */
|
||||||
|
|
||||||
func TestValidateRootlessUserns(t *testing.T) {
|
func TestValidateRootlessEUIDUserns(t *testing.T) {
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
config.Namespaces = nil
|
config.Namespaces = nil
|
||||||
if err := validator.Validate(config); err == nil {
|
if err := validator.Validate(config); err == nil {
|
||||||
t.Errorf("Expected error to occur if user namespaces not set")
|
t.Errorf("Expected error to occur if user namespaces not set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootlessMappingUid(t *testing.T) {
|
func TestValidateRootlessEUIDMappingUid(t *testing.T) {
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
config.UidMappings = nil
|
config.UidMappings = nil
|
||||||
if err := validator.Validate(config); err == nil {
|
if err := validator.Validate(config); err == nil {
|
||||||
t.Errorf("Expected error to occur if no uid mappings provided")
|
t.Errorf("Expected error to occur if no uid mappings provided")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootlessMappingGid(t *testing.T) {
|
func TestValidateNonZeroEUIDMappingGid(t *testing.T) {
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
config.GidMappings = nil
|
config.GidMappings = nil
|
||||||
if err := validator.Validate(config); err == nil {
|
if err := validator.Validate(config); err == nil {
|
||||||
t.Errorf("Expected error to occur if no gid mappings provided")
|
t.Errorf("Expected error to occur if no gid mappings provided")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rootlessMount() */
|
/* rootlessEUIDMount() */
|
||||||
|
|
||||||
func TestValidateRootlessMountUid(t *testing.T) {
|
func TestValidateRootlessEUIDMountUid(t *testing.T) {
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config.Mounts = []*configs.Mount{
|
config.Mounts = []*configs.Mount{
|
||||||
@@ -119,8 +115,8 @@ func TestValidateRootlessMountUid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootlessMountGid(t *testing.T) {
|
func TestValidateRootlessEUIDMountGid(t *testing.T) {
|
||||||
config := rootlessConfig()
|
config := rootlessEUIDConfig()
|
||||||
validator := New()
|
validator := New()
|
||||||
|
|
||||||
config.Mounts = []*configs.Mount{
|
config.Mounts = []*configs.Mount{
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
|
|||||||
if err := v.intelrdt(config); err != nil {
|
if err := v.intelrdt(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if config.Rootless {
|
if config.RootlessEUID {
|
||||||
if err := v.rootless(config); err != nil {
|
if err := v.rootlessEUID(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ type State struct {
|
|||||||
|
|
||||||
// Platform specific fields below here
|
// 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"`
|
Rootless bool `json:"rootless"`
|
||||||
|
|
||||||
// Path to all the cgroups setup for a container. Key is cgroup subsystem name
|
// 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 nil, err
|
||||||
}
|
}
|
||||||
return &setnsProcess{
|
return &setnsProcess{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
cgroupPaths: c.cgroupManager.GetPaths(),
|
cgroupPaths: c.cgroupManager.GetPaths(),
|
||||||
intelRdtPath: state.IntelRdtPath,
|
rootlessCgroups: c.config.RootlessCgroups,
|
||||||
childPipe: childPipe,
|
intelRdtPath: state.IntelRdtPath,
|
||||||
parentPipe: parentPipe,
|
childPipe: childPipe,
|
||||||
config: c.newInitConfig(p),
|
parentPipe: parentPipe,
|
||||||
process: p,
|
config: c.newInitConfig(p),
|
||||||
bootstrapData: data,
|
process: p,
|
||||||
|
bootstrapData: data,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +547,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
|||||||
PassedFilesCount: len(process.ExtraFiles),
|
PassedFilesCount: len(process.ExtraFiles),
|
||||||
ContainerId: c.ID(),
|
ContainerId: c.ID(),
|
||||||
NoNewPrivileges: c.config.NoNewPrivileges,
|
NoNewPrivileges: c.config.NoNewPrivileges,
|
||||||
Rootless: c.config.Rootless,
|
RootlessEUID: c.config.RootlessEUID,
|
||||||
|
RootlessCgroups: c.config.RootlessCgroups,
|
||||||
AppArmorProfile: c.config.AppArmorProfile,
|
AppArmorProfile: c.config.AppArmorProfile,
|
||||||
ProcessLabel: c.config.ProcessLabel,
|
ProcessLabel: c.config.ProcessLabel,
|
||||||
Rlimits: c.config.Rlimits,
|
Rlimits: c.config.Rlimits,
|
||||||
@@ -613,16 +616,16 @@ func (c *linuxContainer) Resume() error {
|
|||||||
|
|
||||||
func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
|
func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
|
||||||
// XXX(cyphar): This requires cgroups.
|
// XXX(cyphar): This requires cgroups.
|
||||||
if c.config.Rootless {
|
if c.config.RootlessCgroups {
|
||||||
return nil, fmt.Errorf("cannot get OOM notifications from rootless container")
|
logrus.Warn("getting OOM notifications may fail if you don't have the full access to cgroups")
|
||||||
}
|
}
|
||||||
return notifyOnOOM(c.cgroupManager.GetPaths())
|
return notifyOnOOM(c.cgroupManager.GetPaths())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
|
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
|
||||||
// XXX(cyphar): This requires cgroups.
|
// XXX(cyphar): This requires cgroups.
|
||||||
if c.config.Rootless {
|
if c.config.RootlessCgroups {
|
||||||
return nil, fmt.Errorf("cannot get memory pressure notifications from rootless container")
|
logrus.Warn("getting memory pressure notifications may fail if you don't have the full access to cgroups")
|
||||||
}
|
}
|
||||||
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
|
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
|
||||||
}
|
}
|
||||||
@@ -866,12 +869,11 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
|
|||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
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
|
// TODO(avagin): Figure out how to make this work nicely. CRIU 2.0 has
|
||||||
// support for doing unprivileged dumps, but the setup of
|
// support for doing unprivileged dumps, but the setup of
|
||||||
// rootless containers might make this complicated.
|
// rootless containers might make this complicated.
|
||||||
if c.config.Rootless {
|
|
||||||
return fmt.Errorf("cannot checkpoint a rootless container")
|
|
||||||
}
|
|
||||||
|
|
||||||
// criu 1.5.2 => 10502
|
// criu 1.5.2 => 10502
|
||||||
if err := c.checkCriuVersion(10502); err != nil {
|
if err := c.checkCriuVersion(10502); err != nil {
|
||||||
@@ -1105,11 +1107,10 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
|
|||||||
|
|
||||||
var extraFiles []*os.File
|
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
|
// TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have
|
||||||
// support for unprivileged restore at the moment.
|
// support for unprivileged restore at the moment.
|
||||||
if c.config.Rootless {
|
|
||||||
return fmt.Errorf("cannot restore a rootless container")
|
|
||||||
}
|
|
||||||
|
|
||||||
// criu 1.5.2 => 10502
|
// criu 1.5.2 => 10502
|
||||||
if err := c.checkCriuVersion(10502); err != nil {
|
if err := c.checkCriuVersion(10502); err != nil {
|
||||||
@@ -1717,7 +1718,7 @@ func (c *linuxContainer) currentState() (*State, error) {
|
|||||||
InitProcessStartTime: startTime,
|
InitProcessStartTime: startTime,
|
||||||
Created: c.created,
|
Created: c.created,
|
||||||
},
|
},
|
||||||
Rootless: c.config.Rootless,
|
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
|
||||||
CgroupPaths: c.cgroupManager.GetPaths(),
|
CgroupPaths: c.cgroupManager.GetPaths(),
|
||||||
IntelRdtPath: intelRdtPath,
|
IntelRdtPath: intelRdtPath,
|
||||||
NamespacePaths: make(map[configs.NamespaceType]string),
|
NamespacePaths: make(map[configs.NamespaceType]string),
|
||||||
@@ -1818,7 +1819,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||||||
if !joinExistingUser {
|
if !joinExistingUser {
|
||||||
// write uid mappings
|
// write uid mappings
|
||||||
if len(c.config.UidMappings) > 0 {
|
if len(c.config.UidMappings) > 0 {
|
||||||
if c.config.Rootless && c.newuidmapPath != "" {
|
if c.config.RootlessEUID && c.newuidmapPath != "" {
|
||||||
r.AddData(&Bytemsg{
|
r.AddData(&Bytemsg{
|
||||||
Type: UidmapPathAttr,
|
Type: UidmapPathAttr,
|
||||||
Value: []byte(c.newuidmapPath),
|
Value: []byte(c.newuidmapPath),
|
||||||
@@ -1844,7 +1845,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||||||
Type: GidmapAttr,
|
Type: GidmapAttr,
|
||||||
Value: b,
|
Value: b,
|
||||||
})
|
})
|
||||||
if c.config.Rootless && c.newgidmapPath != "" {
|
if c.config.RootlessEUID && c.newgidmapPath != "" {
|
||||||
r.AddData(&Bytemsg{
|
r.AddData(&Bytemsg{
|
||||||
Type: GidmapPathAttr,
|
Type: GidmapPathAttr,
|
||||||
Value: []byte(c.newgidmapPath),
|
Value: []byte(c.newgidmapPath),
|
||||||
@@ -1869,8 +1870,8 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||||||
|
|
||||||
// write rootless
|
// write rootless
|
||||||
r.AddData(&Boolmsg{
|
r.AddData(&Boolmsg{
|
||||||
Type: RootlessAttr,
|
Type: RootlessEUIDAttr,
|
||||||
Value: c.config.Rootless,
|
Value: c.config.RootlessEUID,
|
||||||
})
|
})
|
||||||
|
|
||||||
return bytes.NewReader(r.Serialize()), nil
|
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
|
// containers that use the native cgroups filesystem implementation to create
|
||||||
// and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is
|
// and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is
|
||||||
// that RootlessCgroupfs can transparently handle permission errors that occur
|
// 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).
|
// they've been set up properly).
|
||||||
func RootlessCgroupfs(l *LinuxFactory) error {
|
func RootlessCgroupfs(l *LinuxFactory) error {
|
||||||
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
|
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ type initConfig struct {
|
|||||||
CreateConsole bool `json:"create_console"`
|
CreateConsole bool `json:"create_console"`
|
||||||
ConsoleWidth uint16 `json:"console_width"`
|
ConsoleWidth uint16 `json:"console_width"`
|
||||||
ConsoleHeight uint16 `json:"console_height"`
|
ConsoleHeight uint16 `json:"console_height"`
|
||||||
Rootless bool `json:"rootless"`
|
RootlessEUID bool `json:"rootless_euid,omitempty"`
|
||||||
|
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type initer interface {
|
type initer interface {
|
||||||
@@ -283,7 +284,7 @@ func setupUser(config *initConfig) error {
|
|||||||
return fmt.Errorf("cannot set gid to unmapped user in user namespace")
|
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 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
|
// 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
|
// 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
|
// 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
|
// ignore setting groups here (since the user didn't explicitly ask us to
|
||||||
// set the group).
|
// set the group).
|
||||||
if !config.Rootless {
|
if !config.RootlessEUID {
|
||||||
suppGroups := append(execUser.Sgids, addGroups...)
|
suppGroups := append(execUser.Sgids, addGroups...)
|
||||||
if err := unix.Setgroups(suppGroups); err != nil {
|
if err := unix.Setgroups(suppGroups); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ import (
|
|||||||
// list of known message types we want to send to bootstrap program
|
// list of known message types we want to send to bootstrap program
|
||||||
// The number is randomly chosen to not conflict with known netlink types
|
// The number is randomly chosen to not conflict with known netlink types
|
||||||
const (
|
const (
|
||||||
InitMsg uint16 = 62000
|
InitMsg uint16 = 62000
|
||||||
CloneFlagsAttr uint16 = 27281
|
CloneFlagsAttr uint16 = 27281
|
||||||
NsPathsAttr uint16 = 27282
|
NsPathsAttr uint16 = 27282
|
||||||
UidmapAttr uint16 = 27283
|
UidmapAttr uint16 = 27283
|
||||||
GidmapAttr uint16 = 27284
|
GidmapAttr uint16 = 27284
|
||||||
SetgroupAttr uint16 = 27285
|
SetgroupAttr uint16 = 27285
|
||||||
OomScoreAdjAttr uint16 = 27286
|
OomScoreAdjAttr uint16 = 27286
|
||||||
RootlessAttr uint16 = 27287
|
RootlessEUIDAttr uint16 = 27287
|
||||||
UidmapPathAttr uint16 = 27288
|
UidmapPathAttr uint16 = 27288
|
||||||
GidmapPathAttr uint16 = 27289
|
GidmapPathAttr uint16 = 27289
|
||||||
)
|
)
|
||||||
|
|
||||||
type Int32msg struct {
|
type Int32msg struct {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ struct nlconfig_t {
|
|||||||
uint8_t is_setgroup;
|
uint8_t is_setgroup;
|
||||||
|
|
||||||
/* Rootless container settings. */
|
/* Rootless container settings. */
|
||||||
uint8_t is_rootless;
|
uint8_t is_rootless_euid; /* boolean */
|
||||||
char *uidmappath;
|
char *uidmappath;
|
||||||
size_t uidmappath_len;
|
size_t uidmappath_len;
|
||||||
char *gidmappath;
|
char *gidmappath;
|
||||||
@@ -100,7 +100,7 @@ struct nlconfig_t {
|
|||||||
#define GIDMAP_ATTR 27284
|
#define GIDMAP_ATTR 27284
|
||||||
#define SETGROUP_ATTR 27285
|
#define SETGROUP_ATTR 27285
|
||||||
#define OOM_SCORE_ADJ_ATTR 27286
|
#define OOM_SCORE_ADJ_ATTR 27286
|
||||||
#define ROOTLESS_ATTR 27287
|
#define ROOTLESS_EUID_ATTR 27287
|
||||||
#define UIDMAPPATH_ATTR 27288
|
#define UIDMAPPATH_ATTR 27288
|
||||||
#define GIDMAPPATH_ATTR 27289
|
#define GIDMAPPATH_ATTR 27289
|
||||||
|
|
||||||
@@ -419,8 +419,8 @@ static void nl_parse(int fd, struct nlconfig_t *config)
|
|||||||
case CLONE_FLAGS_ATTR:
|
case CLONE_FLAGS_ATTR:
|
||||||
config->cloneflags = readint32(current);
|
config->cloneflags = readint32(current);
|
||||||
break;
|
break;
|
||||||
case ROOTLESS_ATTR:
|
case ROOTLESS_EUID_ATTR:
|
||||||
config->is_rootless = readint8(current);
|
config->is_rootless_euid = readint8(current); /* boolean */
|
||||||
break;
|
break;
|
||||||
case OOM_SCORE_ADJ_ATTR:
|
case OOM_SCORE_ADJ_ATTR:
|
||||||
config->oom_score_adj = current;
|
config->oom_score_adj = current;
|
||||||
@@ -687,7 +687,7 @@ void nsexec(void)
|
|||||||
* newuidmap/newgidmap shall be used.
|
* 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);
|
update_setgroups(child, SETGROUPS_DENY);
|
||||||
|
|
||||||
/* Set up mappings. */
|
/* Set up mappings. */
|
||||||
@@ -953,7 +953,7 @@ void nsexec(void)
|
|||||||
if (setgid(0) < 0)
|
if (setgid(0) < 0)
|
||||||
bail("setgid failed");
|
bail("setgid failed");
|
||||||
|
|
||||||
if (!config.is_rootless && config.is_setgroup) {
|
if (!config.is_rootless_euid && config.is_setgroup) {
|
||||||
if (setgroups(0, NULL) < 0)
|
if (setgroups(0, NULL) < 0)
|
||||||
bail("setgroups failed");
|
bail("setgroups failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,15 +46,16 @@ type parentProcess interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type setnsProcess struct {
|
type setnsProcess struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
parentPipe *os.File
|
parentPipe *os.File
|
||||||
childPipe *os.File
|
childPipe *os.File
|
||||||
cgroupPaths map[string]string
|
cgroupPaths map[string]string
|
||||||
intelRdtPath string
|
rootlessCgroups bool
|
||||||
config *initConfig
|
intelRdtPath string
|
||||||
fds []string
|
config *initConfig
|
||||||
process *Process
|
fds []string
|
||||||
bootstrapData io.Reader
|
process *Process
|
||||||
|
bootstrapData io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *setnsProcess) startTime() (uint64, error) {
|
func (p *setnsProcess) startTime() (uint64, error) {
|
||||||
@@ -86,7 +87,7 @@ func (p *setnsProcess) start() (err error) {
|
|||||||
return newSystemErrorWithCause(err, "executing setns process")
|
return newSystemErrorWithCause(err, "executing setns process")
|
||||||
}
|
}
|
||||||
if len(p.cgroupPaths) > 0 {
|
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())
|
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
|
// 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.
|
// are needed.
|
||||||
func ToRootless(spec *specs.Spec) {
|
func ToRootless(spec *specs.Spec) {
|
||||||
var namespaces []specs.LinuxNamespace
|
var namespaces []specs.LinuxNamespace
|
||||||
|
|||||||
@@ -148,7 +148,8 @@ type CreateOpts struct {
|
|||||||
NoPivotRoot bool
|
NoPivotRoot bool
|
||||||
NoNewKeyring bool
|
NoNewKeyring bool
|
||||||
Spec *specs.Spec
|
Spec *specs.Spec
|
||||||
Rootless bool
|
RootlessEUID bool
|
||||||
|
RootlessCgroups bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLibcontainerConfig creates a new libcontainer configuration from a
|
// 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))
|
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
config := &configs.Config{
|
config := &configs.Config{
|
||||||
Rootfs: rootfsPath,
|
Rootfs: rootfsPath,
|
||||||
NoPivotRoot: opts.NoPivotRoot,
|
NoPivotRoot: opts.NoPivotRoot,
|
||||||
Readonlyfs: spec.Root.Readonly,
|
Readonlyfs: spec.Root.Readonly,
|
||||||
Hostname: spec.Hostname,
|
Hostname: spec.Hostname,
|
||||||
Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)),
|
Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)),
|
||||||
NoNewKeyring: opts.NoNewKeyring,
|
NoNewKeyring: opts.NoNewKeyring,
|
||||||
Rootless: opts.Rootless,
|
RootlessEUID: opts.RootlessEUID,
|
||||||
|
RootlessCgroups: opts.RootlessCgroups,
|
||||||
}
|
}
|
||||||
|
|
||||||
exists := false
|
exists := false
|
||||||
@@ -332,12 +334,9 @@ func createCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
|
|||||||
c.Path = myCgroupPath
|
c.Path = myCgroupPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// In rootless containers, any attempt to make cgroup changes will fail.
|
// In rootless containers, any attempt to make cgroup changes is likely to fail.
|
||||||
// libcontainer will validate this and we shouldn't add any cgroup options
|
// libcontainer will validate this but ignores the error.
|
||||||
// the user didn't specify.
|
c.Resources.AllowedDevices = allowedDevices
|
||||||
if !opts.Rootless {
|
|
||||||
c.Resources.AllowedDevices = allowedDevices
|
|
||||||
}
|
|
||||||
if spec.Linux != nil {
|
if spec.Linux != nil {
|
||||||
r := spec.Linux.Resources
|
r := spec.Linux.Resources
|
||||||
if r == nil {
|
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
|
||||||
// append the default allowed devices to the end of the list
|
c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
|
||||||
c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
|
|
||||||
}
|
|
||||||
return c, nil
|
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) {
|
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
|
||||||
t.Skip("userns is unsupported")
|
t.Skip("userns is unsupported")
|
||||||
}
|
}
|
||||||
@@ -430,7 +430,8 @@ func TestRootlessSpecconvValidate(t *testing.T) {
|
|||||||
CgroupName: "ContainerID",
|
CgroupName: "ContainerID",
|
||||||
UseSystemdCgroup: false,
|
UseSystemdCgroup: false,
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
Rootless: true,
|
RootlessEUID: true,
|
||||||
|
RootlessCgroups: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := CreateLibcontainerConfig(opts)
|
config, err := CreateLibcontainerConfig(opts)
|
||||||
|
|||||||
@@ -63,13 +63,8 @@ func main() {
|
|||||||
app.Version = strings.Join(v, "\n")
|
app.Version = strings.Join(v, "\n")
|
||||||
|
|
||||||
root := "/run/runc"
|
root := "/run/runc"
|
||||||
rootless, err := isRootless(nil)
|
if shouldHonorXDGRuntimeDir() {
|
||||||
if err != nil {
|
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
if rootless {
|
|
||||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
|
||||||
if runtimeDir != "" {
|
|
||||||
root = runtimeDir + "/runc"
|
root = runtimeDir + "/runc"
|
||||||
// According to the XDG specification, we need to set anything in
|
// 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
|
// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
|
||||||
@@ -115,7 +110,7 @@ func main() {
|
|||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "rootless",
|
Name: "rootless",
|
||||||
Value: "auto",
|
Value: "auto",
|
||||||
Usage: "enable rootless mode ('true', 'false', or 'auto')",
|
Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/urfave/cli"
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
var pauseCommand = cli.Command{
|
var pauseCommand = cli.Command{
|
||||||
Name: "pause",
|
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 {
|
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||||
return err
|
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)
|
container, err := getContainer(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err := checkArgs(context, 1, exactArgs); err != nil {
|
||||||
return err
|
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)
|
container, err := getContainer(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,13 +29,12 @@ var psCommand = cli.Command{
|
|||||||
if err := checkArgs(context, 1, minArgs); err != nil {
|
if err := checkArgs(context, 1, minArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently not supported with rootless containers.
|
rootlessCg, err := shouldUseRootlessCgroupManager(context)
|
||||||
rootless, err := isRootless(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rootless {
|
if rootlessCg {
|
||||||
return fmt.Errorf("runc ps requires root")
|
logrus.Warn("runc ps may fail if you don't have the full access to cgroups")
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := getContainer(context)
|
container, err := getContainer(context)
|
||||||
|
|||||||
+4
-7
@@ -3,10 +3,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,12 +97,8 @@ using the runc checkpoint command.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// XXX: Currently this is untested with rootless containers.
|
||||||
rootless, err := isRootless(context)
|
if os.Geteuid() != 0 || system.RunningInUserNS() {
|
||||||
if err != nil {
|
logrus.Warn("runc checkpoint is untested with rootless containers")
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rootless {
|
|
||||||
return fmt.Errorf("runc restore requires root")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := setupSpec(context)
|
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"
|
||||||
|
}
|
||||||
@@ -61,7 +61,11 @@ container on your host.
|
|||||||
Alternatively, you can start a rootless container, which has the ability to run
|
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
|
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
|
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{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "bundle, b",
|
Name: "bundle, b",
|
||||||
|
|||||||
+5
-24
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
||||||
"github.com/opencontainers/runc/libcontainer/specconv"
|
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||||
"github.com/opencontainers/runc/libcontainer/system"
|
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"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
|
// We default to cgroupfs, and can only use systemd if the system is a
|
||||||
// systemd box.
|
// systemd box.
|
||||||
cgroupManager := libcontainer.Cgroupfs
|
cgroupManager := libcontainer.Cgroupfs
|
||||||
rootless, err := isRootless(context)
|
rootlessCg, err := shouldUseRootlessCgroupManager(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if rootless {
|
if rootlessCg {
|
||||||
cgroupManager = libcontainer.RootlessCgroupfs
|
cgroupManager = libcontainer.RootlessCgroupfs
|
||||||
}
|
}
|
||||||
if context.GlobalBool("systemd-cgroup") {
|
if context.GlobalBool("systemd-cgroup") {
|
||||||
@@ -226,27 +225,8 @@ func createPidFile(path string, process *libcontainer.Process) error {
|
|||||||
return os.Rename(tmpName, path)
|
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) {
|
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
|
||||||
rootless, err := isRootless(context)
|
rootlessCg, err := shouldUseRootlessCgroupManager(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -256,7 +236,8 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcont
|
|||||||
NoPivotRoot: context.Bool("no-pivot"),
|
NoPivotRoot: context.Bool("no-pivot"),
|
||||||
NoNewKeyring: context.Bool("no-new-keyring"),
|
NoNewKeyring: context.Bool("no-new-keyring"),
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
Rootless: rootless,
|
RootlessEUID: os.Geteuid() != 0,
|
||||||
|
RootlessCgroups: rootlessCg,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user