TutorialsLast Updated Apr 16, 20255 min read

Adding application and image scans to your CI/CD pipeline

Angel Rivera

Developer Advocate, CircleCI

Developer B sits at a desk working on an intermediate-level project.

Security is a shared responsibility. DevSecOps integrates security into the software development lifecycle (SDLC) to detect and resolve vulnerabilities early. Traditional security checks at the end of the development cycle often result in delays, rework, and inefficiencies. Embedding security into CI/CD pipelines mitigates these issues by automating scans and alerts, ensuring vulnerabilities are addressed promptly.

Adding security to CI/CD with Snyk

Snyk helps developers identify and fix vulnerabilities in applications and containers. CircleCI integrates with Snyk via the Snyk Orb, enabling seamless security scanning within CI/CD.

Setting up the project

To demonstrate the DevSecOps practices, we will create a simple Python(Flask) project with basic functionality, write unit tests using pytest, and integrate security scanning with Snyk.

Prerequisites

Ensure you have:

Creating the Flask project

Create a new directory and set up a basic Flask application:

mkdir circleci-flask-app && cd circleci-flask-app
python -m venv venv
source venv/bin/activate
pip install flask==0.12.4 Jinja2==2.10.1 pytest

This initializes a new project, creates a virtual environment, and installs Flask along with other required dependencies.

Next, create app.py with a simple route:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "Hello, Secure World!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

This is a minimal Flask application that serves a simple message when accessed via a browser.

Now create requirements.txt with the dependencies:

Flask==0.12.4
Jinja2==2.10.1
pytest

This defines the dependencies required for the application to run.

Deploying the project to CircleCI (without security)

To automate the build process and streamline deployments, we will integrate our Flask application with CircleCI. This initial setup focuses on ensuring that our application builds successfully without introducing any security checks. Later, we will enhance the pipeline with security scanning to detect vulnerabilities.

Create .circleci/config.yml and paste the following in it:

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/python:3.10
    steps:
      - checkout
      - run:
          name: Install Dependencies
          command: pip install --no-cache-dir -r requirements.txt
workflows:
  build_test_deploy:
    jobs:
      - build

At this stage, the pipeline will execute without running security checks, allowing the application to build successfully but leaving vulnerabilities undetected.

Commit and push your local changes to GitHub.

Next, login to CircleCI account, search for the project circleci-flask-app and click Set Up Project

Setting up project

Select fastest then click Set Up Project.

This will trigger the pipeline to run and build successfully.

Build successful

Again, nothing much to check here as the pipeline is running without security checks. Before we add security scanning, let’s add the Snyk API token to CircleCI as an environment variable. Click on the project settings -> Environment variables and add a new variable SNYK_TOKEN with the value of your Snyk API token.

Adding Snyk API token

You can find the Snyk API token in your Snyk account settings.

Snyk Auth Token

Adding Snyk security scan

Now modify the .circleci/config.yml file to include the Snyk Orb for security scanning:

version: 2.1
orbs:
  snyk: snyk/snyk@2.2.0
jobs:
  build_test:
    docker:
      - image: cimg/python:3.12.2
    steps:
      - checkout
      - run:
          name: Install Dependencies
          command: pip install --no-cache-dir -r requirements.txt
      - snyk/scan
workflows:
  build_test_deploy:
    jobs:
      - build_test

This introduces Snyk scanning to the pipeline. Since the project still uses outdated dependencies, the scan will fail. This is expected behavior as the goal is to identify and fix vulnerabilities. Push the changes to GitHub to trigger the pipeline.

Snyk scan failed

Fixing vulnerabilities and adding unit tests

At this stage, we will update our dependencies and introduce unit tests to verify that our application behaves as excpected. Updating dependencies ensures that we mitigate security risks, while unit tests provide a layer of validation.

Update the requirements.txt file to fix the vulnerabilities:

Flask
Jinja2
pytest

Now create test_app.py to verify the endpoint:

