diff --git a/daemon/container.go b/daemon/container.go index c29c3246f3..0bcdee33f2 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -337,6 +337,10 @@ func populateCommand(c *Container, env []string) error { pid := &execdriver.Pid{} pid.HostPid = c.hostConfig.PidMode.IsHost() + uts := &execdriver.UTS{ + HostUTS: c.hostConfig.UTSMode.IsHost(), + } + // Build lists of devices allowed and created within the container. var userSpecifiedDevices []*configs.Device for _, deviceMapping := range c.hostConfig.Devices { @@ -412,6 +416,7 @@ func populateCommand(c *Container, env []string) error { Network: en, Ipc: ipc, Pid: pid, + UTS: uts, Resources: resources, AllowedDevices: allowedDevices, AutoCreatedDevices: autoCreatedDevices, diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 68ab60238b..554ad82c1b 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -86,6 +86,11 @@ type Pid struct { HostPid bool `json:"host_pid"` } +// UTS settings of the container +type UTS struct { + HostUTS bool `json:"host_uts"` +} + type NetworkInterface struct { Gateway string `json:"gateway"` IPAddress string `json:"ip"` @@ -155,6 +160,7 @@ type Command struct { Network *Network `json:"network"` Ipc *Ipc `json:"ipc"` Pid *Pid `json:"pid"` + UTS *UTS `json:"uts"` Resources *Resources `json:"resources"` Mounts []Mount `json:"mounts"` AllowedDevices []*configs.Device `json:"allowed_devices"` diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index ffec5ec6c3..ff0da9a276 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -29,6 +29,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) return nil, err } + if err := d.createUTS(container, c); err != nil { + return nil, err + } + if err := d.createNetwork(container, c); err != nil { return nil, err } @@ -173,6 +177,16 @@ func (d *driver) createPid(container *configs.Config, c *execdriver.Command) err return nil } +func (d *driver) createUTS(container *configs.Config, c *execdriver.Command) error { + if c.UTS.HostUTS { + container.Namespaces.Remove(configs.NEWUTS) + container.Hostname = "" + return nil + } + + return nil +} + func (d *driver) setPrivileged(container *configs.Config) (err error) { container.Capabilities = execdriver.GetAllCapabilities() container.Cgroups.AllowAllDevices = true diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 02958e55ed..d7a64282d0 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -42,6 +42,7 @@ docker-create - Create a new container [**-P**|**--publish-all**[=*false*]] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] +[**--uts**[=*[]*]] [**--privileged**[=*false*]] [**--read-only**[=*false*]] [**--restart**[=*RESTART*]] @@ -193,6 +194,11 @@ This value should always larger than **-m**, so you should alway use this with * **host**: use the host's PID namespace inside the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure. +**--uts**=host + Set the UTS mode for the container + **host**: use the host's UTS namespace inside the container. + Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. + **--privileged**=*true*|*false* Give extended privileges to this container. The default is *false*. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 57bda2b65b..eec0f1cefc 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -43,6 +43,7 @@ docker-run - Run a command in a new container [**-P**|**--publish-all**[=*false*]] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] +[**--uts**[=*[]*]] [**--privileged**[=*false*]] [**--read-only**[=*false*]] [**--restart**[=*RESTART*]] @@ -323,6 +324,11 @@ ports and the exposed ports, use `docker port`. **host**: use the host's PID namespace inside the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure. +**--uts**=host + Set the UTS mode for the container + **host**: use the host's UTS namespace inside the container. + Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. + **--privileged**=*true*|*false* Give extended privileges to this container. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 3b7d88e35c..940f0f0abd 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -991,6 +991,8 @@ Creates a new container. --oom-kill-disable=false Whether to disable OOM Killer for the container or not -P, --publish-all=false Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host + --pid="" PID namespace to use + --uts="" UTS namespace to use --privileged=false Give extended privileges to this container --read-only=false Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always) @@ -1958,6 +1960,7 @@ To remove an image using its digest: -P, --publish-all=false Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host --pid="" PID namespace to use + --uts="" UTS namespace to use --privileged=false Give extended privileges to this container --read-only=false Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index fdc905fe44..7c5113f6de 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -157,6 +157,7 @@ called a digest. As long as the input used to generate the image is unchanged, the digest value is predictable and referenceable. ## PID settings (--pid) + --pid="" : Set the PID (Process) Namespace mode for the container, 'host': use the host's PID namespace inside the container @@ -177,6 +178,23 @@ within the container. This command would allow you to use `strace` inside the container on pid 1234 on the host. +## UTS settings (--uts) + + --uts="" : Set the UTS namespace mode for the container, + 'host': use the host's UTS namespace inside the container + +The UTS namespace is for setting the hostname and the domain that is visible +to running processes in that namespace. By default, all containers, including +those with `--net=host`, have their own UTS namespace. The `host` setting will +result in the container using the same UTS namespace as the host. + +You may wish to share the UTS namespace with the host if you would like the +hostname of the container to change as the hostname of the host changes. A +more advanced use case would be changing the host's hostname from a container. + +> **Note**: `--uts="host"` gives the container full access to change the +> hostname of the host and is therefore considered insecure. + ## IPC settings (--ipc) --ipc="" : Set the IPC mode for the container, diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 3e3614138f..79678b0d24 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2765,6 +2765,38 @@ func (s *DockerSuite) TestRunModePidHost(c *check.C) { } } +func (s *DockerSuite) TestRunModeUTSHost(c *check.C) { + testRequires(c, NativeExecDriver, SameHostDaemon) + defer deleteAllContainers() + + hostUTS, err := os.Readlink("/proc/1/ns/uts") + if err != nil { + c.Fatal(err) + } + + cmd := exec.Command(dockerBinary, "run", "--uts=host", "busybox", "readlink", "/proc/self/ns/uts") + out2, _, err := runCommandWithOutput(cmd) + if err != nil { + c.Fatal(err, out2) + } + + out2 = strings.Trim(out2, "\n") + if hostUTS != out2 { + c.Fatalf("UTS different with --uts=host %s != %s\n", hostUTS, out2) + } + + cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/uts") + out2, _, err = runCommandWithOutput(cmd) + if err != nil { + c.Fatal(err, out2) + } + + out2 = strings.Trim(out2, "\n") + if hostUTS == out2 { + c.Fatalf("UTS should be different without --uts=host %s == %s\n", hostUTS, out2) + } +} + func (s *DockerSuite) TestRunTLSverify(c *check.C) { cmd := exec.Command(dockerBinary, "ps") out, ec, err := runCommandWithOutput(cmd) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 719bd3a87c..9f0f279f94 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -76,6 +76,27 @@ func (n IpcMode) Container() string { return "" } +type UTSMode string + +// IsPrivate indicates whether container use it's private UTS namespace +func (n UTSMode) IsPrivate() bool { + return !(n.IsHost()) +} + +func (n UTSMode) IsHost() bool { + return n == "host" +} + +func (n UTSMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", "host": + default: + return false + } + return true +} + type PidMode string // IsPrivate indicates whether container use it's private pid stack @@ -187,6 +208,7 @@ type HostConfig struct { NetworkMode NetworkMode IpcMode IpcMode PidMode PidMode + UTSMode UTSMode CapAdd []string CapDrop []string RestartPolicy RestartPolicy diff --git a/runconfig/parse.go b/runconfig/parse.go index 8ddfd4983f..b40bbeb022 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -52,6 +52,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container") flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use") + flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use") flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports") flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") @@ -281,6 +282,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode") } + utsMode := UTSMode(*flUTSMode) + if !utsMode.Valid() { + return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode") + } + restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) if err != nil { return nil, nil, cmd, err @@ -337,6 +343,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe NetworkMode: netMode, IpcMode: ipcMode, PidMode: pidMode, + UTSMode: utsMode, Devices: deviceMappings, CapAdd: flCapAdd.GetAll(), CapDrop: flCapDrop.GetAll(),