GitLab CI’s container scanning feature can detect Common Vulnerabilities and Exposures (CVEs) in your container images, but it’s not magic. It relies on external vulnerability databases and a specific scanning tool to do the heavy lifting.
Here’s a look at the system in action, using a simple .gitlab-ci.yml file:
stages:
- build
- scan
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build_image:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
scan_image:
stage: scan
image: registry.gitlab.com/security-products/container-scanning:latest
script:
- echo "Scanning image $IMAGE_TAG"
- /analyzer run
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
When this pipeline runs, the build_image stage uses docker:20.10.16 to build a container image from the current directory (.) and tags it with the GitLab registry path and the commit SHA. It then logs into the GitLab container registry and pushes the built image.
The scan_image stage is where the magic happens. It uses the registry.gitlab.com/security-products/container-scanning:latest image, which contains the container scanning analyzer. The script section simply echoes a message and then executes /analyzer run. This command triggers the scanning process. The results are then saved to gl-container-scanning-report.json, which GitLab CI recognizes as a container scanning report artifact.
The Mental Model
The core problem GitLab CI’s container scanning solves is the inherent complexity of managing software dependencies within containerized applications. As your application evolves, so do the libraries and base operating system packages it relies on. Each of these components can have security vulnerabilities (CVEs) that attackers can exploit. Manually tracking and updating these dependencies across all your container images is a tedious and error-prone process.
GitLab CI’s container scanning automates this by integrating a security scanner directly into your CI/CD pipeline. Here’s how it works internally:
- Image Retrieval: The scanning tool, by default, pulls the image specified in the
artifacts:reports:container_scanningconfiguration (or, if not explicitly set, it looks for the image built in a preceding stage that matches a predefined naming convention). - Dependency Analysis: The scanner then unpacks the image and analyzes its contents. It identifies the installed packages (e.g., from
apt,apk,yum,npm,pip,gem). - Vulnerability Database Lookup: For each identified package and its version, the scanner queries one or more external vulnerability databases (like the National Vulnerability Database (NVD), Debian Security Advisories, Alpine Linux Security Advisories, etc.).
- Report Generation: If a CVE is found that affects an installed package, the scanner generates a report detailing the vulnerability, the affected package, its version, and the fixed version. This report is typically in JSON format.
- Artifact Upload: GitLab CI collects this report artifact and makes it available in the pipeline’s "Security" tab, providing a clear overview of any found vulnerabilities.
The key levers you control are:
- The Base Image: The choice of your base image is paramount. A well-maintained, minimal base image from a trusted source will have fewer potential vulnerabilities to begin with.
- Package Management: How you install packages within your Dockerfile directly impacts what the scanner can find. Using your distribution’s package manager (
apt,apk) is generally well-supported. - Dependency Pinning: While not directly controlled by the scanner configuration, pinning specific versions of application dependencies (e.g., in
package.jsonorrequirements.txt) can help ensure reproducible builds and make it easier to track down specific vulnerable versions. - Scanner Configuration (Advanced): For more complex setups, you can configure specific analyzers to use, adjust the vulnerability sources, or set severity thresholds.
The container scanning tool doesn’t actually execute your container or its applications. It’s a static analysis tool that inspects the filesystem and package manifests within the image layers. This means it can find vulnerabilities in the operating system packages and language-specific dependencies that are present at build time, but it won’t detect runtime vulnerabilities or misconfigurations.
When you push an image to the GitLab Container Registry, GitLab itself performs a scan. This is separate from the CI job but uses a similar underlying mechanism. The CI job scan provides an opportunity to fail the pipeline before the image is pushed, offering an earlier feedback loop.