Conftest: The path to more efficient and effective Kubernetes automated testing
Updated: Jul 15
This TeraTip is to take our DevSecOps pipelines to the next level! We are going to make use of Conftest. What is Conftest?
Conftest is a utility to help you write tests against structured configuration data. For instance, you could write tests for your Kubernetes configurations, Tekton pipeline definitions, Terraform code, Serverless configs or any other structured data.
Conftest relies on the Rego language from Open Policy Agent for writing policies. If you're unsure what exactly a policy is, or unfamiliar with the Rego policy language, the Policy Language documentation provided by the Open Policy Agent documentation site is a great resource to read.
We are going to make a brief demo to configure some rules on a Dockerfile. It's demo time!
1) Get familiar with the Rego language
2) Let's begin writing some rules for our Dockerfile. Execute the following
touch opa-docker-security.rego
Remember, don't forget the file extension, must be .rego
3) With your IDE of choice open the file and add the following rule
package main
# Do Not store secrets in ENV variables
secrets_env = [
"passed",
"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]) }
With this rule, we are checking for potential keys and sensible data within the Dockerfile. The list is secrets_env.
4) If you don't have a Dockerfile ready, then its time to create one.
touch Dockerfile
And write some content into it.
FROM adoptopenjdk/openjdk8:alpine-slim
WORKDIR /app
EXPOSE 8080
ARG ${JAR_FILE}=/app.jar
ENV tera-secret="secret"
RUN sudo apt-get upgrade
RUN curl https://www.teracloud.io/
COPY ${JAR_FILE} /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
5) With our Dockerfile and our OPA rules defined we can proceed to the scan. Execute the following.
docker run --rm -v $(pwd):/project openpolicyagent/conftest test --policy opa docker-security.rego Dockerfile
If you don't have the image locally it will pull it automatically. Make sure you run this command in the same directory where your Dockerfile lives.
The output shows.
Since our Dockerfile is not using the best practices, let's add some more rules in the next step.
6) Add more OPA rules!
Copy and paste the following on our .rego file
# 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
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
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] {
input[i].Cmd == "user"
val := input[i].Value
contains(lower(val[_]), forbidden_users[_])
msg = sprintf("Line %d: Do not run as root: %s", [i, val])
}
# 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])
}
7) Alright! now run again the Conftest.
The output shows:
We got some failures! remediate them on the next step.
8) Make your Dockerfile compliant with your established rules! (Try it out without seeing the solution).
New Dockerfile.
FROM adoptopenjdk/openjdk8:alpine-slim
WORKDIR /app
EXPOSE 8080
ARG JAR_FILE=target/*.jar
RUN addgroup -S pipeline && adduser -S sec-pipeline -G pipeline
COPY ${JAR_FILE} /home/sec-pipeline/app.jar
USER sec-pipeline
ENTRYPOINT ["java","-jar","/home/k8s-pipeline/app.jar"]
The output.
Awesome! We successfully added OPA rules for our Dockerfile!
Now, our Dockerfiles are going to be more secure and will follow the standards we established.
References
https://docs.docker.com/develop/develop-images/dockerfile_best-
practices/
Tomás Torales
Cloud Engineer
Teracloud
If you want to know more about Kubernetes, we suggest going check Enhance your Kubernetes security by leveraging KubeSec