Merge pull request #1529 from giuseppe/rootless-improvements
Support multiple users/groups mapped for the rootless case
This commit is contained in:
commit
8b47a242a9
|
@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y \
|
|||
protobuf-c-compiler \
|
||||
protobuf-compiler \
|
||||
python-minimal \
|
||||
uidmap \
|
||||
--no-install-recommends \
|
||||
&& apt-get clean
|
||||
|
||||
|
|
5
Makefile
5
Makefile
|
@ -77,11 +77,10 @@ localintegration: all
|
|||
bats -t tests/integration${TESTFLAGS}
|
||||
|
||||
rootlessintegration: runcimage
|
||||
docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) --cap-drop=ALL -u rootless $(RUNC_IMAGE) make localintegration
|
||||
docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localrootlessintegration
|
||||
|
||||
# FIXME: This should not be separate from rootlessintegration's method of running.
|
||||
localrootlessintegration: all
|
||||
sudo -u rootless -H PATH="${PATH}" bats -t tests/integration${TESTFLAGS}
|
||||
tests/rootless.sh
|
||||
|
||||
shell: all
|
||||
docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash
|
||||
|
|
|
@ -36,37 +36,27 @@ func (v *ConfigValidator) rootless(config *configs.Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func rootlessMappings(config *configs.Config) error {
|
||||
rootuid, err := config.HostRootUID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get root uid from uidMappings: %v", err)
|
||||
func hasIDMapping(id int, mappings []configs.IDMap) bool {
|
||||
for _, m := range mappings {
|
||||
if id >= m.ContainerID && id < m.ContainerID+m.Size {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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 rootuid != euid {
|
||||
return fmt.Errorf("rootless containers cannot map container root to a different host user")
|
||||
}
|
||||
}
|
||||
|
||||
rootgid, err := config.HostRootGID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get root gid from gidMappings: %v", err)
|
||||
if len(config.UidMappings) == 0 {
|
||||
return fmt.Errorf("rootless containers requires at least one UID mapping")
|
||||
}
|
||||
|
||||
// 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")
|
||||
if len(config.GidMappings) == 0 {
|
||||
return fmt.Errorf("rootless containers requires at least one UID mapping")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -104,11 +94,28 @@ func rootlessMount(config *configs.Config) error {
|
|||
// 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, "uid=") {
|
||||
var uid int
|
||||
n, err := fmt.Sscanf(opt, "uid=%d", &uid)
|
||||
if n != 1 || err != nil {
|
||||
// Ignore unknown mount options.
|
||||
continue
|
||||
}
|
||||
if !hasIDMapping(uid, config.UidMappings) {
|
||||
return fmt.Errorf("cannot specify uid= mount options for unmapped uid in rootless containers")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(opt, "gid=") {
|
||||
var gid int
|
||||
n, err := fmt.Sscanf(opt, "gid=%d", &gid)
|
||||
if n != 1 || err != nil {
|
||||
// Ignore unknown mount options.
|
||||
continue
|
||||
}
|
||||
if !hasIDMapping(gid, config.GidMappings) {
|
||||
return fmt.Errorf("cannot specify gid= mount options for unmapped gid in rootless containers")
|
||||
}
|
||||
if strings.HasPrefix(opt, "gid=") && opt != "gid=0" {
|
||||
return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,28 +66,6 @@ func TestValidateRootlessMappingUid(t *testing.T) {
|
|||
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) {
|
||||
|
@ -98,28 +76,6 @@ func TestValidateRootlessMappingGid(t *testing.T) {
|
|||
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() */
|
||||
|
@ -149,6 +105,18 @@ func TestValidateRootlessMountUid(t *testing.T) {
|
|||
if err := validator.Validate(config); err != nil {
|
||||
t.Errorf("Expected error to not occur when setting uid=0 in mount options: %+v", err)
|
||||
}
|
||||
|
||||
config.Mounts[0].Data = "uid=2"
|
||||
config.UidMappings[0].Size = 10
|
||||
if err := validator.Validate(config); err != nil {
|
||||
t.Errorf("Expected error to not occur when setting uid=2 in mount options and UidMapping[0].size is 10")
|
||||
}
|
||||
|
||||
config.Mounts[0].Data = "uid=20"
|
||||
config.UidMappings[0].Size = 10
|
||||
if err := validator.Validate(config); err == nil {
|
||||
t.Errorf("Expected error to occur when setting uid=20 in mount options and UidMapping[0].size is 10")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRootlessMountGid(t *testing.T) {
|
||||
|
@ -176,6 +144,18 @@ func TestValidateRootlessMountGid(t *testing.T) {
|
|||
if err := validator.Validate(config); err != nil {
|
||||
t.Errorf("Expected error to not occur when setting gid=0 in mount options: %+v", err)
|
||||
}
|
||||
|
||||
config.Mounts[0].Data = "gid=5"
|
||||
config.GidMappings[0].Size = 10
|
||||
if err := validator.Validate(config); err != nil {
|
||||
t.Errorf("Expected error to not occur when setting gid=5 in mount options and GidMapping[0].size is 10")
|
||||
}
|
||||
|
||||
config.Mounts[0].Data = "gid=11"
|
||||
config.GidMappings[0].Size = 10
|
||||
if err := validator.Validate(config); err == nil {
|
||||
t.Errorf("Expected error to occur when setting gid=11 in mount options and GidMapping[0].size is 10")
|
||||
}
|
||||
}
|
||||
|
||||
/* rootlessCgroup() */
|
||||
|
|
|
@ -44,6 +44,8 @@ type linuxContainer struct {
|
|||
initProcess parentProcess
|
||||
initProcessStartTime uint64
|
||||
criuPath string
|
||||
newuidmapPath string
|
||||
newgidmapPath string
|
||||
m sync.Mutex
|
||||
criuVersion int
|
||||
state containerState
|
||||
|
@ -1707,6 +1709,12 @@ 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 != "" {
|
||||
r.AddData(&Bytemsg{
|
||||
Type: UidmapPathAttr,
|
||||
Value: []byte(c.newuidmapPath),
|
||||
})
|
||||
}
|
||||
b, err := encodeIDMapping(c.config.UidMappings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1727,6 +1735,12 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||
Type: GidmapAttr,
|
||||
Value: b,
|
||||
})
|
||||
if c.config.Rootless && c.newgidmapPath != "" {
|
||||
r.AddData(&Bytemsg{
|
||||
Type: GidmapPathAttr,
|
||||
Value: []byte(c.newgidmapPath),
|
||||
})
|
||||
}
|
||||
// The following only applies if we are root.
|
||||
if !c.config.Rootless {
|
||||
// check if we have CAP_SETGID to setgroup properly
|
||||
|
|
|
@ -140,6 +140,9 @@ func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
|
|||
}
|
||||
Cgroupfs(l)
|
||||
for _, opt := range options {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if err := opt(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -160,6 +163,11 @@ type LinuxFactory struct {
|
|||
// containers.
|
||||
CriuPath string
|
||||
|
||||
// New{u,g}uidmapPath is the path to the binaries used for mapping with
|
||||
// rootless containers.
|
||||
NewuidmapPath string
|
||||
NewgidmapPath string
|
||||
|
||||
// Validator provides validation to container configurations.
|
||||
Validator validate.Validator
|
||||
|
||||
|
@ -201,6 +209,8 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
|
|||
config: config,
|
||||
initArgs: l.InitArgs,
|
||||
criuPath: l.CriuPath,
|
||||
newuidmapPath: l.NewuidmapPath,
|
||||
newgidmapPath: l.NewgidmapPath,
|
||||
cgroupManager: l.NewCgroupsManager(config.Cgroups, nil),
|
||||
}
|
||||
c.intelRdtManager = nil
|
||||
|
@ -236,6 +246,8 @@ func (l *LinuxFactory) Load(id string) (Container, error) {
|
|||
config: &state.Config,
|
||||
initArgs: l.InitArgs,
|
||||
criuPath: l.CriuPath,
|
||||
newuidmapPath: l.NewuidmapPath,
|
||||
newgidmapPath: l.NewgidmapPath,
|
||||
cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths),
|
||||
root: containerRoot,
|
||||
created: state.Created,
|
||||
|
@ -349,3 +361,21 @@ func (l *LinuxFactory) validateID(id string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewuidmapPath returns an option func to configure a LinuxFactory with the
|
||||
// provided ..
|
||||
func NewuidmapPath(newuidmapPath string) func(*LinuxFactory) error {
|
||||
return func(l *LinuxFactory) error {
|
||||
l.NewuidmapPath = newuidmapPath
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewgidmapPath returns an option func to configure a LinuxFactory with the
|
||||
// provided ..
|
||||
func NewgidmapPath(newgidmapPath string) func(*LinuxFactory) error {
|
||||
return func(l *LinuxFactory) error {
|
||||
l.NewgidmapPath = newgidmapPath
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,25 +261,27 @@ func setupUser(config *initConfig) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Rather than just erroring out later in setuid(2) and setgid(2), check
|
||||
// that the user is mapped here.
|
||||
if _, err := config.Config.HostUID(int(execUser.Uid)); err != nil {
|
||||
return fmt.Errorf("cannot set uid to unmapped user in user namespace")
|
||||
}
|
||||
if _, err := config.Config.HostGID(int(execUser.Gid)); err != nil {
|
||||
return fmt.Errorf("cannot set gid to unmapped user in user namespace")
|
||||
}
|
||||
|
||||
if config.Rootless {
|
||||
if execUser.Uid != 0 {
|
||||
return fmt.Errorf("cannot run as a non-root user in a rootless container")
|
||||
}
|
||||
|
||||
if execUser.Gid != 0 {
|
||||
return fmt.Errorf("cannot run as a non-root group in a rootless container")
|
||||
}
|
||||
|
||||
// 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
|
||||
// earlier, but if libcontainer.Process.User was typesafe this might work.
|
||||
// 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
|
||||
// this might work.
|
||||
if len(addGroups) > 0 {
|
||||
return fmt.Errorf("cannot set any additional groups in a rootless container")
|
||||
}
|
||||
}
|
||||
|
||||
// before we change to the container's user make sure that the processes STDIO
|
||||
// is correctly owned by the user that we are switching to.
|
||||
// Before we change to the container's user make sure that the processes
|
||||
// STDIO is correctly owned by the user that we are switching to.
|
||||
if err := fixStdioPermissions(config, execUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -298,7 +300,6 @@ func setupUser(config *initConfig) error {
|
|||
if err := system.Setgid(execUser.Gid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := system.Setuid(execUser.Uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ const (
|
|||
SetgroupAttr uint16 = 27285
|
||||
OomScoreAdjAttr uint16 = 27286
|
||||
RootlessAttr uint16 = 27287
|
||||
UidmapPathAttr uint16 = 27288
|
||||
GidmapPathAttr uint16 = 27289
|
||||
)
|
||||
|
||||
type Int32msg struct {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
#define _GNU_SOURCE
|
||||
#include <endian.h>
|
||||
#include <errno.h>
|
||||
|
@ -19,6 +20,8 @@
|
|||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <linux/netlink.h>
|
||||
|
@ -64,7 +67,13 @@ struct clone_t {
|
|||
|
||||
struct nlconfig_t {
|
||||
char *data;
|
||||
|
||||
/* Process settings. */
|
||||
uint32_t cloneflags;
|
||||
char *oom_score_adj;
|
||||
size_t oom_score_adj_len;
|
||||
|
||||
/* User namespace settings.*/
|
||||
char *uidmap;
|
||||
size_t uidmap_len;
|
||||
char *gidmap;
|
||||
|
@ -72,9 +81,13 @@ struct nlconfig_t {
|
|||
char *namespaces;
|
||||
size_t namespaces_len;
|
||||
uint8_t is_setgroup;
|
||||
|
||||
/* Rootless container settings.*/
|
||||
uint8_t is_rootless;
|
||||
char *oom_score_adj;
|
||||
size_t oom_score_adj_len;
|
||||
char *uidmappath;
|
||||
size_t uidmappath_len;
|
||||
char *gidmappath;
|
||||
size_t gidmappath_len;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -89,6 +102,8 @@ struct nlconfig_t {
|
|||
#define SETGROUP_ATTR 27285
|
||||
#define OOM_SCORE_ADJ_ATTR 27286
|
||||
#define ROOTLESS_ATTR 27287
|
||||
#define UIDMAPPATH_ATTR 27288
|
||||
#define GIDMAPPATH_ATTR 27289
|
||||
|
||||
/*
|
||||
* Use the raw syscall for versions of glibc which don't include a function for
|
||||
|
@ -191,22 +206,96 @@ static void update_setgroups(int pid, enum policy_t setgroup)
|
|||
}
|
||||
}
|
||||
|
||||
static void update_uidmap(int pid, char *map, size_t map_len)
|
||||
static int try_mapping_tool(const char *app, int pid, char *map, size_t map_len)
|
||||
{
|
||||
if (map == NULL || map_len <= 0)
|
||||
return;
|
||||
int child;
|
||||
|
||||
if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0)
|
||||
bail("failed to update /proc/%d/uid_map", pid);
|
||||
/*
|
||||
* If @app is NULL, execve will segfault. Just check it here and bail (if
|
||||
* we're in this path, the caller is already getting desparate and there
|
||||
* isn't a backup to this failing). This usually would be a configuration
|
||||
* or programming issue.
|
||||
*/
|
||||
if (!app)
|
||||
bail("mapping tool not present");
|
||||
|
||||
child = fork();
|
||||
if (child < 0)
|
||||
bail("failed to fork");
|
||||
|
||||
if (!child) {
|
||||
#define MAX_ARGV 20
|
||||
char *argv[MAX_ARGV];
|
||||
char *envp[] = {NULL};
|
||||
char pid_fmt[16];
|
||||
int argc = 0;
|
||||
char *next;
|
||||
|
||||
snprintf(pid_fmt, 16, "%d", pid);
|
||||
|
||||
argv[argc++] = (char *) app;
|
||||
argv[argc++] = pid_fmt;
|
||||
/*
|
||||
* Convert the map string into a list of argument that
|
||||
* newuidmap/newgidmap can understand.
|
||||
*/
|
||||
|
||||
while (argc < MAX_ARGV) {
|
||||
if (*map == '\0') {
|
||||
argv[argc++] = NULL;
|
||||
break;
|
||||
}
|
||||
argv[argc++] = map;
|
||||
next = strpbrk(map, "\n ");
|
||||
if (next == NULL)
|
||||
break;
|
||||
*next++ = '\0';
|
||||
map = next + strspn(next, "\n ");
|
||||
}
|
||||
|
||||
static void update_gidmap(int pid, char *map, size_t map_len)
|
||||
execve(app, argv, envp);
|
||||
bail("failed to execv");
|
||||
} else {
|
||||
int status;
|
||||
|
||||
while (true) {
|
||||
if (waitpid(child, &status, 0) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
bail("failed to waitpid");
|
||||
}
|
||||
if (WIFEXITED(status) || WIFSIGNALED(status))
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void update_uidmap(const char *path, int pid, char *map, size_t map_len)
|
||||
{
|
||||
if (map == NULL || map_len <= 0)
|
||||
return;
|
||||
|
||||
if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0)
|
||||
if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) {
|
||||
if (errno != EPERM)
|
||||
bail("failed to update /proc/%d/uid_map", pid);
|
||||
if (try_mapping_tool(path, pid, map, map_len))
|
||||
bail("failed to use newuid map on %d", pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_gidmap(const char *path, int pid, char *map, size_t map_len)
|
||||
{
|
||||
if (map == NULL || map_len <= 0)
|
||||
return;
|
||||
|
||||
if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) {
|
||||
if (errno != EPERM)
|
||||
bail("failed to update /proc/%d/gid_map", pid);
|
||||
if (try_mapping_tool(path, pid, map, map_len))
|
||||
bail("failed to use newgid map on %d", pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_oom_score_adj(char *data, size_t len)
|
||||
|
@ -350,6 +439,14 @@ static void nl_parse(int fd, struct nlconfig_t *config)
|
|||
config->gidmap = current;
|
||||
config->gidmap_len = payload_len;
|
||||
break;
|
||||
case UIDMAPPATH_ATTR:
|
||||
config->uidmappath = current;
|
||||
config->uidmappath_len = payload_len;
|
||||
break;
|
||||
case GIDMAPPATH_ATTR:
|
||||
config->gidmappath = current;
|
||||
config->gidmappath_len = payload_len;
|
||||
break;
|
||||
case SETGROUP_ATTR:
|
||||
config->is_setgroup = readint8(current);
|
||||
break;
|
||||
|
@ -596,8 +693,8 @@ void nsexec(void)
|
|||
update_setgroups(child, SETGROUPS_DENY);
|
||||
|
||||
/* Set up mappings. */
|
||||
update_uidmap(child, config.uidmap, config.uidmap_len);
|
||||
update_gidmap(child, config.gidmap, config.gidmap_len);
|
||||
update_uidmap(config.uidmappath, child, config.uidmap, config.uidmap_len);
|
||||
update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len);
|
||||
|
||||
s = SYNC_USERMAP_ACK;
|
||||
if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
|
||||
|
|
11
main.go
11
main.go
|
@ -61,6 +61,15 @@ func main() {
|
|||
}
|
||||
v = append(v, fmt.Sprintf("spec: %s", specs.Version))
|
||||
app.Version = strings.Join(v, "\n")
|
||||
|
||||
root := "/run/runc"
|
||||
if os.Geteuid() != 0 {
|
||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if runtimeDir != "" {
|
||||
root = runtimeDir + "/runc"
|
||||
}
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
|
@ -78,7 +87,7 @@ func main() {
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "root",
|
||||
Value: "/run/runc",
|
||||
Value: root,
|
||||
Usage: "root directory for storage of container state (this should be located in tmpfs)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
|
|
|
@ -50,7 +50,7 @@ value for "bundle" is the current directory.
|
|||
--debug enable debug output for logging
|
||||
--log value set the log file path where internal debug information is written (default: "/dev/null")
|
||||
--log-format value set the format used by logs ('text' (default), or 'json') (default: "text")
|
||||
--root value root directory for storage of container state (this should be located in tmpfs) (default: "/run/runc")
|
||||
--root value root directory for storage of container state (this should be located in tmpfs) (default: "/run/runc" or $XDG_RUNTIME_DIR/runc for rootless containers)
|
||||
--criu value path to the criu binary used for checkpoint and restore (default: "criu")
|
||||
--systemd-cgroup enable systemd cgroup support, expects cgroupsPath to be of form "slice:prefix:name" for e.g. "system.slice:runc:434234"
|
||||
--help, -h show help
|
||||
|
|
|
@ -100,8 +100,8 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc exec --user" {
|
||||
# --user can't work in rootless containers
|
||||
requires root
|
||||
# --user can't work in rootless containers that don't have idmap.
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# run busybox detached
|
||||
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
|
||||
|
@ -110,5 +110,5 @@ function teardown() {
|
|||
runc exec --user 1000:1000 test_busybox id
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
[[ ${output} == "uid=1000 gid=1000" ]]
|
||||
[[ "${output}" == "uid=1000 gid=1000"* ]]
|
||||
}
|
||||
|
|
|
@ -58,15 +58,41 @@ function __runc() {
|
|||
"$RUNC" --log /proc/self/fd/2 --root "$ROOT" "$@"
|
||||
}
|
||||
|
||||
# Wrapper for runc spec.
|
||||
# Wrapper for runc spec, which takes only one argument (the bundle path).
|
||||
function runc_spec() {
|
||||
local args=""
|
||||
! [[ "$#" > 1 ]]
|
||||
|
||||
local args=()
|
||||
local bundle=""
|
||||
|
||||
if [ "$ROOTLESS" -ne 0 ]; then
|
||||
args+="--rootless"
|
||||
args+=("--rootless")
|
||||
fi
|
||||
if [ "$#" -ne 0 ]; then
|
||||
bundle="$1"
|
||||
args+=("--bundle" "$bundle")
|
||||
fi
|
||||
|
||||
runc spec $args "$@"
|
||||
runc spec "${args[@]}"
|
||||
|
||||
# Always add additional mappings if we have idmaps.
|
||||
if [[ "$ROOTLESS" -ne 0 ]] && [[ "$ROOTLESS_FEATURES" == *"idmap"* ]]; then
|
||||
runc_rootless_idmap "$bundle"
|
||||
fi
|
||||
}
|
||||
|
||||
# Shortcut to add additional uids and gids, based on the values set as part of
|
||||
# a rootless configuration.
|
||||
function runc_rootless_idmap() {
|
||||
bundle="${1:-.}"
|
||||
cat "$bundle/config.json" \
|
||||
| jq '.mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .)' \
|
||||
| jq '.linux.uidMappings |= .+ [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}]' \
|
||||
| jq '.linux.gidMappings |= .+ [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}]' \
|
||||
| jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+10))"', "containerID": 1, "size": 20}]' \
|
||||
| jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+100))"', "containerID": 1000, "size": '"$(($ROOTLESS_GIDMAP_LENGTH-1000))"'}]' \
|
||||
>"$bundle/config.json.tmp"
|
||||
mv "$bundle/config.json"{.tmp,}
|
||||
}
|
||||
|
||||
# Fails the current test, providing the error given.
|
||||
|
@ -90,14 +116,19 @@ function requires() {
|
|||
skip "test requires ${var}"
|
||||
fi
|
||||
;;
|
||||
rootless_idmap)
|
||||
if [[ "$ROOTLESS_FEATURES" != *"idmap"* ]]; then
|
||||
skip "test requires ${var}"
|
||||
fi
|
||||
;;
|
||||
cgroups_kmem)
|
||||
if [ ! -e "$KMEM" ]; then
|
||||
skip "Test requires ${var}."
|
||||
skip "Test requires ${var}"
|
||||
fi
|
||||
;;
|
||||
cgroups_rt)
|
||||
if [ ! -e "$RT_PERIOD" ]; then
|
||||
skip "Test requires ${var}."
|
||||
skip "Test requires ${var}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
|
|
|
@ -51,7 +51,7 @@ function teardown() {
|
|||
[ ! -e "$HELLO_BUNDLE"/config.json ]
|
||||
|
||||
# test generation of spec does not return an error
|
||||
runc_spec --bundle "$HELLO_BUNDLE"
|
||||
runc_spec "$HELLO_BUNDLE"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# test generation of spec created our config.json (spec)
|
||||
|
|
|
@ -21,8 +21,8 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc run detached ({u,g}id != 0)" {
|
||||
# cannot start containers as another user in rootless setup
|
||||
requires root
|
||||
# cannot start containers as another user in rootless setup without idmap
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# replace "uid": 0 with "uid": 1000
|
||||
# and do a similar thing for gid.
|
||||
|
|
|
@ -21,8 +21,8 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc run ({u,g}id != 0)" {
|
||||
# cannot start containers as another user in rootless setup
|
||||
requires root
|
||||
# cannot start containers as another user in rootless setup without idmap
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# replace "uid": 0 with "uid": 1000
|
||||
# and do a similar thing for gid.
|
||||
|
|
|
@ -24,9 +24,9 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc run [tty owner]" {
|
||||
# tty chmod is not doable in rootless containers.
|
||||
# tty chmod is not doable in rootless containers without idmap.
|
||||
# TODO: this can be made as a change to the gid test.
|
||||
requires root
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# Replace sh script with stat.
|
||||
sed -i 's/"sh"/"sh", "-c", "stat -c %u:%g $(tty) | tr : \\\\\\\\n"/' config.json
|
||||
|
@ -40,8 +40,8 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc run [tty owner] ({u,g}id != 0)" {
|
||||
# tty chmod is not doable in rootless containers.
|
||||
requires root
|
||||
# tty chmod is not doable in rootless containers without idmap.
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# replace "uid": 0 with "uid": 1000
|
||||
# and do a similar thing for gid.
|
||||
|
@ -76,9 +76,9 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc exec [tty owner]" {
|
||||
# tty chmod is not doable in rootless containers.
|
||||
# tty chmod is not doable in rootless containers without idmap.
|
||||
# TODO: this can be made as a change to the gid test.
|
||||
requires root
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# run busybox detached
|
||||
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
|
||||
|
@ -95,8 +95,8 @@ function teardown() {
|
|||
}
|
||||
|
||||
@test "runc exec [tty owner] ({u,g}id != 0)" {
|
||||
# tty chmod is not doable in rootless containers.
|
||||
requires root
|
||||
# tty chmod is not doable in rootless containers without idmap.
|
||||
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
|
||||
|
||||
# replace "uid": 0 with "uid": 1000
|
||||
# and do a similar thing for gid.
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
#!/bin/bash
|
||||
# Copyright (C) 2017 SUSE LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# rootless.sh -- Runner for rootless container tests. The purpose of this
|
||||
# script is to allow for the addition (and testing) of "opportunistic" features
|
||||
# to rootless containers while still testing the base features. In order to add
|
||||
# a new feature, please match the existing style. Add an entry to $ALL_FEATURES,
|
||||
# and add an enable_* and disable_* hook.
|
||||
|
||||
ALL_FEATURES=("idmap")
|
||||
ROOT="$(readlink -f "$(dirname "${BASH_SOURCE}")/..")"
|
||||
|
||||
# FEATURE: Opportunistic new{uid,gid}map support, allowing a rootless container
|
||||
# to be set up with the usage of helper setuid binaries.
|
||||
|
||||
function enable_idmap() {
|
||||
export ROOTLESS_UIDMAP_START=100000 ROOTLESS_UIDMAP_LENGTH=65536
|
||||
export ROOTLESS_GIDMAP_START=200000 ROOTLESS_GIDMAP_LENGTH=65536
|
||||
|
||||
# Set up sub{uid,gid} mappings.
|
||||
[ -e /etc/subuid.tmp ] && mv /etc/subuid{.tmp,}
|
||||
( grep -v '^rootless' /etc/subuid ; echo "rootless:$ROOTLESS_UIDMAP_START:$ROOTLESS_UIDMAP_LENGTH" ) > /etc/subuid.tmp
|
||||
mv /etc/subuid{.tmp,}
|
||||
[ -e /etc/subgid.tmp ] && mv /etc/subgid{.tmp,}
|
||||
( grep -v '^rootless' /etc/subgid ; echo "rootless:$ROOTLESS_GIDMAP_START:$ROOTLESS_GIDMAP_LENGTH" ) > /etc/subgid.tmp
|
||||
mv /etc/subgid{.tmp,}
|
||||
|
||||
# Reactivate new{uid,gid}map helpers if applicable.
|
||||
[ -e /usr/bin/unused-newuidmap ] && mv /usr/bin/{unused-,}newuidmap
|
||||
[ -e /usr/bin/unused-newgidmap ] && mv /usr/bin/{unused-,}newgidmap
|
||||
}
|
||||
|
||||
function disable_idmap() {
|
||||
export ROOTLESS_UIDMAP_START ROOTLESS_UIDMAP_LENGTH
|
||||
export ROOTLESS_GIDMAP_START ROOTLESS_GIDMAP_LENGTH
|
||||
|
||||
# Deactivate sub{uid,gid} mappings.
|
||||
[ -e /etc/subuid ] && mv /etc/subuid{,.tmp}
|
||||
[ -e /etc/subgid ] && mv /etc/subgid{,.tmp}
|
||||
|
||||
# Deactivate new{uid,gid}map helpers. setuid is preserved with mv(1).
|
||||
[ -e /usr/bin/newuidmap ] && mv /usr/bin/{,unused-}newuidmap
|
||||
[ -e /usr/bin/newgidmap ] && mv /usr/bin/{,unused-}newgidmap
|
||||
}
|
||||
|
||||
# Create a powerset of $ALL_FEATURES (the set of all subsets of $ALL_FEATURES).
|
||||
# We test all of the possible combinations (as long as we don't add too many
|
||||
# feature knobs this shouldn't take too long -- but the number of tested
|
||||
# combinations is O(2^n)).
|
||||
function powerset() {
|
||||
eval printf '%s' $(printf '{,%s+}' "$@"):
|
||||
}
|
||||
features_powerset="$(powerset "${ALL_FEATURES[@]}")"
|
||||
|
||||
# Iterate over the powerset of all features.
|
||||
IFS=:
|
||||
for enabled_features in $features_powerset
|
||||
do
|
||||
idx="$(($idx+1))"
|
||||
echo "[$(printf '%.2d' "$idx")] run rootless tests ... (${enabled_features%%+})"
|
||||
|
||||
unset IFS
|
||||
for feature in "${ALL_FEATURES[@]}"
|
||||
do
|
||||
hook_func="disable_$feature"
|
||||
grep -E "(^|\+)$feature(\+|$)" <<<$enabled_features &>/dev/null && hook_func="enable_$feature"
|
||||
"$hook_func"
|
||||
done
|
||||
|
||||
# Run the test suite!
|
||||
set -e
|
||||
echo path: $PATH
|
||||
export ROOTLESS_FEATURES="$enabled_features"
|
||||
sudo -HE -u rootless PATH="$PATH" bats -t "$ROOT/tests/integration"
|
||||
set +e
|
||||
done
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
|
@ -35,6 +36,9 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We default to cgroupfs, and can only use systemd if the system is a
|
||||
// systemd box.
|
||||
cgroupManager := libcontainer.Cgroupfs
|
||||
if context.GlobalBool("systemd-cgroup") {
|
||||
if systemd.UseSystemd() {
|
||||
|
@ -43,11 +47,28 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
|
|||
return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available")
|
||||
}
|
||||
}
|
||||
if intelrdt.IsEnabled() {
|
||||
|
||||
intelRdtManager := libcontainer.IntelRdtFs
|
||||
return libcontainer.New(abs, cgroupManager, intelRdtManager, libcontainer.CriuPath(context.GlobalString("criu")))
|
||||
if !intelrdt.IsEnabled() {
|
||||
intelRdtManager = nil
|
||||
}
|
||||
return libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(context.GlobalString("criu")))
|
||||
|
||||
// We resolve the paths for {newuidmap,newgidmap} from the context of runc,
|
||||
// to avoid doing a path lookup in the nsexec context. TODO: The binary
|
||||
// names are not currently configurable.
|
||||
newuidmap, err := exec.LookPath("newuidmap")
|
||||
if err != nil {
|
||||
newuidmap = ""
|
||||
}
|
||||
newgidmap, err := exec.LookPath("newgidmap")
|
||||
if err != nil {
|
||||
newgidmap = ""
|
||||
}
|
||||
|
||||
return libcontainer.New(abs, cgroupManager, intelRdtManager,
|
||||
libcontainer.CriuPath(context.GlobalString("criu")),
|
||||
libcontainer.NewuidmapPath(newuidmap),
|
||||
libcontainer.NewgidmapPath(newgidmap))
|
||||
}
|
||||
|
||||
// getContainer returns the specified container instance by loading it from state
|
||||
|
|
Loading…
Reference in New Issue