Skip to content

Commit

Permalink
Add user namespace (mapping) support to the Docker engine
Browse files Browse the repository at this point in the history
Adds support for the daemon to handle user namespace maps as a
per-daemon setting.

Support for handling uid/gid mapping is added to the builder,
archive/unarchive packages and functions, all graphdrivers (except
Windows), and the test suite is updated to handle user namespace daemon
rootgraph changes.

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
  • Loading branch information
estesp committed Oct 9, 2015
1 parent 9a3ab03 commit 442b456
Show file tree
Hide file tree
Showing 56 changed files with 876 additions and 201 deletions.
10 changes: 9 additions & 1 deletion api/server/router/local/image.go
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/docker/docker/daemon/daemonbuilder"
"github.com/docker/docker/graph"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
Expand Down Expand Up @@ -393,7 +395,13 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
}
}()

docker := daemonbuilder.Docker{s.daemon, output, authConfigs}
uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
defaultArchiver := &archive.Archiver{
Untar: chrootarchive.Untar,
UIDMaps: uidMaps,
GIDMaps: gidMaps,
}
docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}

b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions daemon/config.go
Expand Up @@ -30,6 +30,7 @@ type CommonConfig struct {
LogConfig runconfig.LogConfig
Mtu int
Pidfile string
RemappedRoot string
Root string
TrustKeyPath string
DefaultNetwork string
Expand Down
3 changes: 3 additions & 0 deletions daemon/config_unix.go
Expand Up @@ -27,6 +27,7 @@ type Config struct {
CorsHeaders string
EnableCors bool
EnableSelinuxSupport bool
RemappedRoot string
SocketGroup string
Ulimits map[string]*ulimit.Ulimit
}
Expand Down Expand Up @@ -77,4 +78,6 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
cmd.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic"))
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"))

config.attachExperimentalFlags(cmd, usageFn)
}
7 changes: 6 additions & 1 deletion daemon/container.go
Expand Up @@ -553,7 +553,12 @@ func (container *Container) export() (archive.Archive, error) {
return nil, err
}

