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)
| ... | ... |
@@ -2,118 +2,7 @@ |
| 2 | 2 |
|
| 3 | 3 |
package daemon |
| 4 | 4 |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- "strconv" |
|
| 8 |
- "strings" |
|
| 9 |
- |
|
| 10 |
- "github.com/docker/docker/pkg/idtools" |
|
| 11 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 12 |
- "github.com/opencontainers/runc/libcontainer/user" |
|
| 13 |
-) |
|
| 5 |
+import flag "github.com/docker/docker/pkg/mflag" |
|
| 14 | 6 |
|
| 15 | 7 |
func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
| 16 |
- cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
|
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-const ( |
|
| 20 |
- defaultIDSpecifier string = "default" |
|
| 21 |
- defaultRemappedID string = "dockremap" |
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-// Parse the remapped root (user namespace) option, which can be one of: |
|
| 25 |
-// username - valid username from /etc/passwd |
|
| 26 |
-// username:groupname - valid username; valid groupname from /etc/group |
|
| 27 |
-// uid - 32-bit unsigned int valid Linux UID value |
|
| 28 |
-// uid:gid - uid value; 32-bit unsigned int Linux GID value |
|
| 29 |
-// |
|
| 30 |
-// If no groupname is specified, and a username is specified, an attempt |
|
| 31 |
-// will be made to lookup a gid for that username as a groupname |
|
| 32 |
-// |
|
| 33 |
-// If names are used, they are verified to exist in passwd/group |
|
| 34 |
-func parseRemappedRoot(usergrp string) (string, string, error) {
|
|
| 35 |
- |
|
| 36 |
- var ( |
|
| 37 |
- userID, groupID int |
|
| 38 |
- username, groupname string |
|
| 39 |
- ) |
|
| 40 |
- |
|
| 41 |
- idparts := strings.Split(usergrp, ":") |
|
| 42 |
- if len(idparts) > 2 {
|
|
| 43 |
- return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp)
|
|
| 44 |
- } |
|
| 45 |
- |
|
| 46 |
- if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
|
|
| 47 |
- // must be a uid; take it as valid |
|
| 48 |
- userID = int(uid) |
|
| 49 |
- luser, err := user.LookupUid(userID) |
|
| 50 |
- if err != nil {
|
|
| 51 |
- return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
|
|
| 52 |
- } |
|
| 53 |
- username = luser.Name |
|
| 54 |
- if len(idparts) == 1 {
|
|
| 55 |
- // if the uid was numeric and no gid was specified, take the uid as the gid |
|
| 56 |
- groupID = userID |
|
| 57 |
- lgrp, err := user.LookupGid(groupID) |
|
| 58 |
- if err != nil {
|
|
| 59 |
- return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
|
|
| 60 |
- } |
|
| 61 |
- groupname = lgrp.Name |
|
| 62 |
- } |
|
| 63 |
- } else {
|
|
| 64 |
- lookupName := idparts[0] |
|
| 65 |
- // special case: if the user specified "default", they want Docker to create or |
|
| 66 |
- // use (after creation) the "dockremap" user/group for root remapping |
|
| 67 |
- if lookupName == defaultIDSpecifier {
|
|
| 68 |
- lookupName = defaultRemappedID |
|
| 69 |
- } |
|
| 70 |
- luser, err := user.LookupUser(lookupName) |
|
| 71 |
- if err != nil && idparts[0] != defaultIDSpecifier {
|
|
| 72 |
- // error if the name requested isn't the special "dockremap" ID |
|
| 73 |
- return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
|
|
| 74 |
- } else if err != nil {
|
|
| 75 |
- // special case-- if the username == "default", then we have been asked |
|
| 76 |
- // to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
|
|
| 77 |
- // ranges will be used for the user and group mappings in user namespaced containers |
|
| 78 |
- _, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID) |
|
| 79 |
- if err == nil {
|
|
| 80 |
- return defaultRemappedID, defaultRemappedID, nil |
|
| 81 |
- } |
|
| 82 |
- return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err)
|
|
| 83 |
- } |
|
| 84 |
- userID = luser.Uid |
|
| 85 |
- username = luser.Name |
|
| 86 |
- if len(idparts) == 1 {
|
|
| 87 |
- // we only have a string username, and no group specified; look up gid from username as group |
|
| 88 |
- group, err := user.LookupGroup(lookupName) |
|
| 89 |
- if err != nil {
|
|
| 90 |
- return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
|
|
| 91 |
- } |
|
| 92 |
- groupID = group.Gid |
|
| 93 |
- groupname = group.Name |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- |
|
| 97 |
- if len(idparts) == 2 {
|
|
| 98 |
- // groupname or gid is separately specified and must be resolved |
|
| 99 |
- // to a unsigned 32-bit gid |
|
| 100 |
- if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
|
|
| 101 |
- // must be a gid, take it as valid |
|
| 102 |
- groupID = int(gid) |
|
| 103 |
- lgrp, err := user.LookupGid(groupID) |
|
| 104 |
- if err != nil {
|
|
| 105 |
- return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
|
|
| 106 |
- } |
|
| 107 |
- groupname = lgrp.Name |
|
| 108 |
- } else {
|
|
| 109 |
- // not a number; attempt a lookup |
|
| 110 |
- group, err := user.LookupGroup(idparts[1]) |
|
| 111 |
- if err != nil {
|
|
| 112 |
- return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err)
|
|
| 113 |
- } |
|
| 114 |
- groupID = group.Gid |
|
| 115 |
- groupname = idparts[1] |
|
| 116 |
- } |
|
| 117 |
- } |
|
| 118 |
- return username, groupname, nil |
|
| 119 | 8 |
} |
| ... | ... |
@@ -79,6 +79,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin |
| 79 | 79 |
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"))
|
| 80 | 80 |
cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
|
| 81 | 81 |
cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers"))
|
| 82 |
+ cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
|
|
| 82 | 83 |
|
| 83 | 84 |
config.attachExperimentalFlags(cmd, usageFn) |
| 84 | 85 |
} |
| ... | ... |
@@ -2,88 +2,8 @@ |
| 2 | 2 |
|
| 3 | 3 |
package daemon |
| 4 | 4 |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- "os" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "runtime" |
|
| 10 |
- |
|
| 11 |
- "github.com/Sirupsen/logrus" |
|
| 12 |
- "github.com/docker/docker/pkg/idtools" |
|
| 13 |
- "github.com/docker/engine-api/types/container" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
| 17 |
- if runtime.GOOS != "linux" && config.RemappedRoot != "" {
|
|
| 18 |
- return nil, nil, fmt.Errorf("User namespaces are only supported on Linux")
|
|
| 19 |
- } |
|
| 20 |
- |
|
| 21 |
- // if the daemon was started with remapped root option, parse |
|
| 22 |
- // the config option to the int uid,gid values |
|
| 23 |
- var ( |
|
| 24 |
- uidMaps, gidMaps []idtools.IDMap |
|
| 25 |
- ) |
|
| 26 |
- if config.RemappedRoot != "" {
|
|
| 27 |
- username, groupname, err := parseRemappedRoot(config.RemappedRoot) |
|
| 28 |
- if err != nil {
|
|
| 29 |
- return nil, nil, err |
|
| 30 |
- } |
|
| 31 |
- if username == "root" {
|
|
| 32 |
- // Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op |
|
| 33 |
- // effectively |
|
| 34 |
- logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
|
|
| 35 |
- return uidMaps, gidMaps, nil |
|
| 36 |
- } |
|
| 37 |
- logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname)
|
|
| 38 |
- // update remapped root setting now that we have resolved them to actual names |
|
| 39 |
- config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
|
|
| 40 |
- |
|
| 41 |
- uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname) |
|
| 42 |
- if err != nil {
|
|
| 43 |
- return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err)
|
|
| 44 |
- } |
|
| 45 |
- } |
|
| 46 |
- return uidMaps, gidMaps, nil |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
|
| 50 |
- config.Root = rootDir |
|
| 51 |
- // the docker root metadata directory needs to have execute permissions for all users (o+x) |
|
| 52 |
- // so that syscalls executing as non-root, operating on subdirectories of the graph root |
|
| 53 |
- // (e.g. mounted layers of a container) can traverse this path. |
|
| 54 |
- // The user namespace support will create subdirectories for the remapped root host uid:gid |
|
| 55 |
- // pair owned by that same uid:gid pair for proper write access to those needed metadata and |
|
| 56 |
- // layer content subtrees. |
|
| 57 |
- if _, err := os.Stat(rootDir); err == nil {
|
|
| 58 |
- // root current exists; verify the access bits are correct by setting them |
|
| 59 |
- if err = os.Chmod(rootDir, 0701); err != nil {
|
|
| 60 |
- return err |
|
| 61 |
- } |
|
| 62 |
- } else if os.IsNotExist(err) {
|
|
| 63 |
- // no root exists yet, create it 0701 with root:root ownership |
|
| 64 |
- if err := os.MkdirAll(rootDir, 0701); err != nil {
|
|
| 65 |
- return err |
|
| 66 |
- } |
|
| 67 |
- } |
|
| 68 |
- |
|
| 69 |
- // if user namespaces are enabled we will create a subtree underneath the specified root |
|
| 70 |
- // with any/all specified remapped root uid/gid options on the daemon creating |
|
| 71 |
- // a new subdirectory with ownership set to the remapped uid/gid (so as to allow |
|
| 72 |
- // `chdir()` to work for containers namespaced to that uid/gid) |
|
| 73 |
- if config.RemappedRoot != "" {
|
|
| 74 |
- config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID))
|
|
| 75 |
- logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
|
|
| 76 |
- // Create the root directory if it doesn't exists |
|
| 77 |
- if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil {
|
|
| 78 |
- return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
|
|
| 79 |
- } |
|
| 80 |
- } |
|
| 81 |
- return nil |
|
| 82 |
-} |
|
| 5 |
+import "github.com/docker/engine-api/types/container" |
|
| 83 | 6 |
|
| 84 | 7 |
func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
|
| 85 |
- if hostConfig.Privileged && daemon.configStore.RemappedRoot != "" {
|
|
| 86 |
- return nil, fmt.Errorf("Privileged mode is incompatible with user namespace mappings")
|
|
| 87 |
- } |
|
| 88 | 8 |
return nil, nil |
| 89 | 9 |
} |
| ... | ... |
@@ -2,26 +2,7 @@ |
| 2 | 2 |
|
| 3 | 3 |
package daemon |
| 4 | 4 |
|
| 5 |
-import ( |
|
| 6 |
- "os" |
|
| 7 |
- |
|
| 8 |
- "github.com/docker/docker/pkg/idtools" |
|
| 9 |
- "github.com/docker/docker/pkg/system" |
|
| 10 |
- "github.com/docker/engine-api/types/container" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
| 14 |
- return nil, nil, nil |
|
| 15 |
-} |
|
| 16 |
- |
|
| 17 |
-func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
|
| 18 |
- config.Root = rootDir |
|
| 19 |
- // Create the root directory if it doesn't exists |
|
| 20 |
- if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
|
|
| 21 |
- return err |
|
| 22 |
- } |
|
| 23 |
- return nil |
|
| 24 |
-} |
|
| 5 |
+import "github.com/docker/engine-api/types/container" |
|
| 25 | 6 |
|
| 26 | 7 |
func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
|
| 27 | 8 |
return nil, nil |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"net" |
| 8 | 8 |
"os" |
| 9 | 9 |
"path/filepath" |
| 10 |
+ "runtime" |
|
| 10 | 11 |
"strconv" |
| 11 | 12 |
"strings" |
| 12 | 13 |
"syscall" |
| ... | ... |
@@ -33,6 +34,7 @@ import ( |
| 33 | 33 |
"github.com/docker/libnetwork/types" |
| 34 | 34 |
blkiodev "github.com/opencontainers/runc/libcontainer/configs" |
| 35 | 35 |
"github.com/opencontainers/runc/libcontainer/label" |
| 36 |
+ "github.com/opencontainers/runc/libcontainer/user" |
|
| 36 | 37 |
) |
| 37 | 38 |
|
| 38 | 39 |
const ( |
| ... | ... |
@@ -42,6 +44,9 @@ const ( |
| 42 | 42 |
platformSupported = true |
| 43 | 43 |
// It's not kernel limit, we want this 4M limit to supply a reasonable functional container |
| 44 | 44 |
linuxMinMemory = 4194304 |
| 45 |
+ // constants for remapped root settings |
|
| 46 |
+ defaultIDSpecifier string = "default" |
|
| 47 |
+ defaultRemappedID string = "dockremap" |
|
| 45 | 48 |
) |
| 46 | 49 |
|
| 47 | 50 |
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
|
| ... | ... |
@@ -375,6 +380,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
| 375 | 375 |
warnings = append(warnings, "IPv4 forwarding is disabled. Networking will not work.") |
| 376 | 376 |
logrus.Warnf("IPv4 forwarding is disabled. Networking will not work")
|
| 377 | 377 |
} |
| 378 |
+ if hostConfig.Privileged && daemon.configStore.RemappedRoot != "" {
|
|
| 379 |
+ return warnings, fmt.Errorf("Privileged mode is incompatible with user namespace mappings")
|
|
| 380 |
+ } |
|
| 378 | 381 |
return warnings, nil |
| 379 | 382 |
} |
| 380 | 383 |
|
| ... | ... |
@@ -674,6 +682,171 @@ func setupInitLayer(initLayer string, rootUID, rootGID int) error {
|
| 674 | 674 |
return nil |
| 675 | 675 |
} |
| 676 | 676 |
|
| 677 |
+// Parse the remapped root (user namespace) option, which can be one of: |
|
| 678 |
+// username - valid username from /etc/passwd |
|
| 679 |
+// username:groupname - valid username; valid groupname from /etc/group |
|
| 680 |
+// uid - 32-bit unsigned int valid Linux UID value |
|
| 681 |
+// uid:gid - uid value; 32-bit unsigned int Linux GID value |
|
| 682 |
+// |
|
| 683 |
+// If no groupname is specified, and a username is specified, an attempt |
|
| 684 |
+// will be made to lookup a gid for that username as a groupname |
|
| 685 |
+// |
|
| 686 |
+// If names are used, they are verified to exist in passwd/group |
|
| 687 |
+func parseRemappedRoot(usergrp string) (string, string, error) {
|
|
| 688 |
+ |
|
| 689 |
+ var ( |
|
| 690 |
+ userID, groupID int |
|
| 691 |
+ username, groupname string |
|
| 692 |
+ ) |
|
| 693 |
+ |
|
| 694 |
+ idparts := strings.Split(usergrp, ":") |
|
| 695 |
+ if len(idparts) > 2 {
|
|
| 696 |
+ return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp)
|
|
| 697 |
+ } |
|
| 698 |
+ |
|
| 699 |
+ if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
|
|
| 700 |
+ // must be a uid; take it as valid |
|
| 701 |
+ userID = int(uid) |
|
| 702 |
+ luser, err := user.LookupUid(userID) |
|
| 703 |
+ if err != nil {
|
|
| 704 |
+ return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
|
|
| 705 |
+ } |
|
| 706 |
+ username = luser.Name |
|
| 707 |
+ if len(idparts) == 1 {
|
|
| 708 |
+ // if the uid was numeric and no gid was specified, take the uid as the gid |
|
| 709 |
+ groupID = userID |
|
| 710 |
+ lgrp, err := user.LookupGid(groupID) |
|
| 711 |
+ if err != nil {
|
|
| 712 |
+ return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
|
|
| 713 |
+ } |
|
| 714 |
+ groupname = lgrp.Name |
|
| 715 |
+ } |
|
| 716 |
+ } else {
|
|
| 717 |
+ lookupName := idparts[0] |
|
| 718 |
+ // special case: if the user specified "default", they want Docker to create or |
|
| 719 |
+ // use (after creation) the "dockremap" user/group for root remapping |
|
| 720 |
+ if lookupName == defaultIDSpecifier {
|
|
| 721 |
+ lookupName = defaultRemappedID |
|
| 722 |
+ } |
|
| 723 |
+ luser, err := user.LookupUser(lookupName) |
|
| 724 |
+ if err != nil && idparts[0] != defaultIDSpecifier {
|
|
| 725 |
+ // error if the name requested isn't the special "dockremap" ID |
|
| 726 |
+ return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
|
|
| 727 |
+ } else if err != nil {
|
|
| 728 |
+ // special case-- if the username == "default", then we have been asked |
|
| 729 |
+ // to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
|
|
| 730 |
+ // ranges will be used for the user and group mappings in user namespaced containers |
|
| 731 |
+ _, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID) |
|
| 732 |
+ if err == nil {
|
|
| 733 |
+ return defaultRemappedID, defaultRemappedID, nil |
|
| 734 |
+ } |
|
| 735 |
+ return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err)
|
|
| 736 |
+ } |
|
| 737 |
+ userID = luser.Uid |
|
| 738 |
+ username = luser.Name |
|
| 739 |
+ if len(idparts) == 1 {
|
|
| 740 |
+ // we only have a string username, and no group specified; look up gid from username as group |
|
| 741 |
+ group, err := user.LookupGroup(lookupName) |
|
| 742 |
+ if err != nil {
|
|
| 743 |
+ return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
|
|
| 744 |
+ } |
|
| 745 |
+ groupID = group.Gid |
|
| 746 |
+ groupname = group.Name |
|
| 747 |
+ } |
|
| 748 |
+ } |
|
| 749 |
+ |
|
| 750 |
+ if len(idparts) == 2 {
|
|
| 751 |
+ // groupname or gid is separately specified and must be resolved |
|
| 752 |
+ // to a unsigned 32-bit gid |
|
| 753 |
+ if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
|
|
| 754 |
+ // must be a gid, take it as valid |
|
| 755 |
+ groupID = int(gid) |
|
| 756 |
+ lgrp, err := user.LookupGid(groupID) |
|
| 757 |
+ if err != nil {
|
|
| 758 |
+ return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
|
|
| 759 |
+ } |
|
| 760 |
+ groupname = lgrp.Name |
|
| 761 |
+ } else {
|
|
| 762 |
+ // not a number; attempt a lookup |
|
| 763 |
+ group, err := user.LookupGroup(idparts[1]) |
|
| 764 |
+ if err != nil {
|
|
| 765 |
+ return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err)
|
|
| 766 |
+ } |
|
| 767 |
+ groupID = group.Gid |
|
| 768 |
+ groupname = idparts[1] |
|
| 769 |
+ } |
|
| 770 |
+ } |
|
| 771 |
+ return username, groupname, nil |
|
| 772 |
+} |
|
| 773 |
+ |
|
| 774 |
+func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
| 775 |
+ if runtime.GOOS != "linux" && config.RemappedRoot != "" {
|
|
| 776 |
+ return nil, nil, fmt.Errorf("User namespaces are only supported on Linux")
|
|
| 777 |
+ } |
|
| 778 |
+ |
|
| 779 |
+ // if the daemon was started with remapped root option, parse |
|
| 780 |
+ // the config option to the int uid,gid values |
|
| 781 |
+ var ( |
|
| 782 |
+ uidMaps, gidMaps []idtools.IDMap |
|
| 783 |
+ ) |
|
| 784 |
+ if config.RemappedRoot != "" {
|
|
| 785 |
+ username, groupname, err := parseRemappedRoot(config.RemappedRoot) |
|
| 786 |
+ if err != nil {
|
|
| 787 |
+ return nil, nil, err |
|
| 788 |
+ } |
|
| 789 |
+ if username == "root" {
|
|
| 790 |
+ // Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op |
|
| 791 |
+ // effectively |
|
| 792 |
+ logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
|
|
| 793 |
+ return uidMaps, gidMaps, nil |
|
| 794 |
+ } |
|
| 795 |
+ logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname)
|
|
| 796 |
+ // update remapped root setting now that we have resolved them to actual names |
|
| 797 |
+ config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
|
|
| 798 |
+ |
|
| 799 |
+ uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname) |
|
| 800 |
+ if err != nil {
|
|
| 801 |
+ return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err)
|
|
| 802 |
+ } |
|
| 803 |
+ } |
|
| 804 |
+ return uidMaps, gidMaps, nil |
|
| 805 |
+} |
|
| 806 |
+ |
|
| 807 |
+func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
|
| 808 |
+ config.Root = rootDir |
|
| 809 |
+ // the docker root metadata directory needs to have execute permissions for all users (o+x) |
|
| 810 |
+ // so that syscalls executing as non-root, operating on subdirectories of the graph root |
|
| 811 |
+ // (e.g. mounted layers of a container) can traverse this path. |
|
| 812 |
+ // The user namespace support will create subdirectories for the remapped root host uid:gid |
|
| 813 |
+ // pair owned by that same uid:gid pair for proper write access to those needed metadata and |
|
| 814 |
+ // layer content subtrees. |
|
| 815 |
+ if _, err := os.Stat(rootDir); err == nil {
|
|
| 816 |
+ // root current exists; verify the access bits are correct by setting them |
|
| 817 |
+ if err = os.Chmod(rootDir, 0701); err != nil {
|
|
| 818 |
+ return err |
|
| 819 |
+ } |
|
| 820 |
+ } else if os.IsNotExist(err) {
|
|
| 821 |
+ // no root exists yet, create it 0701 with root:root ownership |
|
| 822 |
+ if err := os.MkdirAll(rootDir, 0701); err != nil {
|
|
| 823 |
+ return err |
|
| 824 |
+ } |
|
| 825 |
+ } |
|
| 826 |
+ |
|
| 827 |
+ // if user namespaces are enabled we will create a subtree underneath the specified root |
|
| 828 |
+ // with any/all specified remapped root uid/gid options on the daemon creating |
|
| 829 |
+ // a new subdirectory with ownership set to the remapped uid/gid (so as to allow |
|
| 830 |
+ // `chdir()` to work for containers namespaced to that uid/gid) |
|
| 831 |
+ if config.RemappedRoot != "" {
|
|
| 832 |
+ config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID))
|
|
| 833 |
+ logrus.Debugf("Creating user namespaced daemon root: %s", config.Root)
|
|
| 834 |
+ // Create the root directory if it doesn't exists |
|
| 835 |
+ if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil {
|
|
| 836 |
+ return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
|
|
| 837 |
+ } |
|
| 838 |
+ } |
|
| 839 |
+ return nil |
|
| 840 |
+} |
|
| 841 |
+ |
|
| 677 | 842 |
// registerLinks writes the links to a file. |
| 678 | 843 |
func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
|
| 679 | 844 |
if hostConfig == nil || hostConfig.Links == nil {
|
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"errors" |
| 6 | 6 |
"fmt" |
| 7 |
+ "os" |
|
| 7 | 8 |
"path/filepath" |
| 8 | 9 |
"runtime" |
| 9 | 10 |
"strings" |
| ... | ... |
@@ -18,6 +19,7 @@ import ( |
| 18 | 18 |
containertypes "github.com/docker/engine-api/types/container" |
| 19 | 19 |
// register the windows graph driver |
| 20 | 20 |
"github.com/docker/docker/daemon/graphdriver/windows" |
| 21 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 21 | 22 |
"github.com/docker/docker/pkg/system" |
| 22 | 23 |
"github.com/docker/libnetwork" |
| 23 | 24 |
blkiodev "github.com/opencontainers/runc/libcontainer/configs" |
| ... | ... |
@@ -135,6 +137,19 @@ func (daemon *Daemon) cleanupMounts() error {
|
| 135 | 135 |
return nil |
| 136 | 136 |
} |
| 137 | 137 |
|
| 138 |
+func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) {
|
|
| 139 |
+ return nil, nil, nil |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error {
|
|
| 143 |
+ config.Root = rootDir |
|
| 144 |
+ // Create the root directory if it doesn't exists |
|
| 145 |
+ if err := system.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) {
|
|
| 146 |
+ return err |
|
| 147 |
+ } |
|
| 148 |
+ return nil |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 138 | 151 |
// conditionalMountOnStart is a platform specific helper function during the |
| 139 | 152 |
// container start to call mount. |
| 140 | 153 |
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
|
| ... | ... |
@@ -99,7 +99,7 @@ if [ ! "$GOPATH" ]; then |
| 99 | 99 |
exit 1 |
| 100 | 100 |
fi |
| 101 | 101 |
|
| 102 |
-if [ "$DOCKER_EXPERIMENTAL" ] || [ "$DOCKER_REMAP_ROOT" ]; then |
|
| 102 |
+if [ "$DOCKER_EXPERIMENTAL" ]; then |
|
| 103 | 103 |
echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features' |
| 104 | 104 |
echo >&2 |
| 105 | 105 |
DOCKER_BUILDTAGS+=" experimental pkcs11" |
| ... | ... |
@@ -652,10 +652,14 @@ func (s *DockerSuite) TestContainerApiCreateWithDomainName(c *check.C) {
|
| 652 | 652 |
c.Assert(containerJSON.Config.Domainname, checker.Equals, domainName, check.Commentf("Mismatched Domainname"))
|
| 653 | 653 |
} |
| 654 | 654 |
|
| 655 |
-func (s *DockerSuite) TestContainerApiCreateNetworkMode(c *check.C) {
|
|
| 655 |
+func (s *DockerSuite) TestContainerApiCreateBridgeNetworkMode(c *check.C) {
|
|
| 656 | 656 |
testRequires(c, DaemonIsLinux) |
| 657 |
- UtilCreateNetworkMode(c, "host") |
|
| 658 | 657 |
UtilCreateNetworkMode(c, "bridge") |
| 658 |
+} |
|
| 659 |
+ |
|
| 660 |
+func (s *DockerSuite) TestContainerApiCreateOtherNetworkModes(c *check.C) {
|
|
| 661 |
+ testRequires(c, DaemonIsLinux, NotUserNamespace) |
|
| 662 |
+ UtilCreateNetworkMode(c, "host") |
|
| 659 | 663 |
UtilCreateNetworkMode(c, "container:web1") |
| 660 | 664 |
} |
| 661 | 665 |
|