Docker Security issues fall under the broader umbrella of container security. However, in swarm mode, Docker also acts as the orchestrator. This article will focus on container security when using Docker. A follow-on article will look at Docker as the orchestration layer in swarm mode and what security concerns you’ll need to worry about.
Components of Container Security
In the book Container Security by Liz Rice, you’ll see a diagram that looks like the following:
This diagram does a great job of showing all the things you need to think about when using containers, specifically Docker.
Physical security should always be of concern. This is the first layer of security with a long list of vulnerabilities, but in summary, ask these questions:
1. Who can access the building? Do they need badges, do they need to check in, and is their access tracked?
2. Are the server rooms secure? Are the wires into and out of the building secure? Do your doors include automatic unlocking when someone is near from the inside? If so, sliding paper under the door can activate it.
3. Do the physical machines have USB ports or other ways to attach 3rd party devices?
4. Can you log in to the server via a keyboard/monitor? If so, does that require two-factor authentication? Is that access tracked and audited? Are there cameras watching the terminals?
Hosts and virtual machines have the same security issues with or without containers. You need to do at least the following, but probably more:
1. Ensure your operating system stays patched
2. Only expose ports that your applications need to use
3. Make sure you have multiple networks. One for exposing functionality to external parties, such as APIs, and at least one more for accessing back-plane functionality, such as databases, key/secret sharing, orchestration communication, etc.
4. Turn off any unneeded services. A lot of times, operating systems such as Windows or Linux can have many things turned on, such as FTP, SSH, Telnet, echo, and a long list of other services you want to ensure are turned off if you aren’t using them.
5. Turn on audit logging so when someone does access your server, you know, they accessed it and what they accessed.
6. Use secure protocols when you can, such as only allowing HTTPS and disallowing HTTP.
7. Encrypt file system data if possible, or use a file system with encryption built in.
Network security can get pretty complex, especially since there are multiple layers. Containers are just one piece of the puzzle, but your container is being orchestrated and hosted on compute nodes. Your compute nodes are probably part of a system of many application clusters, which all need to expose some level of functionality to the intranet or internet. This will include multiple protocols, networks, many servers talking to each other, multiple layers of firewalls, intrusion detection, auditing, etc.
A typical checklist for containers may look like the following:
1. Deny all firewall rules, only opening ports you need. Preferably more secure protocols like HTTPS.
2. With docker/swarm, you use Ingres to expose ports to external parties and internal communication. These are also called overlay networks. Read about them here: https://docs.docker.com/network/overlay/
The nice thing about Ingres is that, by default, nothing is exposed. You must publish that port to expose your service to third parties on an intranet or internet. Take note that Windows doesn’t work with encrypted communications on overlay networks. It doesn’t necessarily add a security risk, but it also won’t work.
3. It’s common for different application clusters to be in a VPC. This way, your applications are walled off. This can get complicated when talking about shared physical networks, storage, and applications such as databases. In a truly secure environment, you may want to have all three physically separated so they can’t be used as an attack vector or a vulnerability.
4. Whitelist IP addresses for accessing management ports. That way, people who need to manage docker, swarm, Kubernetes, and/or OpenStack are the only ones able to access those ports or sockets.
5. Run port scans and vulnerability scans against your application clusters often. If ports get opened accidentally or vulnerabilities get published, you can be alerted immediately.
Securing containers is about isolation. You want as few privileges on the host machine/operating system as possible. This way, an attacker can’t use your container as a jumping-off point for further attacks.
Here is a simple checklist of things to check:
1. Do not run as root. A lot of times, this is the default behavior. If using YAML files from other people, internal or external, make sure to scrutinize them for flaws or possible attack vectors.
2. Don’t allow containers to install software at runtime; AKA creates immutable containers. Your images should be static and secure. Further modification can allow for vulnerabilities outside your CI/CD pipeline, including scanning for vulnerabilities.
3. Don’t use the privileged flag. Read this blog for more information: https://ericchiang.github.io/post/privileged-containers/
4. Be very careful when mounting drives to your container. You just want to mount data directories. For instance, don’t do things like mounting /var, /bin, etc. This can expose host operating system operations/data to your container, which breaks isolation and could allow an attacker to take over the host machine, possibly with root access, giving them access to your entire environment.
5. Check out the sidecar container pattern. There is a lot of useful functionality that can be given to a sidecar. Examples are security monitoring, ensuring TLS communication across your cluster, etc.
Image security seems simple, but there are many moving parts, each of which can introduce vulnerabilities.
It starts with your base images. Typically you won’t create an image from scratch. But instead, you’ll get one from the internet. This could be as simple as a base Linux image, or maybe an Nginx image, etc. You must fully scan the image to ensure no hidden vulnerabilities, even if you trust the source. You will need to ensure the YAML file isn’t doing things such as running as root. You then need to store that image somewhere secure. This is done in a secure registry, preferably that you control. Docker for Business and Mirantis (previously Docker Enterprise) provide products you can purchase.
Next, you are going to install your application on the image. These applications need to be static if possible. This means not storing state information on the actual image but in a shared space such as a shared file system or database. Your application should be scanned for typical vulnerabilities. For instance, you can use OWASP tools: https://owasp.org/www-community/Vulnerability_Scanning_Tools
Note that this differs from source code scanning, which we will get to in the next section.
As discussed in the container security section. Your images should not be able to download more software. They should be static and fully controlled by your CI/CD pipelines. That way, you are ensured that what you produce is what is used and won’t change until you push a change through your pipeline.
Application security may be the most vulnerable as there is a large list of potential breach points in the process. You probably have multiple developers, vendors, products, and processes involved, each of which is a potential vulnerability. For the types of vulnerabilities you need to be aware of, check out the OWASP top 10 most common vulnerabilities.
First, let’s start with CI/CD pipelines. For those that don’t know, CI stands for continuous integration, and CD stands for continuous delivery. This means when a developer pushes a code change that can result in the application being built, an environment stood up, the application deployed, vulnerability scans run, behavioral and functional testing automatically run, and if everything passes, the application will then be deployed. This allows for a fast and secure way to deliver products to your customers. In the future, we will have a log on microservices and the driving force behind containerization, CI/CD. Still, for now, hopefully, you can see a lot of steps involved and many places for attackers to introduce vulnerabilities.
Here is a short checklist of things to watch out for, but it is nowhere near complete:
1. Start with the developer. Is their machine secure? Being able to check-in the code as a trusted user is a great way to introduce vulnerabilities. Developers should be considered attack vectors and untrusted sources of code. This goes doubly for contractors or software vendors.
This means every check-in needs to be scanned in many different ways.
A. Find Bugs – a common plugin, but also a kind of pattern. Look for common bugs such as not checking nulls, not validating user inputs, infinite loops, etc.
B. Dependency scanner – look for dependencies with known vulnerabilities. A great way for a sneaky attacker is to include slightly older libraries in your code that essentially creates a known back door.
C. Virus Scanner
D. Vulnerability scanner – looking for things like injection flaws, not sanitizing user input for things like SQL injection, etc.
E. Hotspot scanning – looking for potential problems. This can be tricky, but things like not using external keys for encryption, not encrypting/decrypting sensitive data, storing sensitive data locally, etc.
2. Use a secure registry of pre-scanned images to build your image. You don’t want to use a public registry when pushing to production.
3. Your application should not store security information, if possible. Allow another application to handle login, security roles, etc. This could be as simple as using LDAP/Active Directory or https://www.keycloak.org/
The problem with storing/syncing user info is
A. You’ve replicated information that needs to be synced. That’s a problem if it’s a large number of users.
B. You now must be at least as secure with that information as the source. It’s much better to just use another app for that. Especially in microservices where your services should be super simple and at most should just be checking the user’s credentials for functionality.
4. Set up centralized logging and monitoring. Logs, by necessity, need to contain information that could be valuable to an attacker. These need to be secured. Traditional applications would log into the file system, which adds a vulnerability. Instead, use something like cloud watch to aggregate your logs and secure them.
5. Secure your build machine. Great place to introduce vulnerabilities.
6. Using something like Jenkins, you need to do two things at a minimum.
A. Make sure Jenkins is secure and
B. Do not mount the Docker Socket from Jenkins to your production cluster. The Docker Socket in Linux is just a file running at the root. If you have access to that mount point, you can do what you want on that host.
7. Secure any serialization in your code; remember Citibank? But if what you are serializing, such as JSON, can grant any kind of privileges if modified, then you need to secure that thing, encrypt it, checksums, validate, etc. Typically, we suggest applications not do that kind of thing. Serialization is okay for things like user preferences, but if used for authorization, it can become an attack vector, as Citibank discovered.
Cyberark has a good article on CI/CD pipelines. From that article, you can see how complex they can be here:
To securely produce containerized applications, many people, products, and processes must be in place.
You can read the full article here: https://www.cyberark.com/what-is/ci-cd-pipeline/
This article was intended to introduce Docker Container security and the high-level things to be aware of and consider. We often hear prospects and customers tell us they want to implement microservices but don’t know where to start. It’s no surprise, given how complex setting everything up in a secure way can be.
If you’d like to discuss topics in this article, please hit the contact us button.