Build secure cloud-native applications by avoiding the top five security pitfalls we lay out in our Secure Cloud-native Development Series. This blog is the first part of the series, and it will teach you how to choose secure container images.
When it comes to building secure cloud-native applications, the baseline is choosing a secure container image. Docker defines a container as “a standard unit of software that packages up code and all its dependencies, so the application runs quickly and reliably from one computing environment to another.” The problem is, they’re often a pain point for many developers. Journey with us through real examples from the Age of Empires 2 API Project as you learn three ways to ease some pain and strengthen your security posture with container images: pinning, slimming, and updating.
Rather than choosing the “latest” container image, you should be pinning to a specific version of the image. For example: ubuntu:20.04, or python:3.10.6-alpine3.16. Version pinning forces the build to retrieve the specified version. It also reduces build failures that can result from unanticipated changes in required packages.
Why is it important?
If you pull the ubuntu:latest image, what do you get? The answer is: it depends. It depends on the day you pull the image. This can destroy consistency across your environments. As previously stated, it can reduce build failures. Additionally, rule #11 under the Docker Security Cheatsheet states: “Ensure the base image version is pinned”.
Docker also states: “A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings.” As it is designed to be lightweight, it’s best to not complicate things by adding unused packages or picking a bloated image to start with. Run an Alpine image or a lightweight image where possible. For example, if you require python3 for your container, you can choose the python3 alpine container. You can then add in the required packages and libraries needed for this case.
Here we will begin working with an example project of the Age of Empires 2 API project. This open-source project displays stats about each civilization in the game. Looking at the Docker file we can see it uses a custom image:
Pulling the image, you can see it’s definitely not using a slim flavor, and it also hasn’t been updated in more than three years.
To get something you can work with, fork the repo and remove some of the non-essential code for this project. Then, you’ll convert it over to an alpine flavor for the image. As you can see below, you’re starting with a very minimal container image.
FROM python:3.10.6-alpine3.16 COPY . /app WORKDIR /app RUN apk add --–no-cache g++ RUN pip install -r requirements.txt ENTRYPOINT [ "“python"”, "“app.py"” ]
First, you need to scan the base image for vulnerabilities to make sure you're starting with a clean image. When scanning this image, you can see one critical vulnerability. Based on the CVE, you should be able to update that core package and add additional packages that are required for your python API project to build your minimal container image.
Why is it important?
If you run a bloated container image, you run a higher risk of having vulnerable libraries in your container, which will make its way into your production environment. The scan of the original image is around four times larger than this new image.
It also has more than 6k lines of vulnerabilities!
Less is more in the Docker-world. Running slimmer images, and running only what you need in your images, makes them much easier to maintain. There are many great tools, both commercial and open source, that can help you have a broader view of the landscape regarding your containers. We will discuss these shortly.
Going back to our example from the Age of Empires 2 API project, the initial image contained a CVE within one of the APKs. This can be handled by just upgrading the package.
FROM python:3.10.6-alpine3.16 COPY . /app WORKDIR /app RUN apk add --–no-cache g++ && apk --–update --–no-cache upgrade RUN pip install -r requirements.txt
Once built, we can scan again to confirm we have a clean image.
Excellent! Now there’s a clean image to work with. Your software development lifecycle should not only have approved container images, but it should also have a process and cadence to keep them up to date. By scanning an older container image for vulnerabilities, you can see a handful listed below.
It looks like it might be time to bump up to an updated version.
When the time comes to update, pull a newer base image then configure your image from there.
Why is it important?
Remember Log4J? Log4J helped everyone see the critical need to pay attention to open-source libraries and update them regularly. Luckily, there are an assortment of tools that can help give us insight into not only the security risk but also provide a Software Bill of Materials (SBOM) for your applications.
Which tools can help us?
There are many tools that can be used to secure containers. Some tools scan your images upon being pushed to the repository, such as in AWS ECR and Azure’s ACR. You can also scan your images locally. Veracode has some tools that can assist with this.
Veracode Container Security
Veracode Container Security is a dev-first tool that integrates security into your everyday workflow. The scan operates as a command line tool that can be run in the CI/CD pipeline or on a developer’s local machine. Developers can deploy secure containers faster, prevent comprehensive vulnerabilities before runtime, and get contextual results to remediate effectively. In the images from our example above, you can see this tool being used.
Using our API, you can generate a Software Bill of Materials (SBOM) that you can then parse. An SBOM contains an inventory of all the libraries and software components of your application, and is now required for vendors contracting with the federal government. SBOMs are also quickly becoming a mandatory element of many security programs, not just government contractors.
Containers are a great portable and consistent way to deploy apps, but regardless of how secure our source code may be, an insecure container image can introduce high severity vulnerabilities to our stack. Take the time to create secure lightweight container images that have a process in place for being regularly updated.