1---
2title: Docker
3kind: tutorial
4weight: 1
5---
6
7Docker’s out-of-the-box authorization model is all or nothing. But many users
8require finer-grained access control and Docker’s plugin infrastructure allows
9us to do so.
10
11This is an excellent opportunity to see how to policy enable an existing
12service.
13
14## Goals
15
16This tutorial helps you get started with OPA and introduces you to core concepts
17in OPA.
18
19> Policy enabling an application decouples the policy implementation from the
20> business logic so that administrators can define policy without changing the
21> application while still keeping up with the size, complexity, and dynamic
22> nature of modern applications.
23
24For the purpose of this tutorial, we want to use OPA to enforce a policy that
25prevents users from running insecure containers.
26
27This tutorial illustrates two key concepts:
28
29  1. OPA policy definition is decoupled from the implementation of the service
30     (in this case Docker). The administrator is empowered to define and manage
31     policies without requiring changes to any of the apps.
32
33  2. Both the data relevant to policy and the policy definitions themselves can
34     change rapidly.
35
36## Prerequisites
37
38This tutorial requires:
39
40  * Docker Engine 18.06.0-ce or newer
41  * Docker API version 1.38 or newer
42  * `root` or `sudo` access
43
44The tutorial has been tested on the following platforms:
45
46  * Ubuntu 18.04 (64-bit)
47
48If you are using a different distro, OS, or architecture, the steps will be the
49same. However, there may be slight differences in the commands you need to run.
50
51## Steps
52
53Several of the steps below require `root` or `sudo` access. When you are
54modifying files under `/etc/docker` or signalling the Docker daemon to
55restart, you will need root access.
56
57### 1. Create an empty policy definition that will allow all requests.
58
59```shell
60mkdir -p /etc/docker/policies
61```
62
63**/etc/docker/policies/authz.rego**:
64
65```live:docker_authz:module:read_only
66package docker.authz
67
68allow = true
69```
70
71This policy defines a single rule named `allow` that always produces the
72decision `true`. Once all of the components are running, we will come back to
73the policy.
74
75### 2. Install the opa-docker-authz plugin.
76
77```shell
78docker plugin install openpolicyagent/opa-docker-authz-v2:0.4 opa-args="-policy-file /opa/policies/authz.rego"
79```
80
81You need to configure the Docker daemon to use the plugin for authorization.
82
83```shell
84cat > /etc/docker/daemon.json <<EOF
85{
86    "authorization-plugins": ["openpolicyagent/opa-docker-authz-v2:0.4"]
87}
88EOF
89```
90
91Signal the Docker daemon to reload the configuration file.
92
93```shell
94kill -HUP $(pidof dockerd)
95```
96
97### 4. Run a simple Docker command to make sure everything is still working.
98
99```shell
100docker ps
101```
102
103If everything is setup correctly, the command should exit successfully. You can
104expect to see log messages from OPA and the plugin.
105
106### 5. Test that the policy definition is working.
107
108Let’s modify our policy to **deny** all requests:
109
110**/etc/docker/policies/authz.rego**:
111
112```live:docker_authz_deny_all:module:read_only
113package docker.authz
114
115allow = false
116```
117
118In OPA, rules defines the content of documents. Documents be boolean values
119(true/false) or they can represent more complex structures using arrays,
120objects, strings, etc.
121
122In the example above we modified the policy to always return `false` so that
123requests will be rejected.
124
125```shell
126docker ps
127```
128
129The output should be:
130
131```shell
132Error response from daemon: authorization denied by plugin opa-docker-authz: request rejected by administrative policy
133```
134
135To learn more about how rules define the content of documents, see: [How Does OPA Work?](../#how-does-opa-work)
136
137With this policy in place, users will not be able to run any Docker commands. Go
138ahead and try other commands such as `docker run` or `docker pull`. They will
139all be rejected.
140
141Now let's change the policy so that it's a bit more useful.
142
143### 6. Update the policy to reject requests with the unconfined [seccomp](https://en.wikipedia.org/wiki/Seccomp) profile:
144
145**/etc/docker/policies/authz.rego**:
146
147```live:docker_authz_deny_unconfined:module:openable
148package docker.authz
149
150default allow = false
151
152allow {
153    not deny
154}
155
156deny {
157    seccomp_unconfined
158}
159
160seccomp_unconfined {
161    # This expression asserts that the string on the right-hand side is equal
162    # to an element in the array SecurityOpt referenced on the left-hand side.
163    input.Body.HostConfig.SecurityOpt[_] == "seccomp:unconfined"
164}
165```
166
167The plugin queries the `allow` rule to authorize requests to Docker. The `input`
168document is set to the attributes passed from Docker.
169
170```live:docker_authz_deny_unconfined:query:hidden
171allow
172```
173
174```live:docker_authz_deny_unconfined:input
175{
176  "AuthMethod": "",
177  "Body": {
178    "AttachStderr": true,
179    "AttachStdin": false,
180    "AttachStdout": true,
181    "Cmd": null,
182    "Domainname": "",
183    "Entrypoint": null,
184    "Env": [],
185    "HostConfig": {
186      "AutoRemove": false,
187      "Binds": null,
188      "BlkioDeviceReadBps": null,
189      "BlkioDeviceReadIOps": null,
190      "BlkioDeviceWriteBps": null,
191      "BlkioDeviceWriteIOps": null,
192      "BlkioWeight": 0,
193      "BlkioWeightDevice": [],
194      "CapAdd": null,
195      "CapDrop": null,
196      "Cgroup": "",
197      "CgroupParent": "",
198      "ConsoleSize": [
199        0,
200        0
201      ],
202      "ContainerIDFile": "",
203      "CpuCount": 0,
204      "CpuPercent": 0,
205      "CpuPeriod": 0,
206      "CpuQuota": 0,
207      "CpuRealtimePeriod": 0,
208      "CpuRealtimeRuntime": 0,
209      "CpuShares": 0,
210      "CpusetCpus": "",
211      "CpusetMems": "",
212      "DeviceCgroupRules": null,
213      "Devices": [],
214      "DiskQuota": 0,
215      "Dns": [],
216      "DnsOptions": [],
217      "DnsSearch": [],
218      "ExtraHosts": null,
219      "GroupAdd": null,
220      "IOMaximumBandwidth": 0,
221      "IOMaximumIOps": 0,
222      "IpcMode": "",
223      "Isolation": "",
224      "KernelMemory": 0,
225      "Links": null,
226      "LogConfig": {
227        "Config": {},
228        "Type": ""
229      },
230      "MaskedPaths": null,
231      "Memory": 0,
232      "MemoryReservation": 0,
233      "MemorySwap": 0,
234      "MemorySwappiness": -1,
235      "NanoCpus": 0,
236      "NetworkMode": "default",
237      "OomKillDisable": false,
238      "OomScoreAdj": 0,
239      "PidMode": "",
240      "PidsLimit": 0,
241      "PortBindings": {},
242      "Privileged": false,
243      "PublishAllPorts": false,
244      "ReadonlyPaths": null,
245      "ReadonlyRootfs": false,
246      "RestartPolicy": {
247        "MaximumRetryCount": 0,
248        "Name": "no"
249      },
250      "SecurityOpt": null,
251      "ShmSize": 0,
252      "UTSMode": "",
253      "Ulimits": null,
254      "UsernsMode": "",
255      "VolumeDriver": "",
256      "VolumesFrom": null
257    },
258    "Hostname": "",
259    "Image": "hello-world",
260    "Labels": {},
261    "NetworkingConfig": {
262      "EndpointsConfig": {}
263    },
264    "OnBuild": null,
265    "OpenStdin": false,
266    "StdinOnce": false,
267    "Tty": false,
268    "User": "",
269    "Volumes": {},
270    "WorkingDir": ""
271  },
272  "Headers": {
273    "Content-Length": "1470",
274    "Content-Type": "application/json",
275    "User-Agent": "Docker-Client/18.06.1-ce (linux)"
276  },
277  "Method": "POST",
278  "Path": "/v1.38/containers/create",
279  "User": ""
280}
281```
282
283For the input above, the value of `allow` is:
284
285```live:docker_authz_deny_unconfined:output
286```
287
288> Many of the examples in the documentation are interactive. Try editing the
289> input above by setting `Body.HostConfig.SecurityOpt` to
290> `["seccomp:unconfined"]`.
291
292### 7. Test the policy is working by running a simple container:
293
294```shell
295docker run hello-world
296```
297
298Now try running the same container but disable seccomp (which should be
299prevented by the policy):
300
301```shell
302docker run --security-opt seccomp:unconfined hello-world
303```
304
305Congratulations! You have successfully prevented containers from running without
306seccomp!
307
308The rest of the tutorial shows how you can grant fine grained access to specific
309clients.
310
311### <a name="identify-user"></a> 8. Identify the user in Docker requests.
312
313> Back up your existing Docker configuration, just in case. You can replace your
314> original configuration after you are done with the tutorial.
315
316```shell
317mkdir -p ~/.docker
318cp ~/.docker/config.json ~/.docker/config.json~
319```
320
321To identify the user, include an HTTP header in all of the requests sent to the
322Docker daemon:
323
324```shell
325cat >~/.docker/config.json <<EOF
326{
327    "HttpHeaders": {
328        "Authz-User": "bob"
329    }
330}
331EOF
332```
333
334> Docker does not currently provide a way to authenticate clients. But in Docker
335> 1.12, clients can be authenticated using TLS and there are plans to include
336> other means of authentication. For the purpose of this tutorial, we assume that
337> an authentication system is place.
338
339### 9. Update the policy to include basic user access controls.
340
341```live:docker_authz_users:module:read_only,openable
342package docker.authz
343
344default allow = false
345
346# allow if the user is granted read/write access.
347allow {
348    user_id := input.Headers["Authz-User"]
349    user := users[user_id]
350    not user.readOnly
351}
352
353# allow if the user is granted read-only access and the request is a GET.
354allow {
355    user_id := input.Headers["Authz-User"]
356    users[user_id].readOnly
357    input.Method == "GET"
358}
359
360# users defines permissions for the user. In this case, we define a single
361# attribute 'readOnly' that controls the kinds of commands the user can run.
362users = {
363    "bob": {"readOnly": true},
364    "alice": {"readOnly": false},
365}
366```
367
368### 10. Attempt to run a container.
369
370Because the configured user is `"bob"`, the request is rejected:
371
372```shell
373docker run hello-world
374```
375
376### 11. Change the user to "alice" and re-run the container.
377
378```shell
379cat > ~/.docker/config.json <<EOF
380{
381    "HttpHeaders": {
382        "Authz-User": "alice"
383    }
384}
385EOF
386```
387
388Because the configured user is `"alice"`, the request will succeed:
389
390```shell
391docker run hello-world
392```
393
394That's it!
395