7 Tips to Secure your Docker Container
Tags: docker, container, security, springboot
Published: 2021-02-19 21:33:16

# 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](#non-root-user)
######2. [Encrypting the secret information](#encrypt-secret-info)
######3. [Verifying base image source and the tag](#base-image-tag)
######4. [Use volumes in appropriate mode like read-only mode etc.](#use-vol-read-only)
######5. [Container capabilities should be only what needed or limited](#container-cap-drop)
######6. [Container shouldn’t be running in privileged mode](#container-privileged-mode)
######7. [Defining resource consumption](#container-resource-consumption)

<a id="non-root-user"></a>
##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*
```shell
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"]
```

*We’ve done the following changes*
#### **`Dockerfile`**

```shell
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"]
```
Note: On linux ports below 1024 can be opened only by root, so the port 80 is restricted by default. ([reference1](https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html), [reference2](https://stackoverflow.com/questions/33703965/how-can-i-run-a-spring-boot-application-on-port-80#answers))

<a id="encrypt-secret-info"></a>
##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](https://docs.docker.com/engine/swarm/secrets/)

<a id="base-image-tag"></a>
##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.
```shell
$ docker history openjdk:8u121-jre-alpine --no-trunc
```

<a id="use-vol-read-only"></a>
##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:
```shell
$chmod -R 777 test_vol_dir
```
To allow read-only access:
```shell
$chmod -R 755 test_vol_dir
```

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](https://docs.docker.com/storage/volumes/))

To mount a volume at runtime in windows:
```shell
docker container run -it --mount type=bind,src=D:/Tests/test_services/test_vol_dir,target=/home/appuser/apps -p 8998:8998 config:v1
```

To mount a volume at runtime in linux:
```shell
docker container run -it --mount type=bind,src=/home/swadmin/test_vol_dir,target=/home/appuser/apps -p 8998:8998 config:v1
```

To define the same in a compose file use “volumes” description. ([reference](https://docs.docker.com/compose/compose-file/#endpoint_mode))

<a id="container-cap-drop"></a>
##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
```
([reference](https://www.redhat.com/en/blog/secure-your-containers-one-weird-trick))

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:
```yml
  config-service: 
    cap_drop:
      - ALL
    cap_add:
      - SETGID
      - SETUID
```

To check the capabilities of container running on alpine:
```shell
$apk update
$apk add libcap
$capsh --print
```

<a id="container-privileged-mode"></a>
##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](https://containerjournal.com/topics/container-security/why-running-a-privileged-container-is-not-a-good-idea/))

To run a container in privileged mode add --privileged option:
```shell
$docker container run -it --privileged alpine
```

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
```shell
$apk update
$apk add libcap
```
And check the capabilities 
```shell
$capsh --print
```
Take a snapshot of the same
```shell
$docker commit <container-id> alpine-cap:latest
```
Now
```shell
$docker container run --rm -it --cap-drop all alpine-cap
```
Finally, check the capabilities after drop all
```shell
$capsh --print
```

Read more: [The most dangerous capability: SYS_ADMIN](https://www.redhat.com/en/blog/container-tidbits-adding-capabilities-container)

<a id="container-resource-consumption"></a>
##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](https://docs.docker.com/compose/compose-file/#resources) 
```yml
services:
  config-service: 
    cap_drop:
      - ALL
    #cap_add:
     # - SETGID
      #- SETUID
    deploy: 
      resources:
        limits:
          cpus: '0.50'
          memory: 300M
        reservations:
          cpus: '0.25'
          memory: 200M
```