Docker mTLS ACLs with Open Policy Agent

2020-04-10

Last week i was working with the docker daemon to help prepare it for remote cli access (which to be clear, isn’t a good idea but i’ll detail this anyway)

By default, the docker daemon runs locally and you use the docker cli to access the daemon which in turn does all the heavy lifting for you (as root usually, by the way!).

The docker daemon accepts requests from the cli as plain old REST api call which you can read about here under Develop with Docker Engine API. As it is just a rest api, you can invoke it directly if its interface is listening. As mentioned, the only interface docker daemon installs by default is on local domain named pipes so that only local docker cli can access it (see Docker daemon attack surface).

I had to look into ways where a remote VMs docker daemon was exposed to external clients with the requirements that:

  • clients connecting to it had to use mTLS
  • different users/clients use use certificates to identify themselves
  • different clients would be allowed different capabilities (ACL permission) on the docker daemon

This led me to look a bit more into the docker’s authorization plugin which is a pretty simple admission controller that provides the plugin metadata about the requestor and action attempted and returns back a allow/deny decision (similar kubernetes RBAC)

Thats great…but do i have to write the controller from scratch?

Nope, may others have done that to ofcourse…and i ended up using Open Policy Agents


This tutorial is a simple walkthrough on how i set it up locally…its nothing new; you can follow OPA’s github page if you want to or here

What we will do is configure your local docker daemon to use mTLS certificates and configure selective access for two different users:

Clone the certificates

This article will use certificates you can find here (feel free to clone+download or just copy the certs over)

Define OPA policy handler

We will define the OPA policy handler to use for this demo

First create the policy file here /etc/docker/policies/authz.rego

with content like this:

package docker.authz

default allow = false
users = {
    "user1@domain.com": {"readOnly": true},
    "user2@domain.com": {"readOnly": false},    
}

allow {
    users[input.User].readOnly
    input.Method == "GET"
}

What does that mean? Well, its OPAs rego language which basically says:

  • “dont allow anyone access by default”
  • “define two users where user1 has a label “readOnly” which evalutes to true and user2 which evaluates to false.
  • For any “GET” request, lookup a user based on the CN value provided in the certificate in the users dictionary.
  • In the users dictionary, evaluate the request by using the users readOnly label.
  • If the its true, allow access, false otherwise.

OPA automatically extracts the CN value for the user and places it into input.User

For example, you can decode user1 and user2 certificates like this

openssl x509 -in client_crt.pem -text -noout
   Subject: C = US, ST = California, O = Google, OU = Enterprise, CN = user1@domain.com   
openssl x509 -in client2_crt.pem -text -noout
  Subject: C = US, ST = California, O = Google, OU = Enterprise, CN = user2.domain.com

The CN= value for gets extracted and populated as the input.User field.

For more information, see: OPA Request Attributes

Install OPA Plugin

Install the OPA plugin and point towards the policy file:

docker plugin install openpolicyagent/opa-docker-authz-v2:0.4 opa-args="-policy-file /opa/policies/authz.rego"

Create custom docker daemon for mtls

Create the docker daemon file at /etc/docker/daemon.json like this:

Remember to download the TEST certificates provided in this repo and place them in the path appropriate for the file:

Test

Use user1 certificate to list operations on the daemon: eg

$ docker --tlsverify --tlscacert=CA_crt.pem --tlscert=client_crt.pem --tlskey=client_key.pem -H=server.domain.com:2376 images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
server.domain.com/alpine                latest              a187dde48cd2        2 weeks ago         5.6MB

Now as try to run an image:

$ docker --tlsverify --tlscacert=CA_crt.pem --tlscert=client_crt.pem --tlskey=client_key.pem -H=server.domain.com:2376 run -ti server.domain.com/alpine 
docker: Error response from daemon: authorization denied by plugin openpolicyagent/opa-docker-authz-v2:0.4: request rejected by administrative policy.

This failed since we only allow GET operations (docker run will create a VM)

Use user2 to perform even a simple operation

$ docker --tlsverify --tlscacert=CA_crt.pem --tlscert=client2_crt.pem --tlskey=client2_key.pem -H=server.domain.com:2376 images
Error response from daemon: authorization denied by plugin openpolicyagent/opa-docker-authz-v2:0.4: request rejected by administrative policy

which fails since user2 isn’t allowed to do anything


Thats it, really..you’ll notice i didn’t even allow admin access to do any thing in there…thats left upto the reader and rego-jockys!

This site supports webmentions. Send me a mention via this form.