Pārlūkot izejas kodu

Move userns support out of experimental into master

Adds the `--userns-remap` flag to the master build

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
Phil Estes 9 gadi atpakaļ
vecāks
revīzija
557c7cb888

+ 1 - 112
daemon/config_experimental.go

@@ -2,118 +2,7 @@
 
 package daemon
 
-import (
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/docker/docker/pkg/idtools"
-	flag "github.com/docker/docker/pkg/mflag"
-	"github.com/opencontainers/runc/libcontainer/user"
-)
+import flag "github.com/docker/docker/pkg/mflag"
 
 func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
-	cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
-}
-
-const (
-	defaultIDSpecifier string = "default"
-	defaultRemappedID  string = "dockremap"
-)
-
-// Parse the remapped root (user namespace) option, which can be one of:
-//   username            - valid username from /etc/passwd
-//   username:groupname  - valid username; valid groupname from /etc/group
-//   uid                 - 32-bit unsigned int valid Linux UID value
-//   uid:gid             - uid value; 32-bit unsigned int Linux GID value
-//
-//  If no groupname is specified, and a username is specified, an attempt
-//  will be made to lookup a gid for that username as a groupname
-//
-//  If names are used, they are verified to exist in passwd/group
-func parseRemappedRoot(usergrp string) (string, string, error) {
-
-	var (
-		userID, groupID     int
-		username, groupname string
-	)
-
-	idparts := strings.Split(usergrp, ":")
-	if len(idparts) > 2 {
-		return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp)
-	}
-
-	if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
-		// must be a uid; take it as valid
-		userID = int(uid)
-		luser, err := user.LookupUid(userID)
-		if err != nil {
-			return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
-		}
-		username = luser.Name
-		if len(idparts) == 1 {
-			// if the uid was numeric and no gid was specified, take the uid as the gid
-			groupID = userID
-			lgrp, err := user.LookupGid(groupID)
-			if err != nil {
-				return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
-			}
-			groupname = lgrp.Name
-		}
-	} else {
-		lookupName := idparts[0]
-		// special case: if the user specified "default", they want Docker to create or
-		// use (after creation) the "dockremap" user/group for root remapping
-		if lookupName == defaultIDSpecifier {
-			lookupName = defaultRemappedID
-		}
-		luser, err := user.LookupUser(lookupName)
-		if err != nil && idparts[0] != defaultIDSpecifier {
-			// error if the name requested isn't the special "dockremap" ID
-			return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
-		} else if err != nil {
-			// special case-- if the username == "default", then we have been asked
-			// to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
-			// ranges will be used for the user and group mappings in user namespaced containers
-			_, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID)
-			if err == nil {
-				return defaultRemappedID, defaultRemappedID, nil
-			}
-			return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err)
-		}
-		userID = luser.Uid
-		username = luser.Name
-		if len(idparts) == 1 {
-			// we only have a string username, and no group specified; look up gid from username as group
-			group, err := user.LookupGroup(lookupName)
-			if err != nil {
-				return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
-			}
-			groupID = group.Gid
-			groupname = group.Name
-		}
-	}
-
-	if len(idparts) == 2 {
-		// groupname or gid is separately specified and must be resolved
-		// to a unsigned 32-bit gid
-		if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
-			// must be a gid, take it as valid
-			groupID = int(gid)
-			lgrp, err := user.LookupGid(groupID)
-			if err != nil {
-				return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
-			}
-			groupname = lgrp.Name
-		} else {
-			// not a number; attempt a lookup
-			group, err := user.LookupGroup(idparts[1])
-			if err != nil {
-				return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err)
-			}
-			groupID = group.Gid
-			groupname = idparts[1]
-		}
-	}
-	return username, groupname, nil
 }

+ 1 - 0
daemon/config_unix.go

@@ -79,6 +79,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
 	cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header"))
 	cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
 	cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers"))
+	cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
 
 	config.attachExperimentalFlags(cmd, usageFn)
 }

+ 1 - 81
daemon/daemon_experimental.go

