Permalink
Browse files

runc: add support for rootless containers

This enables the support for the rootless container mode. There are many
restrictions on what rootless containers can do, so many different runC
commands have been disabled:

* runc checkpoint
* runc events
* runc pause
* runc ps
* runc restore
* runc resume
* runc update

The following commands work:

* runc create
* runc delete
* runc exec
* runc kill
* runc list
* runc run
* runc spec
* runc state

In addition, any specification options that imply joining cgroups have
also been disabled. This is due to support for unprivileged subtree
management not being available from Linux upstream.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
  • Loading branch information...
cyphar committed Apr 23, 2016
1 parent 6bd4bd9 commit d2f49696b09a60f5ab60f7db8259c52a2a2cdbed
View
@@ -4,7 +4,7 @@
SOURCES := $(shell find . 2>&1 | grep -E '.*\.(c|h|go)$$')
PREFIX := $(DESTDIR)/usr/local
BINDIR := $(PREFIX)/sbin
BINDIR := $(PREFIX)/bin
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
RUNC_IMAGE := runc_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
View
@@ -39,6 +39,11 @@ checkpointed.`,
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
return fmt.Errorf("runc checkpoint requires root")
}
container, err := getContainer(context)
if err != nil {
return err
View
@@ -90,9 +90,6 @@ following will output a list of processes running in the container:
if err := checkArgs(context, 1, minArgs); err != nil {
return err
}
if os.Geteuid() != 0 {
return fmt.Errorf("runc should be run as root")
}
if err := revisePidFile(context); err != nil {
return err
}
@@ -183,6 +183,9 @@ type Config struct {
// NoNewKeyring will not allocated a new session keyring for the container. It will use the
// callers keyring in this case.
NoNewKeyring bool `json:"no_new_keyring"`
// Rootless specifies whether the container is a rootless container.
Rootless bool `json:"rootless"`
}
type Hooks struct {
@@ -0,0 +1,117 @@
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 {
return err
}
if err := rootlessMount(config); err != nil {
return err
}
// Currently, cgroups cannot effectively be used in rootless containers.
// The new cgroup namespace doesn't really help us either because it doesn't
// have nice interactions with the user namespace (we're working with upstream
// to fix this).
if err := rootlessCgroup(config); err != nil {
return err
}
// XXX: We currently can't verify the user config at all, because
// configs.Config doesn't store the user-related configs. So this
// has to be verified by setupUser() in init_linux.go.
return nil
}
func rootlessMappings(config *configs.Config) error {
rootuid, err := config.HostUID()
if err != nil {
return fmt.Errorf("failed to get root uid from uidMappings: %v", err)
}
if euid := geteuid(); euid != 0 {
if !config.Namespaces.Contains(configs.NEWUSER) {
return fmt.Errorf("rootless containers require user namespaces")
}
if rootuid != euid {
return fmt.Errorf("rootless containers cannot map container root to a different host user")
}
}
rootgid, err := config.HostGID()
if err != nil {
return fmt.Errorf("failed to get root gid from gidMappings: %v", err)
}
// Similar to the above test, we need to make sure that we aren't trying to
// map to a group ID that we don't have the right to be.
if rootgid != getegid() {
return fmt.Errorf("rootless containers cannot map container root to a different host group")
}
// We can only map one user and group inside a container (our own).
if len(config.UidMappings) != 1 || config.UidMappings[0].Size != 1 {
return fmt.Errorf("rootless containers cannot map more than one user")
}
if len(config.GidMappings) != 1 || config.GidMappings[0].Size != 1 {
return fmt.Errorf("rootless containers cannot map more than one group")
}
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
}
// 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")
}
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 {
// 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.
for _, mount := range config.Mounts {
// Check that the options list doesn't contain any uid= or gid= entries
// that don't resolve to root.
for _, opt := range strings.Split(mount.Data, ",") {
if strings.HasPrefix(opt, "uid=") && opt != "uid=0" {
return fmt.Errorf("cannot specify uid= mount options in rootless containers where argument isn't 0")
}
if strings.HasPrefix(opt, "gid=") && opt != "gid=0" {
return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0")
}
}
}
return nil
}
@@ -0,0 +1,195 @@
package validate
import (
"testing"
"github.com/opencontainers/runc/libcontainer/configs"
)
func init() {
geteuid = func() int { return 1337 }
getegid = func() int { return 7331 }
}
func rootlessConfig() *configs.Config {
return &configs.Config{
Rootfs: "/var",
Rootless: true,
Namespaces: configs.Namespaces(
[]configs.Namespace{
{Type: configs.NEWUSER},
},
),
UidMappings: []configs.IDMap{
{
HostID: geteuid(),
ContainerID: 0,
Size: 1,
},
},
GidMappings: []configs.IDMap{
{
HostID: getegid(),
ContainerID: 0,
Size: 1,
},
},
}
}
func TestValidateRootless(t *testing.T) {
validator := New()
config := rootlessConfig()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur: %+v", err)
}
}
/* rootlessMappings() */
func TestValidateRootlessUserns(t *testing.T) {
validator := New()
config := rootlessConfig()
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) {
validator := New()
config := rootlessConfig()
config.UidMappings = nil
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no uid mappings provided")
}
config = rootlessConfig()
config.UidMappings[0].HostID = geteuid() + 1
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if geteuid() != mapped uid")
}
config = rootlessConfig()
config.UidMappings[0].Size = 1024
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if more than one uid mapped")
}
config = rootlessConfig()
config.UidMappings = append(config.UidMappings, configs.IDMap{
HostID: geteuid() + 1,
ContainerID: 0,
Size: 1,
})
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if more than one uid extent mapped")
}
}
func TestValidateRootlessMappingGid(t *testing.T) {
validator := New()
config := rootlessConfig()
config.GidMappings = nil
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no gid mappings provided")
}
config = rootlessConfig()
config.GidMappings[0].HostID = getegid() + 1
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if getegid() != mapped gid")
}
config = rootlessConfig()
config.GidMappings[0].Size = 1024
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if more than one gid mapped")
}
config = rootlessConfig()
config.GidMappings = append(config.GidMappings, configs.IDMap{
HostID: getegid() + 1,
ContainerID: 0,
Size: 1,
})
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if more than one gid extent mapped")
}
}
/* rootlessMount() */
func TestValidateRootlessMountUid(t *testing.T) {
config := rootlessConfig()
validator := New()
config.Mounts = []*configs.Mount{
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
},
}
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur when uid= not set in mount options: %+v", err)
}
config.Mounts[0].Data = "uid=5"
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur when setting uid=5 in mount options")
}
config.Mounts[0].Data = "uid=0"
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur when setting uid=0 in mount options: %+v", err)
}
}
func TestValidateRootlessMountGid(t *testing.T) {
config := rootlessConfig()
validator := New()
config.Mounts = []*configs.Mount{
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
},
}
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur when gid= not set in mount options: %+v", err)
}
config.Mounts[0].Data = "gid=5"
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur when setting gid=5 in mount options")
}
config.Mounts[0].Data = "gid=0"
if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur when setting gid=0 in mount options: %+v", err)
}
}
/* rootlessCgroup() */
func TestValidateRootlessCgroup(t *testing.T) {
validator := New()
config := rootlessConfig()
config.Cgroups = &configs.Cgroup{
Resources: &configs.Resources{
PidsLimit: 1337,
},
}
if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if cgroup limits set")
}
}
@@ -40,6 +40,11 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
if err := v.sysctl(config); err != nil {
return err
}
if config.Rootless {
if err := v.rootless(config); err != nil {
return err
}
}
return nil
}
Oops, something went wrong.

0 comments on commit d2f4969

Please sign in to comment.