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