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