@@ -2,88 +2,8 @@
 
 package daemon
 
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"runtime"
-
-	"github.com/Sirupsen/logrus"
-	"github.com/docker/docker/pkg/idtools"
-	"github.com/docker/engine-api/types/container"
-)
-
-func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
-	if runtime.GOOS != "linux" && config.RemappedRoot != "" {
-		return nil, nil, fmt.Errorf("User namespaces are only supported on Linux")
-	}
-
-	// if the daemon was started with remapped root option, parse
-	// the config option to the int uid,gid values
-	var (
-		uidMaps, gidMaps []idtools.IDMap
-	)
-	if config.RemappedRoot != "" {
-		username, groupname, err := parseRemappedRoot(config.RemappedRoot)
-		if err != nil {
-			return nil, nil, err
-		}
-		if username == "root" {
-			// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
-			// effectively
-			logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
-			return uidMaps, gidMaps, nil
-		}
-		logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname)
-		// update remapped root setting now that we have resolved them to actual names
-		config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
-
-		uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname)
-		if err != nil {
-			return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err)
-		}
-	}
-	return uidMaps, gidMaps, nil
-}
-
-func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
-	config.Root = rootDir
-	// the docker root metadata directory needs to have execute permissions for all users (o+x)
-	// so that syscalls executing as non-root, operating on subdirectories of the graph root
-	// (e.g. mounted layers of a container) can traverse this path.
-	// The user namespace support will create subdirectories for the remapped root host uid:gid
-	// pair owned by that same uid:gid pair for proper write access to those needed metadata and
-	// layer content subtrees.
-	if _, err := os.Stat(rootDir); err == nil {
-		// root current exists; verify the access bits are correct by setting them
-		if err = os.Chmod(rootDir, 0701); err != nil {
-			return err
-		}
-	} else if os.IsNotExist(err) {
-		// no root exists yet, create it 0701 with root:root ownership
-		if err := os.MkdirAll(rootDir, 0701); err != nil {
-			return err
-		}
-	}
-
-	// if user namespaces are enabled we will create a subtree underneath the specified root
-	// with any/all specified remapped root uid/gid options on the daemon creating
-	// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
-	// `chdir()` to work for containers namespaced to that uid/gid)
-	if config.RemappedRoot != "" {
-		config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID))
-		logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
-		// Create the root directory if it doesn't exists
-		if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil {
-			return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
-		}
-	}
-	return nil
-}
+import "github.com/docker/engine-api/types/container"
 
 func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
-	if hostConfig.Privileged && daemon.configStore.RemappedRoot != "" {
-		return nil, fmt.Errorf("Privileged mode is incompatible with user namespace mappings")
-	}
 	return nil, nil
 }

+ 1 - 20
daemon/daemon_stub.go

@@ -2,26 +2,7 @@
 
 package daemon
 
-import (
-	"os"
-
-	"github.com/docker/docker/pkg/idtools"
-	"github.com/docker/docker/pkg/system"
-	"github.com/docker/engine-api/types/container"
-)
-
-func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
-	return nil, nil, nil
-}
-
-func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
-	config.Root = rootDir
-	// Create the root directory if it doesn't exists
-	if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
-		return err
-	}
-	return nil
-}
+import "github.com/docker/engine-api/types/container"
 
 func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
 	return nil, nil

+ 173 - 0
daemon/daemon_unix.go

@@ -7,6 +7,7 @@ import (
 	"net"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strconv"
 	"strings"
 	"syscall"
@@ -33,6 +34,7 @@ import (
 	"github.com/docker/libnetwork/types"
 	blkiodev "github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/opencontainers/runc/libcontainer/label"
+	"github.com/opencontainers/runc/libcontainer/user"
 )
 
 const (
@@ -42,6 +44,9 @@ const (
 	platformSupported = true
 	// It's not kernel limit, we want this 4M limit to supply a reasonable functional container
 	linuxMinMemory = 4194304
+	// constants for remapped root settings
+	defaultIDSpecifier string = "default"
+	defaultRemappedID  string = "dockremap"
 )
 
 func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
@@ -375,6 +380,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 		warnings = append(warnings, "IPv4 forwarding is disabled. Networking will not work.")
 		logrus.Warnf("IPv4 forwarding is disabled. Networking will not work")
 	}
+	if hostConfig.Privileged && daemon.configStore.RemappedRoot != "" {
+		return warnings, fmt.Errorf("Privileged mode is incompatible with user namespace mappings")
+	}
 	return warnings, nil
 }
 
