As security threats are rapidly increasing in organizations they need to look into the security best practices to mitigate the potential risk. Nowadays, Docker Containers are being used everywhere to deploy the application, and the thing that we should always consider while working with Docker containers is how secure our containerized application is.? By default docker containers do not implement the container security best practices and to implement the container security we need to make sure the Dockerfile must follow all the docker container security best practices.
In this article, I am gonna walk you through an awesome open-source tool called OPA Conftest which will help you mitigate the potential risk that can come with the docker containers.
What is Conftest?
Conftest is a utility to help you write tests (statically analyze) against structured configuration data. Using Conftest you can write tests for,
Kubernetes Configurations, Terraform code, Dockerfile, or any other config files. Conftest uses the Rego language from Open Policy Agent for writing the assertions.
What is OPA (Open Policy Agent)?
The Open Policy Agent (OPA, pronounced “oh-pa”) is an open-source, general-purpose policy engine that unifies policy enforcement
across the stack. We can use OPA to enforce policies in microservices, Kubernetes, CI/CD pipelines, API gateways, and more. OPA policies are expressed in a high-level declarative language called Rego. Rego (pronounced “ray-go”) is purpose-built for expressing policies over complex hierarchical data structures.
Create the Rego policy for Dockerfile.
To make it easier for us we will use an already created rego policy which will contain all the OPA rules to statically analyze Dockerfiles to improve security.
Create a file name “dockerfile-security.rego” and copy-paste the following content mentioned below. Make sure the policy should reside where the project Dockerfile exists.
package main
# Do Not store secrets in ENV variables
secrets_env = [
"passwd",
"password",
"pass",
"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
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])
}
# Do not use 'latest' tag for base imagedeny[msg] {
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
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
warn[msg] {
input[i].Cmd == "run"
val := concat(" ", input[i].Value)
matches := regex.match(".*?(apk|yum|dnf|apt|pip).+?(install|[dist-|check-|group]?up[grade|date]).*", lower(val))
matches == true
msg = sprintf("Line: %d: Do not upgrade your system packages: %s", [i, val])
}
# Do not use ADD if possible
deny[msg] {
input[i].Cmd == "add"
msg = sprintf("Line %d: Use COPY instead of ADD", [i])
}
# Any user...
any_user {
input[i].Cmd == "user"
}
deny[msg] {
not any_user
msg = "Do not run as root, use USER instead"
}
# ... but do not root
forbidden_users = [
"root",
"toor",
"0"
]
deny[msg] {
command := "user"
users := [name | input[i].Cmd == "user"; name := input[i].Value]
lastuser := users[count(users)-1]
contains(lower(lastuser[_]), forbidden_users[_])
msg = sprintf("Line %d: Last USER directive (USER %s) is forbidden", [i, lastuser])
}
# Do not sudo
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])
}
# Use multi-stage builds
default multi_stage = false
multi_stage = true {
input[i].Cmd == "copy"
val := concat(" ", input[i].Flags)
contains(lower(val), "--from=")
}
deny[msg] {
multi_stage == false
msg = sprintf("You COPY, but do not appear to use multi-stage builds...", [])
}
There are a total of 10 docker container security policies mentioned which will define how secure your docker containers are by scanning your Dockerfile.
Run the OPA Conftest Scan.
Before moving to the next step make sure that Docker is pre-installed in your system. Otherwise, the following command will not work. But don’t worry if you don’t have Docker installed on your system you can go through the following OPA-Install link to install the binary according to your OS.
Now, to run the scan against your Dockerfile run the following command:-
$ sudo docker run - rm -v $(pwd):/project openpolicyagent/conftest test - policy dockerfile-security.rego Dockerfile
When you run the above command you will get an output similar to the below-mentioned screenshot. This will first install the latest OPA image and run the scan against your Dockerfile with the rego policy which you have created in the above step.
The scanned output will vary according to your Dockerfile configuration. In this case, I found five security issues in my Dockerfile I will fix this and run the scan again. In this way, you can check against your Dockerfile and resolve the potential Docker container security issues.
Conclusion:
In this article, we learn why Docker container security best practices should be considered and how we can improve. We have learned what is OPA and Confest. We have also learned how to use it to scan the Dockerfile to mitigate the potential Docker container security risks.
Follow-up
If you enjoy reading and would like to read more in the future. Please subscribe here and connect with me on LinkedIn.
You can buy me a coffee too🤎🤎🤎