This is the sixth day of my participation in the August Text Challenge.More challenges in August

Writing in the front

Ensuring the security of services and applications in containers is the key to containerization evolution. Container security involves the whole life cycle of application development and maintenance. This paper mainly looks at some security problems and countermeasures of Docker container from the perspective of image construction.


1. Permission management

1. Avoid running the container as root

In Openshift and K8S environments, the default container needs to run as a non-root USER, which is rare, so don’t forget to include the USER directive in the dockerfile to change the UID that is valid by default when the container is started to a non-root USER.

Run the two steps you need to do in Dockerfile as a non-root user:

  • Make sure thatUSERSpecified in the instructionThe userIt’s in the container.
  • Provide the appropriate file system permissions where the process will read or write.
FROM alpine # create directory, add myuser, Myuser RUN mkdir /server && adduser -d myuser && chown -r myuser /server USER myuser WORKDIR /server COPY myapp./  CMD ["./myapp"]Copy the code

2. The permission of the executable file must be owned by the root user but cannot be written

Every executable in a container should be owned by the root user, even if it is executed by a non-root user, and should not be globally writable.

By preventing the execution of user modifications to existing binaries or scripts, you can effectively reduce attacks and ensure container immutability. Immutable containers do not automatically update their code at run time, and in this way we can prevent running applications from being accidentally or maliciously modified.

When we use COPY

COPY --chown=myuser:myuser myapp./ #Copy the code

Two, reduce the attack surface

Avoid loading unnecessary packages, third-party applications, or exposing ports to reduce the attack surface. The more component content we include in the image, the more exposed the container becomes, and the more difficult it becomes to maintain.

1. Adopt a multi-phase build

As we discussed in The Dockerfile Multi-phase Build Practice, using multi-phase builds can reduce build complexity while effectively reducing image size.

In a multi-phase build, we create an intermediate container (phase) that contains compilation tools and generates the final executable. We then just copy the generated artifacts into the final image without additional development dependencies, temporary build files, and so on.

A well-designed multi-phase build contains only the minimum binaries and dependencies required in the final image, and no build tools or intermediate files. It is more secure and also reduces the mirror size. Can effectively reduce the attack surface, reduce the vulnerability.

For the implementation of multi-phase build, please refer to the previous article “Multi-phase Build practice of Dockerfile”.

2. Use a trusted mirror

If we are not building the image from scratch, the base image built on an untrusted or unmaintained image inherits all problems and vulnerabilities from that image to your container.

Basic mirror selection reference:

  • We should choose from a trusted repository and a verified official image.
  • When using a custom image, we should check the image source and the Dockerfile built. Going one step further, we should even build our own base image from this Dockerfile. Because there is no guarantee that images published in a common repository such as dockerHub are actually built from the specified Dockerfile. There’s no guarantee it’s up to date.
  • Sometimes an official image might not be inappropriate for security and minimalism, and the best solution would be to build our own image from scratch.

2. Build an image from scratch

If you build from a centos image, you may create containers that contain dozens or hundreds of vulnerabilities. So to build a secure image it’s best to know what the threats are to our base image. In production it is common to start with Scratch empty mirrors or distroless.

The distroless image contains only the application and its runtime dependencies. They do not include distributing applications such as package managers, shells, or any other programs in standard Linux distributions. The Distroless mirror is very small. The minimum distroless image gcr. IO /distroless/static is about 650 kB. It’s a quarter the size of Alpine (about 2.5 MB) and less than 1.5 percent of Debian (50 MB).

FROM golang:1.13-buster as build WORKDIR /go/ SRC /app ADD./ go/ SRC /app RUN go get -d -v. IO/Distroless /base-debian10 COPY -- FROM =build /go/bin/app/CMD ["/app"]Copy the code

IO /distroless/ Base-debian 10 contains only a basic set of packages, including only required libraries such as glibc, libssl, and openssl. Of course, for statically compiled applications like Go that don’t need liBC, we can replace them with the following base image

FROM gcr.io/distroless/static-debian10
Copy the code

More information about distroless base mirroring can be found at github.com/GoogleConta…

3. Update the image in time

Use frequently updated base images and refactor your images as needed. Sticking with the latest security patches is a common security best practice as new security vulnerabilities are constantly discovered.

Version control policy:

  • Stick with stable or long-supported versions that quickly provide security fixes.
  • Plan ahead. Be prepared to remove older versions and migrate before the base mirror version reaches the end of its life cycle or stops receiving updates.
  • Periodically rebuild your image to get the latest packages from base distributions, Node, Golang, Python, etc. Most package or dependency managers, such as NPM or Go Mod, will provide the latest security updates for a given version.

4. The port is exposed

Each open port in the container is a gate to the system. We should expose only the ports needed by the application and avoid exposing ports such as SSH (22).

We know that Dockerfile provides the EXPOSE command with an EXPOSE port, but this command is only used to provide information and for documentation purposes. The container does not automatically allow all EXPOSE port connections when you run it (unless you use Docker run –publish-all when you start the container).

When starting the container, the ports exposed through -p should be the same as those specified by the EXPOSE command in the Dockerfile for easier maintenance.


Management of sensitive data

1. Credentials and keys

Do not place credentials and keys in Dockerfile directives (in environment variables, parameters, or any other command).

When copying a file to an image, even if the file is deleted in subsequent instructions to Dockerfile, it is still accessible on the previous layer. Because of the mirror layering principle, your files are not actually deleted, just “hidden” in the final file system. Therefore, when building the image, we should follow the following steps:

  • If the application supportsConfigure using environment variables, we can use the docker run-eOption configuration, or useDocker secrets,Kubernetes secretsProvide values as environment variables.
  • Use the configuration file and mount it bound to docker, or use Kubernetes Secret.

The use of secrets will be introduced in detail in the following article.

2. The ADD, COPY

The ADD and COPY directives provide similar functionality in Dockerfile. But COPY is more explicit.

Unless we really need to use ADD, such as adding files from a URL or from a tar file. Otherwise, it is better to use COPY, whose results are more predictable and error-resistant.

In some cases, it is better to use the RUN command rather than ADD to download a package using curl or wget, unzip and delete the original file to reduce the number of layers.

3. Build context and DockerIgnore

We usually use it when building. As context

#docker build -t images:v1 .
Copy the code

We need to be careful when using. As a context, because the Docker CLI adds confidential or unnecessary files in context to daemons and even containers, such as configuration files, credentials, backups, lock files, temporary files, sources, subfolders, dot files, and so on.

In such as:

COPY . /server
Copy the code

At this point, everything in the directory is added to the image, including the Dockfile itself.

So the right thing to do is to create a folder that contains the files you want to COPY inside the container, use that as a build context, and specify the COPY directive where possible (avoid wildcards). Such as:

#docker build -t images:v1 build_files/
Copy the code

To eliminate unnecessary files, we can also create a.dockerignore file in which we explicitly exclude files and directories.


The above are common security issues and related measures during container construction. Container security covers a wide range of issues throughout the DevOPS process. Interested students can get involved in another dimension.

NEXT

  • Docker container Secrets
  • Docker container image size reduction practice

I hope the essay is helpful to you. Please correct me if there is any mistake in the content.

You are free to reprint, modify, publish this article, without my consent.