Dockerfile Security Best Practice
Posted tiantiandas
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dockerfile Security Best Practice相关的知识,希望对你有一定的参考价值。
Reference: [Dockerfile Security Best Practices]
Container security is a broad problem space and there are many low hanging fruits one can harvest to mitigate risks. A good starting point is to follow some rules when writing Dockerfiles.
I’ve compiled a list of common security issues and how to avoid them. For every issue I’ve also written an Open Policy Agent (OPA) rule ready to be used to statically analyze your Dockerfiles with conftest. You can’t shift more left than this!
You can find the .rego
rule set in this repository. I appreciate feedback and contributions.
Do not store secrets in environment variables
Secrets distribution is a hairy problem and it’s easy to do it wrong. For containerized applications one can surface them either from the filesystem by mounting volumes or more handily through environment variables.
Using ENV
to store secrets is bad practice because Dockerfiles are usually distributed with the application, so there is no difference from hard coding secrets in code.
How to detect it:
secrets_env = [
"passwd",
"password",
"pass",
# "pwd", can‘t use this one
"secret",
"key",
"access",
"api_key",
"apikey",
"token",
"tkn"
]
deny[msg] {
input[i].Cmd == "env"
val := input[i].Value
contains(lower(val[_]), secrets_env[_])
msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val])
}
Only use trusted base images
Supply chain attacks for containerized application will also come from the hierarchy of layers used to build the container itself.
The main culprit is obviously the base image used. Untrusted base images are a high risk and whenever possible should be avoided.
Docker provides a set of official base images for most used operating systems and apps. By using them, we minimize risk of compromise by leveraging some sort of shared responsibility with Docker itself.
How to detect it:
deny[msg] {
input[i].Cmd == "from"
val := split(input[i].Value[0], "/")
count(val) > 1
msg = sprintf("Line %d: use a trusted base image", [i])
}
This rule is tuned towards DockerHub’s official images. It’s very dumb since I’m only detecting the absence of a namespace.
The definition of trust depends on your context: change this rule accordingly.
Do not use ‘latest’ tag for base image
Pinning the version of your base images will give you some peace of mind with regards to the predictability of the containers you are building.
If you rely on latest you might silently inherit updated packages that in the best worst case might impact your application reliability, in the worst worst case might introduce a vulnerability.
How to detect it:
deny[msg] {
input[i].Cmd == "from"
val := split(input[i].Value[0], ":")
contains(lower(val[1]), "latest"])
msg = sprintf("Line %d: do not use ‘latest‘ tag for base images", [i])
}
Avoid curl bashing
Pulling stuff from internet and piping it into a shell is as bad as it could be. Unfortunately it’s a widespread solution to streamline installations of software.
wget https://cloudberry.engineering/absolutely-trustworthy.sh | sh
The risk is the same framed for supply chain attacks and it boils down to trust. If you really have to curl bash, do it right:
- use a trusted source
- use a secure connection
- verify the authenticity and integrity of what you download
How to detect it:
deny[msg] {
input[i].Cmd == "run"
val := concat(" ", input[i].Value)
matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1)
count(matches) > 0
msg = sprintf("Line %d: Avoid curl bashing", [i])
}
Do not upgrade your system packages
This might be a bit of a stretch but the reasoning is the following: you want to pin the version of your software dependencies, if you do apt-get upgrade
you will effectively upgrade them all to the latest version.
If you do upgrade and you are using the latest
tag for the base image, you amplify the unpredictability of your dependencies tree.
What you want to do is to pin the base image version and just apt/apk update
.
How to detect it:
upgrade_commands = [
"apk upgrade",
"apt-get upgrade",
"dist-upgrade",
]
deny[msg] {
input[i].Cmd == "run"
val := concat(" ", input[i].Value)
contains(val, upgrade_commands[_])
msg = sprintf(“Line: %d: Do not upgrade your system packages", [i])
}
Do not use ADD if possible
One little feature of the ADD
command is that you can point it to a remote url and it will fetch the content at building time:
ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz
Ironically the official docs suggest to use curl bashing instead.
From a security perspective the same advice applies: don’t. Get whatever content you need before, verify it and then COPY
. But if you really have to, use trusted sources over secure connections.
Note: if you have a fancy build system that dynamically generate Dockerfiles, then ADD
is effectively a sink asking to be exploited.
How to detect it:
deny[msg] {
input[i].Cmd == "add"
msg = sprintf("Line %d: Use COPY instead of ADD", [i])
}
Do not root
Root in a container is the same root as on the host machine, but restricted by the docker daemon configuration. No matter the limitations, if an actor breaks out of the container he will still be able to find a way to get full access to the host.
Of course this is not ideal and your threat model can’t ignore the risk posed by running as root.
As such is best to always specify a user:
USER hopefullynotroot
Note that explicitly setting a user in the Dockerfile is just one layer of defence and won’t solve the whole running as root problem.
Instead one can — and should — adopt a defence in depth approach and mitigate further across the whole stack: strictly configure the docker daemon or use a rootless container solution, restrict the runtime configuration (prohibit --privileged
if possible, etc), and so on.
How to detect it:
any_user {
input[i].Cmd == "user"
}
deny[msg] {
not any_user
msg = "Do not run as root, use USER instead"
}
Do not sudo
As a corollary to do not root
, you shall not sudo either.
Even if you run as a user make sure the user is not in the sudoers
club.
deny[msg] {
input[i].Cmd == "run"
val := concat(" ", input[i].Value)
contains(lower(val), "sudo")
msg = sprintf("Line %d: Do not use ‘sudo‘ command", [i])
}
Acknowledgements
This work has been inspired and is an iteration on prior art from Madhu Akula.
以上是关于Dockerfile Security Best Practice的主要内容,如果未能解决你的问题,请参考以下文章
Professional-Cloud-Security-Engineer Exam Cram - Best Way Pass Professional Cloud Security Engineer
Best practices for writing Dockerfiles
docker中使用Dockerfile部署springboot项目
Docker mongodb Dockerfile ubuntu
已弃用 Spring Boot 应用程序的 java.security.egd=file:/dev/./urandom?