From 409407091a7282d0c4086b71e86397e2d089ba13 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 Jan 2015 13:52:51 -0800 Subject: [PATCH] Add --readonly for read only container rootfs Add a --readonly flag to allow the container's root filesystem to be mounted as readonly. This can be used in combination with volumes to force a container's process to only write to locations that will be persisted. This is useful in many cases where the admin controls where they would like developers to write files and error on any other locations. Closes #7923 Closes #8752 Signed-off-by: Michael Crosby --- daemon/container.go | 1 + daemon/execdriver/driver.go | 3 ++- daemon/execdriver/native/create.go | 1 + docs/man/docker-create.1.md | 4 ++++ docs/man/docker-run.1.md | 8 +++++++ .../reference/api/docker_remote_api.md | 7 ++++++ .../reference/api/docker_remote_api_v1.17.md | 4 ++++ docs/sources/reference/commandline/cli.md | 9 ++++++++ integration-cli/docker_cli_run_test.go | 22 +++++++++++++++++++ runconfig/hostconfig.go | 2 ++ runconfig/parse.go | 2 ++ 11 files changed, 62 insertions(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index 86c0a7d84f..becc69fcec 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 80aad44ff9..fe99e062d1 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 7b764a50e2..c5a8da75b7 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 63fe20ed13..24185489f2 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 de035e9655..b16447bc5a 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 08439ce0d4..a36133795c 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 6c60873960..a44dcbf3a9 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 0b7b0cda03..052da58071 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 b1d63338de..dc4bcdf83b 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 054c683627..3aff582f92 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 781f721f65..2fcd559359 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