Browse code

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 <crosbymichael@gmail.com>

Michael Crosby authored on 2015/01/14 06:52:51
Showing 11 changed files
... ...
@@ -294,6 +294,7 @@ func populateCommand(c *Container, env []string) error {
294 294
 	c.command = &execdriver.Command{
295 295
 		ID:                 c.ID,
296 296
 		Rootfs:             c.RootfsPath(),
297
+		ReadonlyRootfs:     c.hostConfig.ReadonlyRootfs,
297 298
 		InitPath:           "/.dockerinit",
298 299
 		WorkingDir:         c.Config.WorkingDir,
299 300
 		Network:            en,
... ...
@@ -125,7 +125,8 @@ type ProcessConfig struct {
125 125
 // Process wrapps an os/exec.Cmd to add more metadata
126 126
 type Command struct {
127 127
 	ID                 string            `json:"id"`
128
-	Rootfs             string            `json:"rootfs"`   // root fs of the container
128
+	Rootfs             string            `json:"rootfs"` // root fs of the container
129
+	ReadonlyRootfs     bool              `json:"readonly_rootfs"`
129 130
 	InitPath           string            `json:"initpath"` // dockerinit
130 131
 	WorkingDir         string            `json:"working_dir"`
131 132
 	ConfigPath         string            `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
... ...
@@ -31,6 +31,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
31 31
 	container.Cgroups.AllowedDevices = c.AllowedDevices
32 32
 	container.MountConfig.DeviceNodes = c.AutoCreatedDevices
33 33
 	container.RootFs = c.Rootfs
34
+	container.MountConfig.ReadonlyFs = c.ReadonlyRootfs
34 35
 
35 36
 	// check to see if we are running in ramdisk to disable pivot root
36 37
 	container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
... ...
@@ -34,6 +34,7 @@ docker-create - Create a new container
34 34
 [**-p**|**--publish**[=*[]*]]
35 35
 [**--pid**[=*[]*]]
36 36
 [**--privileged**[=*false*]]
37
+[**--read-only**[=*false*]]
37 38
 [**--restart**[=*RESTART*]]
38 39
 [**--security-opt**[=*[]*]]
39 40
 [**-t**|**--tty**[=*false*]]
... ...
@@ -140,6 +141,9 @@ IMAGE [COMMAND] [ARG...]
140 140
 **--privileged**=*true*|*false*
141 141
    Give extended privileges to this container. The default is *false*.
142 142
 
143
+**--read-only**=*true*|*false*
144
+    Mount the container's root filesystem as read only.
145
+
143 146
 **--restart**=""
144 147
    Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
145 148
 
... ...
@@ -35,6 +35,7 @@ docker-run - Run a command in a new container
35 35
 [**-p**|**--publish**[=*[]*]]
36 36
 [**--pid**[=*[]*]]
37 37
 [**--privileged**[=*false*]]
38
+[**--read-only**[=*false*]]
38 39
 [**--restart**[=*RESTART*]]
39 40
 [**--rm**[=*false*]]
40 41
 [**--security-opt**[=*[]*]]
... ...
@@ -253,6 +254,13 @@ to all devices on the host as well as set some configuration in AppArmor to
253 253
 allow the container nearly all the same access to the host as processes running
254 254
 outside of a container on the host.
255 255
 
256
+**--read-only**=*true*|*false*
257
+    Mount the container's root filesystem as read only.
258
+
259
+    By default a container will have its root filesystem writable allowing processes
260
+to write files anywhere.  By specifying the `--read-only` flag the container will have
261
+its root filesystem mounted as read only prohibiting any writes.
262
+
256 263
 **--restart**=""
257 264
    Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
258 265
 
... ...
@@ -61,6 +61,13 @@ This endpoint now returns the list current execs associated with the container (
61 61
 **New!**
62 62
 New endpoint to rename a container `id` to a new name.
63 63
 
64
+`POST /containers/create`
65
+`POST /containers/(id)/start`
66
+
67
+**New!**
68
+(`ReadonlyRootfs`) can be passed in the host config to mount the container's
69
+root filesystem as read only.
70
+
64 71
 ## v1.16
65 72
 
66 73
 ### Full Documentation
... ...
@@ -146,6 +146,7 @@ Create a container
146 146
                "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
147 147
                "PublishAllPorts": false,
148 148
                "Privileged": false,
149
+               "ReadonlyRootfs": false,
149 150
                "Dns": ["8.8.8.8"],
150 151
                "DnsSearch": [""],
151 152
                "VolumesFrom": ["parent", "other:ro"],
... ...
@@ -218,6 +219,8 @@ Json Parameters:
218 218
         exposed ports. Specified as a boolean value.
219 219
   -   **Privileged** - Gives the container full access to the host.  Specified as
220 220
         a boolean value.
221
+  -   **ReadonlyRootfs** - Mount the container's root filesystem as read only.
222
+        Specified as a boolean value.
221 223
   -   **Dns** - A list of dns servers for the container to use.
222 224
   -   **DnsSearch** - A list of DNS search domains
223 225
   -   **VolumesFrom** - A list of volumes to inherit from another container.
... ...
@@ -323,6 +326,7 @@ Return low-level information on the container `id`
323 323
 			"NetworkMode": "bridge",
324 324
 			"PortBindings": {},
325 325
 			"Privileged": false,
326
+			"ReadonlyRootfs": false,
326 327
 			"PublishAllPorts": false,
327 328
 			"RestartPolicy": {
328 329
 				"MaximumRetryCount": 2,
... ...
@@ -753,6 +753,7 @@ Creates a new container.
753 753
                                    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`)
754 754
                                    (use 'docker port' to see the actual mapping)
755 755
       --privileged=false         Give extended privileges to this container
756
+      --read-only=false           Mount the container's root filesystem as read only
756 757
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
757 758
       --security-opt=[]          Security Options
758 759
       -t, --tty=false            Allocate a pseudo-TTY
... ...
@@ -1606,6 +1607,7 @@ removed before the image is removed.
1606 1606
                                    (use 'docker port' to see the actual mapping)
1607 1607
       --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.
1608 1608
       --privileged=false         Give extended privileges to this container
1609
+      --read-only=false           Mount the container's root filesystem as read only
1609 1610
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
1610 1611
       --rm=false                 Automatically remove the container when it exits (incompatible with -d)
1611 1612
       --security-opt=[]          Security Options
... ...
@@ -1681,6 +1683,13 @@ will automatically create this directory on the host for you. In the
1681 1681
 example above, Docker will create the `/doesnt/exist`
1682 1682
 folder before starting your container.
1683 1683
 
1684
+    $ sudo docker run --read-only -v /icanwrite busybox touch /icanwrite here
1685
+
1686
+Volumes can be used in combination with `--read-only` to control where 
1687
+a container writes files.  The `--read only` flag mounts the container's root
1688
+filesystem as read only prohibiting writes to locations other than the
1689
+specified volumes for the container.
1690
+
1684 1691
     $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh
1685 1692
 
1686 1693
 By bind-mounting the docker unix socket and statically linked docker
... ...
@@ -2987,3 +2987,25 @@ func TestRunRestartMaxRetries(t *testing.T) {
2987 2987
 	}
2988 2988
 	logDone("run - test max-retries for --restart")
2989 2989
 }
2990
+
2991
+func TestRunContainerWithWritableRootfs(t *testing.T) {
2992
+	defer deleteAllContainers()
2993
+	out, err := exec.Command(dockerBinary, "run", "--rm", "busybox", "touch", "/file").CombinedOutput()
2994
+	if err != nil {
2995
+		t.Fatal(string(out), err)
2996
+	}
2997
+	logDone("run - writable rootfs")
2998
+}
2999
+
3000
+func TestRunContainerWithReadonlyRootfs(t *testing.T) {
3001
+	defer deleteAllContainers()
3002
+	out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput()
3003
+	if err == nil {
3004
+		t.Fatal("expected container to error on run with read only error")
3005
+	}
3006
+	expected := "Read-only file system"
3007
+	if !strings.Contains(string(out), expected) {
3008
+		t.Fatalf("expected output from failure to contain %s but contains %s", expected, out)
3009
+	}
3010
+	logDone("run - read only rootfs")
3011
+}
... ...
@@ -118,6 +118,7 @@ type HostConfig struct {
118 118
 	CapDrop         []string
119 119
 	RestartPolicy   RestartPolicy
120 120
 	SecurityOpt     []string
121
+	ReadonlyRootfs  bool
121 122
 }
122 123
 
123 124
 // This is used by the create command when you want to set both the
... ...
@@ -148,6 +149,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
148 148
 		NetworkMode:     NetworkMode(job.Getenv("NetworkMode")),
149 149
 		IpcMode:         IpcMode(job.Getenv("IpcMode")),
150 150
 		PidMode:         PidMode(job.Getenv("PidMode")),
151
+		ReadonlyRootfs:  job.GetenvBool("ReadonlyRootfs"),
151 152
 	}
152 153
 
153 154
 	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
... ...
@@ -63,6 +63,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
63 63
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
64 64
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "Default is to create a private IPC namespace (POSIX SysV IPC) for the container\n'container:<name|id>': 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.")
65 65
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)")
66
+		flReadonlyRootfs  = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
66 67
 	)
67 68
 
68 69
 	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
312 312
 		CapDrop:         flCapDrop.GetAll(),
313 313
 		RestartPolicy:   restartPolicy,
314 314
 		SecurityOpt:     flSecurityOpt.GetAll(),
315
+		ReadonlyRootfs:  *flReadonlyRootfs,
315 316
 	}
316 317
 
317 318
 	// When allocating stdin in attached mode, close stdin at client disconnect