archive, err := archive.Tar(container.basefs, archive.Uncompressed)
uidMaps, gidMaps := container.daemon.GetUIDGIDMaps()
archive, err := archive.TarWithOptions(container.basefs, &archive.TarOptions{
Compression: archive.Uncompressed,
UIDMaps: uidMaps,
GIDMaps: gidMaps,
})
if err != nil {
container.Unmount()
return nil, err
Expand Down
23 changes: 21 additions & 2 deletions daemon/container_unix.go
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/docker/docker/daemon/network"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
Expand Down Expand Up @@ -302,6 +303,14 @@ func populateCommand(c *Container, env []string) error {
processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
processConfig.Env = env

remappedRoot := &execdriver.User{}
rootUID, rootGID := c.daemon.GetRemappedUIDGID()
if rootUID != 0 {
remappedRoot.UID = rootUID
remappedRoot.GID = rootGID
}
uidMap, gidMap := c.daemon.GetUIDGIDMaps()

c.command = &execdriver.Command{
ID: c.ID,
Rootfs: c.rootfsPath(),
Expand All @@ -310,6 +319,9 @@ func populateCommand(c *Container, env []string) error {
WorkingDir: c.Config.WorkingDir,
Network: en,
Ipc: ipc,
UIDMapping: uidMap,
GIDMapping: gidMap,
RemappedRoot: remappedRoot,
Pid: pid,
UTS: uts,
Resources: resources,
Expand Down Expand Up @@ -1343,19 +1355,23 @@ func (container *Container) hasMountFor(path string) bool {
}

func (container *Container) setupIpcDirs() error {
rootUID, rootGID := container.daemon.GetRemappedUIDGID()
if !container.hasMountFor("/dev/shm") {
shmPath, err := container.shmPath()
if err != nil {
return err
}

if err := os.MkdirAll(shmPath, 0700); err != nil {
if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil {
return err
}

if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel("mode=1777,size=65536k", container.getMountLabel())); err != nil {
return fmt.Errorf("mounting shm tmpfs: %s", err)
}
if err := os.Chown(shmPath, rootUID, rootGID); err != nil {
return err
}
}

if !container.hasMountFor("/dev/mqueue") {
Expand All @@ -1364,13 +1380,16 @@ func (container *Container) setupIpcDirs() error {
return err
}

if err := os.MkdirAll(mqueuePath, 0700); err != nil {
if err := idtools.MkdirAllAs(mqueuePath, 0700, rootUID, rootGID); err != nil {
return err
}

if err := syscall.Mount("mqueue", mqueuePath, "mqueue", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), ""); err != nil {
return fmt.Errorf("mounting mqueue mqueue : %s", err)
}
if err := os.Chown(mqueuePath, rootUID, rootGID); err != nil {
return err
}
}

return nil
Expand Down
62 changes: 47 additions & 15 deletions daemon/daemon.go
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/docker/docker/pkg/discovery"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/nat"
Expand Down Expand Up @@ -121,6 +122,8 @@ type Daemon struct {
discoveryWatcher discovery.Watcher
root string
shutdown bool
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}

// Get looks for a container using the provided information, which could be
Expand Down Expand Up @@ -632,6 +635,15 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
// on Windows to dump Go routine stacks
setupDumpStackTrap()

uidMaps, gidMaps, err := setupRemappedRoot(config)
if err != nil {
return nil, err
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}

// get the canonical path to the Docker root directory
var realRoot string
if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) {
Expand All @@ -642,14 +654,13 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err)
}
}
config.Root = realRoot
// Create the root directory if it doesn't exists
if err := system.MkdirAll(config.Root, 0700); err != nil {

if err = setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil {
return nil, err
}

// set up the tmpDir to use a canonical path
tmp, err := tempDir(config.Root)
tmp, err := tempDir(config.Root, rootUID, rootGID)
if err != nil {
return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err)
}
Expand All @@ -663,7 +674,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
graphdriver.DefaultDriver = config.GraphDriver

// Load storage driver
driver, err := graphdriver.New(config.Root, config.GraphOptions)
driver, err := graphdriver.New(config.Root, config.GraphOptions, uidMaps, gidMaps)
if err != nil {
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}
Expand Down Expand Up @@ -696,7 +707,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo

daemonRepo := filepath.Join(config.Root, "containers")

if err := system.MkdirAll(daemonRepo, 0700); err != nil {
if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err
}

Expand All @@ -706,13 +717,13 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
}

logrus.Debug("Creating images graph")
g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver)
g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver, uidMaps, gidMaps)
if err != nil {
return nil, err
}

