📜 ⬆️ ⬇️

Increase Docker Container Security



- Sir, how did you get hacked?
- Not a way, but a container.
Old joke

All unnecessary components of a computer system can be a source of completely unnecessary vulnerabilities. Therefore, images of containers should, if possible, contain only what the application needs. And their size matters not only in terms of ease of distribution, but also cost of ownership and security. In this article we will talk about how to minimize the size and attack surface of Docker images, as well as about the tools to scan them for vulnerabilities.


Anyone who has worked at least a little with Docker has probably heard about the alpine image. It is based on the Alpine Linux distribution , which, compared to, for example, Debian or Ubuntu, with a basic image size of 5 MB leaves hackers with far fewer opportunities to attack. If the application can work in alpine, it will be a great way to optimize.


What about binary files? Can the application work autonomously? If yes, then there is reason to expect an additional reduction in size. As a base for images such as Debian and Ubuntu, scratch is usually used, but it can also make an application on golang. Gianluca Arbezzano has created a repository with minimal ready-made binary files. Let's try linux_386.




 curl -SsL https://github.com/gianarb/micro/releases/download/1.0.0/micro_1.0.0_linux_386 > micro 

We can include this binary file in the scratch image using the Dockerfile like this:


 FROM scratch ADD ./micro /micro EXPOSE 8000 CMD ["/micro"] 

 docker build -t micro-scratch . docker run -p 8000:8000 micro-scratch 

As a result, we managed to launch the http application in an image of only 5 MB in size, that is, we reduced it more than twice compared to the 12 MB that the image created on the basis of alpine occupies.


Scratch-image can not be used with any applications, but for the sake of reducing overhead costs should try.


For Ruby, ruby: 2.3-alpine can be used as the base. Ruby is installed from sources, not from the Alpine package. With semantic versioning, release 2.3 will receive security updates directly from developers.


Otherwise, you would have to independently install Ruby from source and monitor the release of new versions or use the package from Alpine and monitor its update by the developers of the distribution.


Notifications and Web Hooks


If a security update is issued for the base image, you need to update those images that are based on it. This is where MicroBadger notifications can help, which can be sent, for example, to Slack, as the guys from Microscaling do for official alpine and ruby ​​images.



')

They also use alerts to automatically start the build / reassembly procedure in the event of baseline changes. This functionality is also found in the Docker Hub, but Microscaling claims that MicroBadger is better, since it can be used with any system that supports web hooks (for example, CI or a security scanner).


Normal user


One of the key differences between virtual machines and containers is the use of the latest core system kernels. By default, Docker containers run as root, which can lead to serious problems in case of isolation breakthrough, since a compromised container running under root can get root access to the main system.


However, risks can be reduced by running the container as a regular user. Here's how to do this for a Rails application:


 #    WORKDIR /app #   Rails-   COPY . ./ #   ,      RUN addgroup rails && adduser -D -G rails rails \ && chown -R rails:rails /app USER rails 

Security scan


In addition to directly storing, container registries can scan images loaded into them for vulnerabilities. For example, Docker scans the security of official as well as custom images uploaded to the Docker Cloud.


To scan the security of the images, Quay.io uses Clair , an open source product from CoreOS. More recently, Alpine support has been added to Clair, which is actually very cool. Hopefully, this functionality will soon be available in Quay. In addition to Clair, there are TwistLock and Aqua scanners, but in most cases you have to pay to use them.


Clair is an application on Golang that implements a set of HTTP APIs for uploading, downloading, and analyzing images. Vulnerability data is downloaded from various sources, such as Debian Security Tracker or RedHat Security Data , and stored in Postgres. Clair works on the principle of a static analyzer, therefore, in order to scan a container, it does not need to be started - only the image file system is checked.


 docker run -it -p 5000:5000 registry 

Using this command, we launched our own registry to use as a source of images for scanning. Let's try uploading a micro image from Gianluca Arbezzano :


 docker pull gianarb/micro:1.0.0 docker tag gianarb/micro:1.0.0 localhost:5000/gianarb/micro:1.0.0 docker push localhost:5000/gianarb/micro:1.0.0 

