DEV Community

Cover image for 7 Tips to Secure your Docker Container
Vinod Kumar
Vinod Kumar

Posted on • Updated on

7 Tips to Secure your Docker Container

Introduction

In this article, I'm going to share 7 tips that I've included while developing a docker based spring boot micro-services application.

Prerequisites: You need to first know the basics of Docker that includes volumes, docker-compose and some basics of linux commands.
Objective: The main objective of this article is to make a container robust to avoid any kind of attack or vulnerabilities.

Table of contents

1. Running container as non-root user
2. Encrypting the secret information
3. Verifying base image source and the tag
4. Use volumes in appropriate mode like read-only mode etc.
5. Container capabilities should be only what needed or limited
6. Container shouldn’t be running in privileged mode
7. Defining resource consumption

1. Running container as non-root user

A process running in a container is no different from other processes running on Linux, except it has a small piece of metadata that declares that it’s in a container. Containers are not trust boundaries, so therefore, anything running in a container should be treated with the same consideration as anything running on the host itself.Just like you wouldn’t (or shouldn’t) run anything as root on your server, you shouldn’t run anything as root in a container on your server.

Dockerfile before the non-root user change

FROM openjdk:8u121-jre-alpine
COPY target/springboot-app-0.0.1-SNAPSHOT.jar springboot-app-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java","-jar","/springboot-app-0.0.1-SNAPSHOT.jar"]
Enter fullscreen mode Exit fullscreen mode

We’ve done the following changes

Dockerfile

FROM openjdk:8u121-jre-alpine
#adding a non-root user and group to the container’s system
RUN addgroup -g 2000 -S appuser && adduser -u 2000 -S appuser -G appuser -h /home/appuser
#setting our root directory because we cannot put contents on root now 
WORKDIR /home/appuser
#copying executable jar there from target folder
COPY target/springboot-app-0.0.1-SNAPSHOT.jar springboot-app-0.0.1-SNAPSHOT.jar
#changing owner and permissions of that jar
RUN chown appuser:appuser springboot-app-0.0.1-SNAPSHOT.jar && chmod 700 springboot-app-0.0.1-SNAPSHOT.jar
#specifying newly created user
USER appuser
#EXPOSE instruction is a way of documenting which ports are used, but does not actually map or open any ports. Exposing ports is optional.
EXPOSE 8080
#finally to execute the jar the ENTRYPOINT instruction is used
ENTRYPOINT ["java","-jar","springboot-app-0.0.1-SNAPSHOT.jar"]
Enter fullscreen mode Exit fullscreen mode

Note: On linux ports below 1024 can be opened only by root, so the port 80 is restricted by default. (reference1, reference2)

2. Encrypting the secret information

Since the docker secrets are only available to Docker Swarm, So we’ve stored encrypted sensitive information in volumes and decrypted the same at application level.
But if you're using Docker Swarm you can refer to this article

3. Verifying base image source and the tag

Verified and check trusted source along with the tag that we’re not using latest tag, you can use docker history command to check what's happening in the base or any image.

$ docker history openjdk:8u121-jre-alpine --no-trunc
Enter fullscreen mode Exit fullscreen mode

4. Use volumes in appropriate mode like read-only mode etc.

During the course of implementation of this point, the docker container mounted directory permissions are reflected as of host. So, In our case we’d assigned a non-root user appuser and the mounted filesystem wasn’t accessible inside the container to overcome this, one must apply permissions from another user that has access (like swadmin) to the directory that you want to be mounted inside the container.

To allow read-write access:

$chmod -R 777 test_vol_dir
Enter fullscreen mode Exit fullscreen mode

To allow read-only access:

$chmod -R 755 test_vol_dir
Enter fullscreen mode Exit fullscreen mode

Or you can adjust permissions to the mounted volume as per the requirement.

So this was the basic and important step now even docker also provides support for read-only access to mounted directories and to use this add :ro flag to volume description. (reference)

To mount a volume at runtime in windows:

docker container run -it --mount type=bind,src=D:/Tests/test_services/test_vol_dir,target=/home/appuser/apps -p 8998:8998 config:v1
Enter fullscreen mode Exit fullscreen mode

To mount a volume at runtime in linux:

docker container run -it --mount type=bind,src=/home/swadmin/test_vol_dir,target=/home/appuser/apps -p 8998:8998 config:v1
Enter fullscreen mode Exit fullscreen mode

To define the same in a compose file use “volumes” description. (reference)

5. Container capabilities should be only what needed or limited

Capabilities are distinct units of privilege that can be independently enabled or disabled.

Docker default list of capabilities available to privileged processes in a docker container:

chown, dac_override, fowner, fsetid, kill, setgid, setuid, setpcap, net_bind_service, net_raw, sys_chroot, mknod, audit_write, setfcap
Enter fullscreen mode Exit fullscreen mode

(reference)

A container should be specified with the least capabilities it needs and in to add or drop a capability use the following in the compose file.
services:

  config-service: 
    cap_drop:
      - ALL
    cap_add:
      - SETGID
      - SETUID
Enter fullscreen mode Exit fullscreen mode

To check the capabilities of container running on alpine:

$apk update
$apk add libcap
$capsh --print
Enter fullscreen mode Exit fullscreen mode

6. Container shouldn’t be running in privileged mode.

Having privileged containers is a security risk for any organization. It creates opportunities for malicious users to take control of the host system.

Allowing a container root access to everything on the system opens a window of opportunity for cyberattacks. A cyberattacker could connect to the host from the container and endanger the established infrastructure and configuration.

In short, Running in privileged mode indeed gives the container all capabilities. (reference)

To run a container in privileged mode add --privileged option:

$docker container run -it --privileged alpine
Enter fullscreen mode Exit fullscreen mode

Just to dig more you can check the capabilities of container running on alpine with --privileged option:
For this first run a alpine container and add libcap to alpine

$apk update
$apk add libcap
Enter fullscreen mode Exit fullscreen mode

And check the capabilities

$capsh --print
Enter fullscreen mode Exit fullscreen mode

Take a snapshot of the same

$docker commit <container-id> alpine-cap:latest
Enter fullscreen mode Exit fullscreen mode

Now

$docker container run --rm -it --cap-drop all alpine-cap
Enter fullscreen mode Exit fullscreen mode

Finally, check the capabilities after drop all

$capsh --print
Enter fullscreen mode Exit fullscreen mode

Read more: The most dangerous capability: SYS_ADMIN

7. Defining resource consumption

Once you have reasonable expectations for the application’s resource requirements, you should limit the application to using just those resources. This will help you contain the impact of runaway resource consumption due to functional bugs, security breaches, and mis-modeled algorithms. These limits can preserve resources for other applications and system operations running on the host.

Compose file configurations reference

services:
  config-service: 
    cap_drop:
      - ALL
    #cap_add:
     # - SETGID
      #- SETUID
    deploy: 
      resources:
        limits:
          cpus: '0.50'
          memory: 300M
        reservations:
          cpus: '0.25'
          memory: 200M
Enter fullscreen mode Exit fullscreen mode

Top comments (0)