@@ -674,6 +682,171 @@ func setupInitLayer(initLayer string, rootUID, rootGID int) error {
 	return nil
 }
 
+// Parse the remapped root (user namespace) option, which can be one of:
+//   username            - valid username from /etc/passwd
+//   username:groupname  - valid username; valid groupname from /etc/group
+//   uid                 - 32-bit unsigned int valid Linux UID value
+//   uid:gid             - uid value; 32-bit unsigned int Linux GID value
+//
+//  If no groupname is specified, and a username is specified, an attempt
+//  will be made to lookup a gid for that username as a groupname
+//
+//  If names are used, they are verified to exist in passwd/group
+func parseRemappedRoot(usergrp string) (string, string, error) {
+
+	var (
+		userID, groupID     int
+		username, groupname string
+	)
+
+	idparts := strings.Split(usergrp, ":")
+	if len(idparts) > 2 {
+		return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp)
+	}
+
+	if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
+		// must be a uid; take it as valid
+		userID = int(uid)
+		luser, err := user.LookupUid(userID)
+		if err != nil {
+			return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
+		}
+		username = luser.Name
+		if len(idparts) == 1 {
+			// if the uid was numeric and no gid was specified, take the uid as the gid
+			groupID = userID
+			lgrp, err := user.LookupGid(groupID)
+			if err != nil {
+				return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
+			}
+			groupname = lgrp.Name
+		}
+	} else {
+		lookupName := idparts[0]
+		// special case: if the user specified "default", they want Docker to create or
+		// use (after creation) the "dockremap" user/group for root remapping
+		if lookupName == defaultIDSpecifier {
+			lookupName = defaultRemappedID
+		}
+		luser, err := user.LookupUser(lookupName)
+		if err != nil && idparts[0] != defaultIDSpecifier {
+			// error if the name requested isn't the special "dockremap" ID
+			return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
+		} else if err != nil {
+			// special case-- if the username == "default", then we have been asked
+			// to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
+			// ranges will be used for the user and group mappings in user namespaced containers
+			_, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID)
+			if err == nil {
+				return defaultRemappedID, defaultRemappedID, nil
+			}
+			return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err)
+		}
+		userID = luser.Uid
+		username = luser.Name
+		if len(idparts) == 1 {
+			// we only have a string username, and no group specified; look up gid from username as group
+			group, err := user.LookupGroup(lookupName)
+			if err != nil {
+				return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
+			}
+			groupID = group.Gid
+			groupname = group.Name
+		}
+	}
+
+	if len(idparts) == 2 {
+		// groupname or gid is separately specified and must be resolved
+		// to a unsigned 32-bit gid
+		if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
+			// must be a gid, take it as valid
+			groupID = int(gid)
+			lgrp, err := user.LookupGid(groupID)
+			if err != nil {
+				return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
+			}
+			groupname = lgrp.Name
+		} else {
+			// not a number; attempt a lookup
+			group, err := user.LookupGroup(idparts[1])
+			if err != nil {
+				return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err)
+			}
+			groupID = group.Gid
+			groupname = idparts[1]
+		}
+	}
+	return username, groupname, nil
+}
+
+func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
+	if runtime.GOOS != "linux" && config.RemappedRoot != "" {
+		return nil, nil, fmt.Errorf("User namespaces are only supported on Linux")
+	}
+
+	// if the daemon was started with remapped root option, parse
+	// the config option to the int uid,gid values
+	var (
+		uidMaps, gidMaps []idtools.IDMap
+	)
+	if config.RemappedRoot != "" {
+		username, groupname, err := parseRemappedRoot(config.RemappedRoot)
+		if err != nil {
+			return nil, nil, err
+		}
+		if username == "root" {
+			// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
+			// effectively
+			logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
+			return uidMaps, gidMaps, nil
+		}
+		logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname)
+		// update remapped root setting now that we have resolved them to actual names
+		config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
+
+		uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname)
+		if err != nil {
+			return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err)
+		}
+	}
+	return uidMaps, gidMaps, nil
+}
+
+func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
+	config.Root = rootDir
+	// the docker root metadata directory needs to have execute permissions for all users (o+x)
+	// so that syscalls executing as non-root, operating on subdirectories of the graph root
+	// (e.g. mounted layers of a container) can traverse this path.
+	// The user namespace support will create subdirectories for the remapped root host uid:gid
+	// pair owned by that same uid:gid pair for proper write access to those needed metadata and
+	// layer content subtrees.
+	if _, err := os.Stat(rootDir); err == nil {
+		// root current exists; verify the access bits are correct by setting them
+		if err = os.Chmod(rootDir, 0701); err != nil {
+			return err
+		}
+	} else if os.IsNotExist(err) {
+		// no root exists yet, create it 0701 with root:root ownership
+		if err := os.MkdirAll(rootDir, 0701); err != nil {
+			return err
+		}
+	}
+
+	// if user namespaces are enabled we will create a subtree underneath the specified root
+	// with any/all specified remapped root uid/gid options on the daemon creating
+	// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
+	// `chdir()` to work for containers namespaced to that uid/gid)
+	if config.RemappedRoot != "" {
+		config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID))
+		logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
+		// Create the root directory if it doesn't exists
+		if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil {
+			return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
+		}
+	}
+	return nil
+}
+
 // registerLinks writes the links to a file.
 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
 	if hostConfig == nil || hostConfig.Links == nil {

+ 15 - 0
daemon/daemon_windows.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"os"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -18,6 +19,7 @@ import (
 	containertypes "github.com/docker/engine-api/types/container"
 	// register the windows graph driver
 	"github.com/docker/docker/daemon/graphdriver/windows"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/libnetwork"
 	blkiodev "github.com/opencontainers/runc/libcontainer/configs"
@@ -135,6 +137,19 @@ func (daemon *Daemon) cleanupMounts() error {
 	return nil
 }
 
+func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
+	return nil, nil, nil
+}
+
+func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
+	config.Root = rootDir
+	// Create the root directory if it doesn't exists
+	if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
+		return err
+	}
+	return nil
+}
+
 // conditionalMountOnStart is a platform specific helper function during the
 // container start to call mount.
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {

+ 1 - 1
hack/make.sh

@@ -99,7 +99,7 @@ if [ ! "$GOPATH" ]; then
 	exit 1
 fi
 
-if [ "$DOCKER_EXPERIMENTAL" ] || [ "$DOCKER_REMAP_ROOT" ]; then
+if [ "$DOCKER_EXPERIMENTAL" ]; then
 	echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
 	echo >&2
 	DOCKER_BUILDTAGS+=" experimental pkcs11"

+ 6 - 2
integration-cli/docker_api_containers_test.go

@@ -652,10 +652,14 @@ func (s *DockerSuite) TestContainerApiCreateWithDomainName(c *check.C) {
 	c.Assert(containerJSON.Config.Domainname, checker.Equals, domainName, check.Commentf("Mismatched Domainname"))
 }
 
-func (s *DockerSuite) TestContainerApiCreateNetworkMode(c *check.C) {
+func (s *DockerSuite) TestContainerApiCreateBridgeNetworkMode(c *check.C) {
 	testRequires(c, DaemonIsLinux)
-	UtilCreateNetworkMode(c, "host")
 	UtilCreateNetworkMode(c, "bridge")
+}
+
+func (s *DockerSuite) TestContainerApiCreateOtherNetworkModes(c *check.C) {
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
+	UtilCreateNetworkMode(c, "host")
 	UtilCreateNetworkMode(c, "container:web1")
 }