Next, install Clair.


 mkdir $HOME/clair-test/clair_config cd $HOME/clair-test curl -L https://raw.githubusercontent.com/coreos/clair/v1.2.2/config.example.yaml -o clair_config/config.yaml curl -L https://raw.githubusercontent.com/coreos/clair/v1.2.2/docker-compose.yml -o docker-compose.yml 

Register in $HOME/clair_config/config.yml your database connection settings postgresql://postgres:password@postgres:5432?sslmode=disable


To run Postgres and Clair, run the following command:


 docker-compose up 

To facilitate the testing procedure, we will use a CLI called Hyperclair (this is the client for working with Clair). The following are commands for Mac OS (if you are using another OS, see https://github.com/wemanity-belgium/hyperclair/releases ):


 curl -SSl https://github.com/wemanity-belgium/hyperclair/releases/download/0.5.2/hyperclair-darwin-386 > ~/hyperclair chmod 755 ~/hyperclair 

Now we have an executable file in ~ / hyperclair:


 ~/hyperclair pull localhost:5000/gianarb/micro:1.0.0 ~/hyperclair push localhost:5000/gianarb/micro:1.0.0 ~/hyperclair analyze localhost:5000/gianarb/micro:1.0.0 ~/hyperclair report localhost:5000/gianarb/micro:1.0.0 

The generated report looks like this:




Remove potentially vulnerable build dependencies of a Rails application


Since Ruby is an interpreted language, when using the application written on it we get a decent amount of dependencies. We need to install all the Ruby gems that are needed for our application, and all the operating system packages needed for these gems.


Suppose a scan reveals critical vulnerabilities in libxml2 and libxslt. These are the buildtime dependencies of the Nokogiri heme, which is an XML and JSON parser. In order to increase performance, this heme uses extensions written in C that require compilation. But after the gem is installed, libxml2 and libxslt are no longer needed.


Let's remove all buildtime dependencies:


 #     WORKDIR /tmp ADD Gemfile* /tmp/ #       #         apk- RUN apk update && apk upgrade && \ apk add --no-cache $RUBY_PACKAGES && \ apk add --no-cache --virtual build-deps $BUILD_PACKAGES && \ bundle install --jobs 20 --retry 5 && \ apk del build-deps 

Due to caching of Gemfile and Gemfile.lock in / tmp , the bundle install command will be launched only if the Gemfile changes. Otherwise, the Docker cache will be used. Such optimization allows to reduce the execution time and the load on the network, which can be quite large when installing gems.


Notice that the run command is multi-line, and therefore only one layer is added to the image. Packages required for building are installed with the --virtual key, and they are easy to remove after the process is complete.


Automated build


From the point of view of container security, it is extremely important to reassemble them every time an update of the image itself or one of those that underlies it appears. Automation of this procedure can be based on binding to the git repository: in this case, the build starts after creating a new commit in the monitored branch. As mentioned above, an assembly can also be run on a base image change event.


In the case of Ruby, the situation is simplified, since we can take the same Dockerfile files that were used in the creation process. For Go programs, you first need to compile a binary file, and then add it to the image. You can use makefiles locally for this.


An alternative would be to compile a binary file for an event in a docker container. I recommend looking at a couple of golang-builder images from CenturyLinkLabs and Prometheus .


To start the build process, you can use build hooks, which are also convenient for adding dynamic metadata to images .




Conclusion


So, we briefly examined how to minimize Docker images for Go and Ruby applications, learn how to run containers as a regular user, set up a security scan using Clair, and talked a little about automatic reassembly. I hope these simple steps will help improve the security of your Docker containers. That's all for now. Thanks for attention!


List of sources:


  1. https://medium.com/microscaling-systems/dockerfile-security-tuneup-166f1cdafea1#.a24qq9tv7
  2. http://gianarb.it/blog/about-your-images-security-tips

Source: https://habr.com/ru/post/320552/


All Articles