Building Secure Docker Images

Normally, when we begin to develop microservices and build our Docker images, we use Dockerfiles by default and create the images using the root user. This, however, can be exploited as a vulnerability because the root user has access to the container and can modify its resources, meaning malicious code can be injected to run shell commands or volumes can be mounted using root permissions. 

 

To solve this problem we can create our own Docker images and set up a different user, preferably one with restricted permissions, to create or modify resources in the container. If it’s necessary to write over or mount a volume, a destined route must be specified in the container to be able to execute these actions.

Now that we’ve explained the theory, let’s see how it works in practice. You’ll need a virtual machine with Docker installed in order to run the following example; all sources used in this example can be found in my github repository. 

HR-Management-Theory-and-Practice-01-Nov-13-2020-11-42-17-07-PM

For this practical example, I have created a Java application in Micronaut, which is a JAR that will be built by the Dockerfile. This application creates a log to prove the permissions that have been assigned to the user.

To test this, we create our own container with a default Dockerfile using the root user by default. 

 

We run the following command:

docker build -t javawithroot

 

We run our container with the following command:

docker run -d -p 8099:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithroot

The java application demo uses logback and utilizes the “HOME_LOG” as the environment variable for the PATH log, which is set up the moment we run the environment (in this example, the “/opt/logs” route).  Additionally, we mount a volume related to the “/opt/logs” route, where we will save our application logs. 

Now we use SH to enter the container, and once inside, we execute “ps” and observe the processes that are being run by the root user.

 

To enter our container using SH, we enter the following command:

docker exec -it 2228fae46db9d5 /bin/sh

Where 2228fae46db9d5 is your container’s execution ID.

 

With the root user, we can create or manipulate files within the container.

If we verify our “/opt/logs” route, we will see that the root user has created a log:

Now we proceed to create our Docker image with the Dockerfile that was set to a different user (not the root user) with restricted permissions.

We build the image with the following command:

docker build -t javawithoutroot

Now we run the container with the same parameters as before:

To run our container, we execute the following command:

docker run -d -p 8098:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithoutroot

We need to write down our container’s execution ID.

 

If we view the container log, we will see a permission error when an attempt is made to save the log:

This happens because the “/opt/logs” in the main system does not have the required permission to overwrite it. 

To enter our container using SH, we enter:

docker exec -it c06036b4d01db9 /bin/sh

Where c06036b4d01db9 is your container’s execution ID.

 

Once we enter the container using SH, we can see that the processes within the container are no longer run by the root user. If we try to create a file, an ‘permission denied’ message will appear. 

 

To be able to save the “/opt/logs” route, we must give the main system the same permissions that were assigned when creating the image.

 

We enter the following command:

chown 1001:1001 -R /opt/logs/

 

To determine if the problem has been solved, it’s necessary to perform the following steps:

  1. First, we stop our container with the following comand:
    docker stop c06036b4d01d
     
    Where “c06036b4d01d” is the container’s execution ID.
  2. Run the container with the following command and write down the container’s execution ID:
    docker run -d -p 8098:8080 -v /opt/logs:/opt/logs — env HOME_LOG=/opt/logs javawithoutroot
  3. Verify that the problem doesn’t show up again with the following command:
    docker logs -f 3d9bb9b5561092b
     
    Where “f 3d9bb9b5561092b” is the container’s execution ID; with this, you make certain the permissions error does not come up again.
  4. Enter the “/opt/logs” route and verify that the log has been created. And that’s it, problem solved.

Recommendations

Once more, the sources used in this example can be found in my github repository.

By applying the example in this article, we can create safer applications and prevent vulnerabilities from being exploited by possible attacks. In addition to securing our Docker images (and if you are using a Kubernetes environment), we recommend you apply complementary security measures to ensure your cluster’s security.

 https://kubernetes.io/docs/concepts/policy/pod-security-policy/

 

idea-Nov-14-2020-05-00-39-57-PM

KEY TAKEAWAYS

  1. Creating Docker images using the root user can be a vulnerability due to its unrestricted permissions.
  2. This problem can be avoided by creating Docker images using a user with restricted permissions to create or modify the container’s resources.
  3. If it’s necessary to write over or mount a volume, a destined route must be specified in the container to be able to execute these actions.

 

Contact Us

Share this post