1# Docker Authorization
2
3Docker’s out-of-the-box authorization model is all or nothing. But many users
4require finer-grained access control and Docker’s plugin infrastructure allows
5us to do so.
6
7This is an excellent opportunity to see how to policy enable an existing
8service.
9
10## Goals
11
12This tutorial helps you get started with OPA and introduces you to core concepts
13in OPA.
14
15> Policy enabling an application decouples the policy implementation from the
16> business logic so that administrators can define policy without changing the
17> application while still keeping up with the size, complexity, and dynamic
18> nature of modern applications.
19
20For the purpose of this tutorial, we want to use OPA to enforce a policy that
21prevents users from running insecure containers.
22
23This tutorial illustrates two key concepts:
24
25  1. OPA policy definition is decoupled from the implementation of the service
26     (in this case Docker). The administrator is empowered to define and manage
27     policies without requiring changes to any of the apps.
28
29  2. Both the data relevant to policy and the policy definitions themselves can
30     change rapidly.
31
32## Prerequisites
33
34This tutorial requires:
35
36  * Docker Engine 1.11 or newer
37  * `root` or `sudo` access
38
39The tutorial has been tested on the following platforms:
40
41  * Ubuntu 16.04 (64-bit)
42
43If you are using a different distro, OS, or architecture, the steps will be the
44same. However, there may be slight differences in the commands you need to run.
45
46## Steps
47
48### 1. Create an empty policy definition that will allow all requests.
49
50```shell
51$ mkdir policies && cat >policies/example.rego <<EOF
52package docker.authz
53
54allow = true
55EOF
56```
57
58This policy defines a single rule named `allow` that always produces the
59decision `true`. Once all of the components are running, we will come back to
60the policy.
61
62### 2. Run the opa-docker-authz plugin and then open another terminal.
63
64```shell
65$ docker run -d --restart=always \
66    -v $PWD/policies:/policies \
67    -v /run/docker/plugins:/run/docker/plugins \
68    openpolicyagent/opa-docker-authz:0.2 \
69    -policy-file /policies/example.rego
70```
71
72### 3. Reconfigure Docker.
73
74Docker must include the following command-line argument:
75
76```shell
77--authorization-plugin=opa-docker-authz
78```
79
80On Ubuntu 16.04 with systemd, this can be done as follows (requires root):
81
82```shell
83$ sudo mkdir -p /etc/systemd/system/docker.service.d
84$ sudo tee -a /etc/systemd/system/docker.service.d/override.conf > /dev/null <<EOF
85[Service]
86ExecStart=
87ExecStart=/usr/bin/docker daemon -H fd:// --authorization-plugin=opa-docker-authz
88EOF
89$ sudo systemctl daemon-reload
90$ sudo service docker restart
91```
92
93If you are using a different Linux distribution or you are not running systemd,
94the process will be slightly different.
95
96### 4. Run a simple Docker command to make sure everything is still working.
97
98```shell
99$ docker ps
100```
101
102If everything is setup correctly, the command should exit successfully. You can
103expect to see log messages from OPA and the plugin.
104
105### 5. Test that the policy definition is working.
106
107Let’s modify our policy to **deny** all requests:
108
109```shell
110$ cat >policies/example.rego <<EOF
111package docker.authz
112
113allow = false
114EOF
115```
116
117In OPA, rules defines the content of documents. Documents be boolean values
118(true/false) or they can represent more complex structures using arrays,
119objects, strings, etc.
120
121In the example above we modified the policy to always return `false` so that
122requests will be rejected.
123
124```shell
125$ docker ps
126```
127
128The output should be:
129
130```
131Error response from daemon: authorization denied by plugin opa-docker-authz: request rejected by administrative policy
132```
133
134To learn more about how rules define the content of documents, see: [How Does OPA Work?](/how-does-opa-work.md)
135
136With this policy in place, users will not be able to run any Docker commands. Go
137ahead and try other commands such as `docker run` or `docker pull`. They will
138all be rejected.
139
140Now let's change the policy so that it's a bit more useful.
141
142### 6. Update the policy to reject requests with the unconfined [seccomp](https://en.wikipedia.org/wiki/Seccomp) profile:
143
144```shell
145$ cat >policies/example.rego <<EOF
146package docker.authz
147
148default allow = false
149
150allow {
151    not deny
152}
153
154deny {
155    seccomp_unconfined
156}
157
158seccomp_unconfined {
159    # This expression asserts that the string on the right-hand side is equal
160    # to an element in the array SecurityOpt referenced on the left-hand side.
161    input.Body.HostConfig.SecurityOpt[_] = "seccomp:unconfined"
162}
163EOF
164```
165
166### 7. Test the policy is working by running a simple container:
167
168```shell
169$ docker run hello-world
170```
171
172Now try running the same container but disable seccomp (which should be
173prevented by the policy):
174
175```shell
176$ docker run --security-opt seccomp:unconfined hello-world
177```
178
179Congratulations! You have successfully prevented containers from running without
180seccomp!
181
182The rest of the tutorial shows how you can grant fine grained access to specific
183clients.
184
185### <a name="identify-user"></a> 8. Identify the user in Docker requests.
186
187> Back up your existing Docker configuration, just in case. You can replace your
188> original configuration after you are done with the tutorial.
189
190```shell
191$ mkdir -p ~/.docker
192$ cp ~/.docker/config.json ~/.docker/config.json~
193```
194
195To identify the user, include an HTTP header in all of the requests sent to the
196Docker daemon:
197
198```shell
199$ cat >~/.docker/config.json <<EOF
200{
201    "HttpHeaders": {
202        "Authz-User": "bob"
203    }
204}
205EOF
206```
207
208> Docker does not currently provide a way to authenticate clients. But in Docker
209> 1.12, clients can be authenticated using TLS and there are plans to include
210> other means of authentication. For the purpose of this tutorial, we assume that
211> an authentication system is place.
212
213### 9. Update the policy to include basic user access controls.
214
215```shell
216$ cat >policies/example.rego <<EOF
217package docker.authz
218
219default allow = false
220
221# allow if the user is granted read/write access.
222allow {
223    user_id = input.Headers["Authz-User"]
224    not users[user_id].readOnly
225}
226
227# allow if the user is granted read-only access and the request is a GET.
228allow {
229    user_id = input.Headers["Authz-User"]
230    users[user_id].readOnly
231    input.Method = "GET"
232}
233
234# users defines permissions for the user. In this case, we define a single
235# attribute 'readOnly' that controls the kinds of commands the user can run.
236users = {
237    "bob": {"readOnly": true},
238    "alice": {"readOnly": false},
239}
240EOF
241```
242
243### 10. Attempt to run a container.
244
245Because the configured user is `"bob"`, the request is rejected:
246
247```shell
248$ docker run hello-world
249```
250
251### 11. Change the user to "alice" and re-run the container.
252
253```shell
254$ cat > ~/.docker/config.json <<EOF
255{
256    "HttpHeaders": {
257        "Authz-User": "alice"
258    }
259}
260EOF
261```
262
263Because the configured user is `"alice"`, the request will succeed:
264
265```shell
266$ docker run hello-world
267```
268
269### 12. Restore your original configuration.
270
271See: “[Reconfigure Docker.](#reconfigure-docker)” and “[Identify the user in Docker requests.](#identify-user)”.
272
273That's it!
274