diff --git a/daemon/container.go b/daemon/container.go index 86c0a7d84f5bf..becc69fcec3c4 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -294,6 +294,7 @@ func populateCommand(c *Container, env []string) error { c.command = &execdriver.Command{ ID: c.ID, Rootfs: c.RootfsPath(), + ReadonlyRootfs: c.hostConfig.ReadonlyRootfs, InitPath: "/.dockerinit", WorkingDir: c.Config.WorkingDir, Network: en, diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 80aad44ff9b60..fe99e062d1c4b 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -125,7 +125,8 @@ type ProcessConfig struct { // Process wrapps an os/exec.Cmd to add more metadata type Command struct { ID string `json:"id"` - Rootfs string `json:"rootfs"` // root fs of the container + Rootfs string `json:"rootfs"` // root fs of the container + ReadonlyRootfs bool `json:"readonly_rootfs"` InitPath string `json:"initpath"` // dockerinit WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 7b764a50e27ce..c5a8da75b78f0 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -31,6 +31,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e container.Cgroups.AllowedDevices = c.AllowedDevices container.MountConfig.DeviceNodes = c.AutoCreatedDevices container.RootFs = c.Rootfs + container.MountConfig.ReadonlyFs = c.ReadonlyRootfs // check to see if we are running in ramdisk to disable pivot root container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 63fe20ed13dea..24185489f2982 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -34,6 +34,7 @@ docker-create - Create a new container [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] [**--privileged**[=*false*]] +[**--read-only**[=*false*]] [**--restart**[=*RESTART*]] [**--security-opt**[=*[]*]] [**-t**|**--tty**[=*false*]] @@ -140,6 +141,9 @@ IMAGE [COMMAND] [ARG...] **--privileged**=*true*|*false* Give extended privileges to this container. The default is *false*. +**--read-only**=*true*|*false* + Mount the container's root filesystem as read only. + **--restart**="" Restart policy to apply when a container exits (no, on-failure[:max-retry], always) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index de035e9655e05..b16447bc5a8ba 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -35,6 +35,7 @@ docker-run - Run a command in a new container [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] [**--privileged**[=*false*]] +[**--read-only**[=*false*]] [**--restart**[=*RESTART*]] [**--rm**[=*false*]] [**--security-opt**[=*[]*]] @@ -253,6 +254,13 @@ to all devices on the host as well as set some configuration in AppArmor to allow the container nearly all the same access to the host as processes running outside of a container on the host. +**--read-only**=*true*|*false* + Mount the container's root filesystem as read only. + + By default a container will have its root filesystem writable allowing processes +to write files anywhere. By specifying the `--read-only` flag the container will have +its root filesystem mounted as read only prohibiting any writes. + **--restart**="" Restart policy to apply when a container exits (no, on-failure[:max-retry], always) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 08439ce0d45b4..a36133795cdde 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -61,6 +61,13 @@ This endpoint now returns the list current execs associated with the container ( **New!** New endpoint to rename a container `id` to a new name. +`POST /containers/create` +`POST /containers/(id)/start` + +**New!** +(`ReadonlyRootfs`) can be passed in the host config to mount the container's +root filesystem as read only. + ## v1.16 ### Full Documentation diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index 6c60873960548..a44dcbf3a9269 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -146,6 +146,7 @@ Create a container "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts": false, "Privileged": false, + "ReadonlyRootfs": false, "Dns": ["8.8.8.8"], "DnsSearch": [""], "VolumesFrom": ["parent", "other:ro"], @@ -218,6 +219,8 @@ Json Parameters: exposed ports. Specified as a boolean value. - **Privileged** - Gives the container full access to the host. Specified as a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. - **Dns** - A list of dns servers for the container to use. - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. @@ -323,6 +326,7 @@ Return low-level information on the container `id` "NetworkMode": "bridge", "PortBindings": {}, "Privileged": false, + "ReadonlyRootfs": false, "PublishAllPorts": false, "RestartPolicy": { "MaximumRetryCount": 2, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 0b7b0cda03a2f..052da5807198e 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -753,6 +753,7 @@ Creates a new container. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`) (use 'docker port' to see the actual mapping) --privileged=false Give extended privileges to this container + --read-only=false Mount the container's root filesystem as read only --restart="" Restart policy to apply when a container exits (no, on-failure[:max-retry], always) --security-opt=[] Security Options -t, --tty=false Allocate a pseudo-TTY @@ -1606,6 +1607,7 @@ removed before the image is removed. (use 'docker port' to see the actual mapping) --pid=host 'host': use the host PID namespace inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. --privileged=false Give extended privileges to this container + --read-only=false Mount the container's root filesystem as read only --restart="" Restart policy to apply when a container exits (no, on-failure[:max-retry], always) --rm=false Automatically remove the container when it exits (incompatible with -d) --security-opt=[] Security Options @@ -1681,6 +1683,13 @@ will automatically create this directory on the host for you. In the example above, Docker will create the `/doesnt/exist` folder before starting your container. + $ sudo docker run --read-only -v /icanwrite busybox touch /icanwrite here + +Volumes can be used in combination with `--read-only` to control where +a container writes files. The `--read only` flag mounts the container's root +filesystem as read only prohibiting writes to locations other than the +specified volumes for the container. + $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh By bind-mounting the docker unix socket and statically linked docker diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b1d63338dea2b..dc4bcdf83b2a9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2987,3 +2987,25 @@ func TestRunRestartMaxRetries(t *testing.T) { } logDone("run - test max-retries for --restart") } + +func TestRunContainerWithWritableRootfs(t *testing.T) { + defer deleteAllContainers() + out, err := exec.Command(dockerBinary, "run", "--rm", "busybox", "touch", "/file").CombinedOutput() + if err != nil { + t.Fatal(string(out), err) + } + logDone("run - writable rootfs") +} + +func TestRunContainerWithReadonlyRootfs(t *testing.T) { + defer deleteAllContainers() + out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput() + if err == nil { + t.Fatal("expected container to error on run with read only error") + } + expected := "Read-only file system" + if !strings.Contains(string(out), expected) { + t.Fatalf("expected output from failure to contain %s but contains %s", expected, out) + } + logDone("run - read only rootfs") +} diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 054c6836277c6..3aff582f9249d 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -118,6 +118,7 @@ type HostConfig struct { CapDrop []string RestartPolicy RestartPolicy SecurityOpt []string + ReadonlyRootfs bool } // This is used by the create command when you want to set both the @@ -148,6 +149,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { NetworkMode: NetworkMode(job.Getenv("NetworkMode")), IpcMode: IpcMode(job.Getenv("IpcMode")), PidMode: PidMode(job.Getenv("PidMode")), + ReadonlyRootfs: job.GetenvBool("ReadonlyRootfs"), } job.GetenvJson("LxcConf", &hostConfig.LxcConf) diff --git a/runconfig/parse.go b/runconfig/parse.go index 781f721f65f3d..2fcd5593593c0 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -63,6 +63,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "Default is to create a private IPC namespace (POSIX SysV IPC) for the container\n'container:': reuses another container shared memory, semaphores and message queues\n'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.") flRestartPolicy = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)") + flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only") ) cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.") @@ -312,6 +313,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CapDrop: flCapDrop.GetAll(), RestartPolicy: restartPolicy, SecurityOpt: flSecurityOpt.GetAll(), + ReadonlyRootfs: *flReadonlyRootfs, } // When allocating stdin in attached mode, close stdin at client disconnect