import pytest
from app import app

def test_home():
    client = app.test_client()
    response = client.get('/')
    assert response.status_code == 200
    assert response.data == b"Hello, Secure World!"

This test ensures that the home route is working correctly.

Modify .circleci/config.yml to re-run Snyk with updated dependencies and unit tests:

version: 2.1
orbs:
  snyk: snyk/snyk@2.2.0
jobs:
  build_test:
    docker:
      - image: cimg/python:3.12.2
    steps:
      - checkout
      - run:
          name: Install Fixed Dependencies
          command: pip install --no-cache-dir -r requirements.txt
      - snyk/scan
      - run:
          name: Run Unit Tests
          command: pytest
workflows:
  build_test_deploy:
    jobs:
      - build_test

This configuration now includes unit tests and ensures vulnerabilities are mitigated before deployment. Push the changes to GitHub to trigger the pipeline.

Snyk scan passed

Similarly, you can check the Snyk dashboard to confirm the vulnerabilities have been resolved.

Snyk Dashboard

Security enabled pipeline - the Snyk Docker image scan

After discussing Snyk’s application scanning capabilities, we now move on to Docker image scanning, which is seamlessly integrated into CI/CD pipelines. This step ensures that vulnerabilities within containerized applications are detected early, preventing insecure images from being deployed.

We will use the same Flask application and extend our pipeline by adding a Docker image build and scan step. If vulnerabilities are found, the image will not be pushed to Docker Hub, ensuring only secure images are deployed.

Create a Dockerfile in the root of the project:

FROM python:3.7.4
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir Flask==0.12.4 Jinja2==2.10.1 pytest
EXPOSE 5000
CMD ["python", "app.py"]

Next, update the .circleci/config.yml file to include the Docker image build and scan job:

version: 2.1
orbs:
  snyk: snyk/snyk@2.2.0
jobs:
  build_test:
    docker:
      - image: cimg/python:3.12.2
    steps:
      - checkout
      - run:
          name: Install Fixed Dependencies
          command: pip install --no-cache-dir -r requirements.txt
      - snyk/scan
      - run:
          name: Run Unit Tests
          command: pytest
  build_push_image:
    docker:
      - image: cimg/python:3.12.2
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build and Scan Docker Image
          command: |
            docker build -t $DOCKERHUB_USERNAME/app:latest .
      - snyk/scan:
          docker-image-name: "$DOCKERHUB_USERNAME/app:latest"
          monitor-on-build: true
      - run:
          name: Push Docker Image
          command: |
            docker build -t $DOCKERHUB_USERNAME/app:latest .
            echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USERNAME --password-stdin
            docker push $DOCKERHUB_USERNAME/app:latest
workflows:
  build_test_deploy:
    jobs:
      - build_test
      - build_push_image:
          requires:
            - build_test

Before you push the changes to GitHub, go back to CircleCI and add the following environment variables:

  • $DOCKERHUB_USERNAME (Docker Hub username)
  • $DOCKERHUB_PASS (Docker Hub password)

Now commit your changes and push to GitHub to trigger the pipeline.

As expected the pipeline will fail due to the known vulnerabilities in the base image. The Snyk Docker image scan will detect the vulnerabilities and fail the pipeline.

Snyk Docker image scan

You can fix the vulnerabilities by updating the base image in the Dockerfile to a non-vulnerable version.

FROM python:3.12-alpine
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir Flask Jinja2 pytest
EXPOSE 5000
CMD ["python", "app.py"]

Once you push the changes to GitHub, the pipeline will run successfully and the Docker image will be pushed to the Docker Hub.

Conclusion

By integrating security scans into the CI/CD pipeline, teams can proactively identify vulnerabilities in both application dependencies and Docker images. Using the Snyk Orb simplifies security integration, ensuring your applications and infrastructure remain protected.

The complete code for this tutorial is available on GitHub.

Copy to clipboard