In this chapter, we will take a look at working with Docker tools that can be used to secure your environment. We will be taking a look at both command-line tools as well as GUI tools that you can utilize to your advantage. We will cover the following items in this chapter:
In this section, we will cover the tools that can help you secure your Docker environment. These are options that are built into the Docker software, which you are already using. It's time to learn how to enable or utilize these such features to help give you the peace of mind in order to be sure that the communication is secure; this is where we will cover enabling TLS, which is a protocol that ensures privacy between applications. It ensures that nobody is listening in on the communication. Think of it as when you are watching a movie and people on the phone say, is this line secure? It's the same kind of idea when it comes to network communication. Then, we will look at how you can utilize the read-only containers to ensure that the data you are serving up can't be manipulated by anyone.
It is highly recommended to use the Docker Machine to create and manage your Docker hosts. It will automatically set up the communication to use TLS. Here's how you can verify that the default host that was created by docker-machine
indeed uses TLS.
One of the important factors is knowing if you are using TLS or not and then adjusting to use TLS if you are, in fact, not using TLS. The important thing to remember is that, nowadays, almost all the Docker tools ship with the TLS enabled, or if they don't, they appear to be working towards this goal. One of the commands that you can use to check in order to see if your Docker host is utilizing the TLS is with the Docker Machine inspect
command. In the following, we will take a look at a host and see if it is running with the TLS enabled:
docker-machine inspect default { "ConfigVersion": 3, "Driver": { "IPAddress": "192.168.99.100", "MachineName": "default", "SSHUser": "docker", "SSHPort": 50858, "SSHKeyPath": "/Users/scottgallagher/.docker/machine/machines/default/id_rsa", "StorePath": "/Users/scottgallagher/.docker/machine", "SwarmMaster": false, "SwarmHost": "tcp://0.0.0.0:3376", "SwarmDiscovery": "", "VBoxManager": {}, "CPU": 1, "Memory": 2048, "DiskSize": 204800, "Boot2DockerURL": "", "Boot2DockerImportVM": "", "HostDNSResolver": false, "HostOnlyCIDR": "192.168.99.1/24", "HostOnlyNicType": "82540EM", "HostOnlyPromiscMode": "deny", "NoShare": false, "DNSProxy": false, "NoVTXCheck": false }, "DriverName": "virtualbox", "HostOptions": { "Driver": "", "Memory": 0, "Disk": 0, "EngineOptions": { "ArbitraryFlags": [], "Dns": null, "GraphDir": "", "Env": [], "Ipv6": false, "InsecureRegistry": [], "Labels": [], "LogLevel": "", "StorageDriver": "", "SelinuxEnabled": false, "TlsVerify": true, "RegistryMirror": [], "InstallURL": "https://get.docker.com" }, "SwarmOptions": { "IsSwarm": false, "Address": "", "Discovery": "", "Master": false, "Host": "tcp://0.0.0.0:3376", "Image": "swarm:latest", "Strategy": "spread", "Heartbeat": 0, "Overcommit": 0, "ArbitraryFlags": [], "Env": null }, "AuthOptions": { "CertDir": "/Users/scottgallagher/.docker/machine/certs", "CaCertPath": "/Users/scottgallagher/.docker/machine/certs/ca.pem", "CaPrivateKeyPath": "/Users/scottgallagher/.docker/machine/certs/ca-key.pem", "CaCertRemotePath": "", "ServerCertPath": "/Users/scottgallagher/.docker/machine/machines/default/server.pem", "ServerKeyPath": "/Users/scottgallagher/.docker/machine/machines/default/server-key.pem", "ClientKeyPath": "/Users/scottgallagher/.docker/machine/certs/key.pem", "ServerCertRemotePath": "", "ServerKeyRemotePath": "", "ClientCertPath": "/Users/scottgallagher/.docker/machine/certs/cert.pem", "ServerCertSANs": [], "StorePath": "/Users/scottgallagher/.docker/machine/machines/default" } }, "Name": "default" }
From the preceding output, we can focus on the following line:
"SwarmHost": "tcp://0.0.0.0:3376",
This shows us that if we were running Swarm, this host would be utilizing the secure 3376
port. Now, if you aren't using Docker Swarm, then you can disregard this line. However, if you are using Docker Swarm, then this line is important.
Just to take a step back, let's identify what Docker Swarm is. Docker Swarm is native clustering within Docker. It helps in turning multiple Docker hosts into an easy-to-manage single virtual host:
"AuthOptions": { "CertDir": "/Users/scottgallagher/.docker/machine/certs", "CaCertPath": "/Users/scottgallagher/.docker/machine/certs/ca.pem", "CaPrivateKeyPath": "/Users/scottgallagher/.docker/machine/certs/ca-key.pem", "CaCertRemotePath": "", "ServerCertPath": "/Users/scottgallagher/.docker/machine/machines/default/server.pem", "ServerKeyPath": "/Users/scottgallagher/.docker/machine/machines/default/server-key.pem", "ClientKeyPath": "/Users/scottgallagher/.docker/machine/certs/key.pem", "ServerCertRemotePath": "", "ServerKeyRemotePath": "", "ClientCertPath": "/Users/scottgallagher/.docker/machine/certs/cert.pem", "ServerCertSANs": [], "StorePath": "/Users/scottgallagher/.docker/machine/machines/default" }
This shows us that this host is, in fact, using the certificates so we know that it is using TLS, but how do we know from just that? In the following section, we will take a look at how to tell that it is, in fact, using TLS for sure.
Docker Machine also has the option to run everything over TLS. This is the most secure way of using Docker Machine in order to manage your Docker hosts. This setup can be tricky if you start using your own certificates. By default, Docker Machine stores your certificates that it uses in /Users/<user_id>/.docker/machine/certs/
. You can see the location on your machine where the certificates are stored at from the preceding output.
Let's take a look at how we can achieve the goal of viewing if our Docker host is utilize TLS:
docker-machine ls NAME ACTIVE URL STATE URL SWARM DOCKER ERRORS default * virtualbox Running tcp://192.168.99.100:2376 v1.9.1
This is where we can tell that it is using TLS. The insecure port of Docker Machine hosts is the 2375
port and this host is using 2376
, which is the secure TLS port for Docker Machine. Therefore, this host is, in fact, using TLS to communicate, which gives you the peace of mind in knowing that the communication is secure.
With respect to the
docker run
command, we will mainly focus on the option that allows us to set everything inside the container as read-only. Let's take a look at an example and break down what it exactly does:
$ docker run --name mysql --read-only -v /var/lib/mysql v /tmp --e MYSQL_ROOT_PASSWORD=password -d mysql
Here, we are running a mysql
container and setting the entire container as read-only, except for the /var/lib/mysql
directory. What this means is that the only location the data can be written inside the container is the /var/lib/mysql
directory. Any other location inside the container won't allow you to write anything in it. If you try to run the following, it would fail:
$ docker exec mysql touch /opt/filename
This can be extremely helpful if you want to control where the containers can write to or not write to. Make sure to use this wisely. Test thoroughly, as it can have consequences when the applications can't write to certain locations.
Remember the Docker volumes we looked at in the previous chapters, where we were able to set the volumes to be read-only. Similar to the previous command with docker run
, where we set everything to read-only, except for a specified volume, we can now do the opposite and set a single volume (or more, if you use more -v
switches) to read-only. The thing to remember about volumes is that when you use a volume and mount it in a container, it will mount as an empty volume over the top of that directory inside the container, unless you use the --volumes-from
switch or add data to the container in some other way after the fact:
$ docker run -d -v /opt/uploads:/opt/uploads:/opt/uploads:ro nginx
This will mount a volume in /opt/uploads
and set it to read-only. This can be useful if you don't want a running container to write to a volume in order to keep the data or configuration files intact.
The last option that we want to look at, with regards to the docker run
command is the --device=
switch. This switch allows us to mount a device from the Docker host into a specified location inside the container. For doing so, there are some security risks that we need to be aware of. By default, when you do this, the container will get full the access: read, write, and the mknod
access to the device's location. Now, you can control these permissions by manipulating rwm
at the end of the switch command.
Let's take a look at some of these and see how they work:
$ docker run --device=/dev/sdb:/dev/sdc2 -it ubuntu:latest /bin/bash
The previous command will run the latest Ubuntu image and mount the /dev/sdb
device inside the container at the /dev/sdc2
location:
$ docker run --device=/dev/sdb:/dev/sdc2:r -it ubuntu:latest /bin/bash
This command will run the latest Ubuntu image and mount the /dev/sdb1
device inside the container at the /dev/sdc2
location. However, this one has the :r
tag at the end of it that specifies that it's read-only and can't be written.