// Configure the volumes driver
volStore, err := configureVolumes(config)
volStore, err := configureVolumes(config, rootUID, rootGID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -777,7 +788,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo

var sysInitPath string
if config.ExecDriver == "lxc" {
initPath, err := configureSysInit(config)
initPath, err := configureSysInit(config, rootUID, rootGID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -812,6 +823,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
d.EventsService = eventsService
d.volumes = volStore
d.root = config.Root
d.uidMaps = uidMaps
d.gidMaps = gidMaps

if err := d.cleanupMounts(); err != nil {
return nil, err
Expand Down Expand Up @@ -974,7 +987,11 @@ func (daemon *Daemon) diff(container *Container) (archive.Archive, error) {
func (daemon *Daemon) createRootfs(container *Container) error {
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps)
if err != nil {
return err
}
if err := idtools.MkdirAs(container.root, 0700, rootUID, rootGID); err != nil {
return err
}
initID := fmt.Sprintf("%s-init", container.ID)
Expand All @@ -986,7 +1003,7 @@ func (daemon *Daemon) createRootfs(container *Container) error {
return err
}

if err := setupInitLayer(initPath); err != nil {
if err := setupInitLayer(initPath, rootUID, rootGID); err != nil {
daemon.driver.Put(initID)
return err
}
Expand Down Expand Up @@ -1105,6 +1122,21 @@ func (daemon *Daemon) containerGraph() *graphdb.Database {
return daemon.containerGraphDB
}

// GetUIDGIDMaps returns the current daemon's user namespace settings
// for the full uid and gid maps which will be applied to containers
// started in this instance.
func (daemon *Daemon) GetUIDGIDMaps() ([]idtools.IDMap, []idtools.IDMap) {
return daemon.uidMaps, daemon.gidMaps
}

// GetRemappedUIDGID returns the current daemon's uid and gid values
// if user namespaces are in use for this daemon instance. If not
// this function will return "real" root values of 0, 0.
func (daemon *Daemon) GetRemappedUIDGID() (int, int) {
uid, gid, _ := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps)
return uid, gid
}

// ImageGetCached returns the earliest created image that is a child
// of the image with imgID, that had the same config when it was
// created. nil is returned if a child cannot be found. An error is
Expand Down Expand Up @@ -1139,12 +1171,12 @@ func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*i
}

// tempDir returns the default directory to use for temporary files.
func tempDir(rootDir string) (string, error) {
func tempDir(rootDir string, rootUID, rootGID int) (string, error) {
var tmpDir string
if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" {
tmpDir = filepath.Join(rootDir, "tmp")
}
return tmpDir, system.MkdirAll(tmpDir, 0700)
return tmpDir, idtools.MkdirAllAs(tmpDir, 0700, rootUID, rootGID)
}

func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {
Expand Down Expand Up @@ -1228,8 +1260,8 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
return verifyPlatformContainerSettings(daemon, hostConfig, config)
}

func configureVolumes(config *Config) (*store.VolumeStore, error) {
volumesDriver, err := local.New(config.Root)
func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, error) {
volumesDriver, err := local.New(config.Root, rootUID, rootGID)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/daemon_test.go
Expand Up @@ -509,7 +509,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
volumes: store.New(),
}

volumesDriver, err := local.New(tmp)
volumesDriver, err := local.New(tmp, 0, 0)
if err != nil {
return nil, err
}
Expand Down
18 changes: 12 additions & 6 deletions daemon/daemon_unix.go
Expand Up @@ -15,10 +15,10 @@ import (
"github.com/docker/docker/daemon/graphdriver"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
"github.com/docker/libnetwork"
Expand Down Expand Up @@ -121,6 +121,11 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *runconfig.HostC
warnings := []string{}
sysInfo := sysinfo.New(true)

warnings, err := daemon.verifyExperimentalContainerSettings(hostConfig, config)
if err != nil {
return warnings, err
}

if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
}
Expand Down Expand Up @@ -275,7 +280,7 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
return migrateIfAufs(driver, root)
}

func configureSysInit(config *Config) (string, error) {
func configureSysInit(config *Config, rootUID, rootGID int) (string, error) {
localCopy := filepath.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION))
sysInitPath := utils.DockerInitPath(localCopy)
if sysInitPath == "" {
Expand All @@ -284,7 +289,7 @@ func configureSysInit(config *Config) (string, error) {

if sysInitPath != localCopy {
// When we find a suitable dockerinit binary (even if it's our local binary), we copy it into config.Root at localCopy for future use (so that the original can go away without that being a problem, for example during a package upgrade).
if err := os.Mkdir(filepath.Dir(localCopy), 0700); err != nil && !os.IsExist(err) {
if err := idtools.MkdirAs(filepath.Dir(localCopy), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return "", err
}
if _, err := fileutils.CopyFile(sysInitPath, localCopy); err != nil {
Expand Down Expand Up @@ -455,7 +460,7 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
//
// This extra layer is used by all containers as the top-most ro layer. It protects
// the container from unwanted side-effects on the rw layer.
func setupInitLayer(initLayer string) error {
func setupInitLayer(initLayer string, rootUID, rootGID int) error {
for pth, typ := range map[string]string{
"/dev/pts": "dir",
"/dev/shm": "dir",
Expand All @@ -478,12 +483,12 @@ func setupInitLayer(initLayer string) error {

if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
if err := system.MkdirAll(filepath.Join(initLayer, filepath.Dir(pth)), 0755); err != nil {
if err := idtools.MkdirAllAs(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootUID, rootGID); err != nil {
return err
}
switch typ {
case "dir":
if err := system.MkdirAll(filepath.Join(initLayer, pth), 0755); err != nil {
if err := idtools.MkdirAllAs(filepath.Join(initLayer, pth), 0755, rootUID, rootGID); err != nil {
return err
}
case "file":
Expand All @@ -492,6 +497,7 @@ func setupInitLayer(initLayer string) error {
return err
}
f.Close()
f.Chown(rootUID, rootGID)
default:
if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil {
return err
Expand Down

0 comments on commit 442b456

Please sign in to comment.