
Having recently instantiated a full, multi-container docker environment my question quickly became this – how is this made more secure? What are the best practices?
Out of the box, the Docker service runs as root as well as the hosted containers. The majority of container images also include many generic Linux tools (think vim, df, ls etc.) to allow for easier maintenance and additional flexibility with OOTB images without requiring the compilation of a custom image. This not causes the containerization of the hosted services to become load-bearing from a security perspective to prevent escalation in the local host but also provides additional tools to investigate for potential attack surfaces. Combined with poor implementation practices or outdated services and there becomes a tangible risk of compromise.
In the sense of full disclosure, the OOTB configuration of Docker will present a minimal risk to small-scale personal implementations, given that a regular update cadence and standard best practices are observed. The included tools as well as running under UID/GID 0 (root) allows far greater flexibility and fewer snags during implementation, particularly in reference to file permissions inherited via bind-mounts or shared with other containers. However, in production services neither of these pieces are necessary and can be stripped out without adversely affecting the hosted service in the overwhelming majority of cases, providing a large uplift in terms of attack surface reduction, vulnerabilities and restrictions of lateral movement .
Fortunately, Docker performs the hard work already with thousands of popular container images having pre-hardened versions hosted on dhi.io. For enterprise use these containers are not free (and better vendors are available with hardened solutions), although Docker has recently made the entire catalog free to use for personal use.
Docker advertises these images as containing ‘nearly zero’ CVEs with full trust of the upstream supply chain due to SLSA Level-3 Compliance. The philosophy behind these images is enterprise-focused, the idea being to start from zero and build on only what is needed, as opposed to deploying full ‘fat’ images and incessantly hunting down exploits and attack surfaces. The production-tagged versions of these containers run rootless (generally under UID/GID 65532) and building on the bottom-up approach do not include bash or other pieces not essential to the services operation. The effective result is an image with minimal attack surface, no elevated permissions and a promise of ‘nearly zero’ exploits present within the container. Seems like a no-brainer right?
This is where we return to the argument surrounding ease of use and deployment in personal vs. enterprise environments. Hardened Images can be quite difficult to work with if your environment deviates from the standard or depends on any workarounds in it’s implementation. The implication is poor practices, however DHI images do pigeonhole the implementer into best practices with nearly no wiggle-room. The largest issue I have encountered has been file permissions and ensuring a best-practice strategy while accommodating multiple containerized and system-level services & scripts working within the same ecosystem under different contexts. Many users and guides recommend permissive strategies to allow these pieces to interact successfully but can introduce serious concerns into the environment, such as NGINX private keys set to 755 (!). This often requires a background of technical knowledge in the technology in question to ensure the proverbial front door is not locked while leaving the window wide open.
Given these constraints are workable in a given environment, DHI will make sense where available to ensure minimal attack surface and that the full benefits of containerized security are realized. While implementation will require consideration of additional technical blockers, the net gain will outweigh the initial overhead of implementation.