A fairly common question I get asked is how Factory devices securely pull container images from our Docker Registry, hub.foundries.io. It's kind of interesting, so I thought I'd explain.
Like all things "cloud", you are going to need to understand a few different moving pieces:
The Device Gateway is the key to our approach. Factory devices communicate with it using mutual TLS which means our backend knows exactly which device is accessing it. We added an API to the gateway that will create and return an ephemeral API token with read-only access to a device's Factory containers.
The credential helper is the second piece of the puzzle. Docker credential helpers provide a means for third party Docker Registry implementations such as hub.foundries.io to handle custom authentication. In our case, we created docker-credential-helper-fio to authenticate through our gateway.
This is cloud technology - so we need at least one more technology to scream at. In this case we have the third piece of the puzzle: Authentication with hub.foundries.io. The idea behind the open source Docker Registry implementation is that it's agnostic to authentication implementations. It instead defines how an authentication service should work. To oversimplify, the docker client on the device will:
- Ask hub.foundries.io if it can have "pull" access to
- Hub.foundries will then ask our authentication service if this is okay.
- Our service will say "no way, we value our customer's privacy, come back with some credentials we trust".
- The docker client will then look for ways to authenticate and find our credential helper.
- The client will take the user/password returned from the credential helper (which is an ephemeral API token we've created in the backend) and ask our authorization service once more if it can have pull access to the container.
- This time we see this token and can grant access.
- Access is granted by our service signing a JWT.
- The docker client again asks hub.foundries.io for the container but includes this JWT in the request.
- hub.foundries.io sees this, trusts the JWT, and will give the client the container.
At first this seems insane. I shook my fist at "the cloud" when I did the implementation. I've tried to walk multiple engineers through this process and they too have shaken their fists at "the cloud". The cherry on the top - most of this wasn't documented when we did it. I littered locally built copies of docker-client and docker-registry with "printfs" to sort it out. The good news is this: There's now some documentation around this process. Also, if you really think through this, each part of this puzzle is necessary for a secure system that we can all build off of.