, , , ,

Integrate Image Scanning with GitHub Actions

Introduction

GitHub Actions is an automation tool that helps developers design their workflows by automating tasks, allowing the implementation of CI/CD directly from within the Github repositories.

Security is essential when managing production applications to protect data, prevent unauthorized access, and maintain the application integrity, integrating image scanning into the CI/CD process is not just beneficial but essential to ensure the readiness of your applications for production

In this article, I will cover how to implement CI/CD workflows using GitHub Actions, including building and testing an app, creating the image, and performing image scanning before creating a production image on a registry. This will encompass handling interdependencies and using a Marketplace Golang template.

The Basics of Github Actions

To get started with GitHub Actions, you can follow these initial steps:

  1. Create a GitHub Repository: If you haven’t done so already, create a new repository on GitHub or navigate to an existing one.
  2. Navigate to the Actions Tab: Once you’re in the repository, click on the “Actions” tab at the top of the page. This will take you to the GitHub Actions dashboard.
  3. Choose a Workflow Template: GitHub provides a variety of workflow templates to help you automate common development tasks. You can select a template that suits your needs or create a new workflow from scratch. To choose a template, click on “Set up a workflow yourself” or select one from the provided options.
  4. Configure the Workflow: After selecting a template or creating a new workflow, a YAML file will open in the editor. This file defines the steps and actions to be executed in the workflow. Customize the workflow by modifying the YAML file according to your requirements. You can specify triggers, define steps, and configure actions to perform specific tasks.
  5. Monitor Workflow Runs: You can monitor the status and progress of your workflows in the Actions tab. It will display details about each workflow run, such as the status, duration, and logs. This helps you track the execution of your workflows and identify any issues that may arise.

The workflow template will help create a boilerplate for the YAML files needed to address various topics, such as deploying on Azure, ECS, testing, and building with Golang, among many others

Each YAML file found under .github/workflows will create an independent workflow, or you can use a single one with dependencies between them. Each method has advantages and disadvantages, depending on your needs. For this post, I will be using the example-app repository, which contains a sample web server built with Golang and a Dockerfile to build the container

Single or Multiple Files: How to Choose the Right Approach for Your Needs

When using GitHub Actions, you can either organize your workflows in a single file with multiple jobs and actions or split them into multiple files.

The single file approach keeps all workflow definitions in one file, making it simpler to manage and understand. This is ideal for smaller projects with fewer workflows. It is also good for starter developers or organizations willing to test or build initial phase of projects.

The multiple files approach allows you to break down workflows into separate files, offering modularity and reusability. This is useful for larger projects with complex workflows or multiple teams collaborating. In a real world environment, this approach are used to leverage modularity and reusability it is also help to manage across large organization

Choose the approach that suits your project’s size and complexity, as well as your team’s collaboration preferences.

The Workflow

The workflow consists of four jobs: ‘test-code’, ‘build-and-push’, ‘compliance’, and ‘push-to-prod’. Here, you can find the repository and the files I am using for this laboratory.

GitHub Actions is flexible, allowing you to organize workflows as you see fit. Each file under .github/workflows will be interpreted as a separate workflow. For this tutorial, I will simplify the process by using a single file with multiple jobs

Tree hierarchy

.github
└── workflows
    └── release.yml

In the repository, under the Actions tab, you will see the job defined like this.

GH Actions in the repository

Workflows File

The files under .github/workflows are YAML files that define the workflows to be executed in blocks. The initial section indicates the event that triggers the action; in this case, it’s triggered every time a new push to the main branch is detected.

name: Release app
on:
push:
branches:
- main

In the jobs section, I defined the name of each job, the runner on which it will run, and the actions that it will perform.

jobs:
  test-code:
    runs-on: ubuntu-latest
    steps:
      - name: Set up Go
        uses: actions/setup-go@v4

      - name: Check out code
        uses: actions/checkout@v3

      - name: Test the app
        run: go test -v ./...

Every action under the steps section is performed on an Ubuntu-latest machine. There are other GitHub-hosted runners available, or you can use your own. Once the application has been successfully tested, it is time to build and push.

      - name: Build Image
        id: build-image
        uses: redhat-actions/buildah-build@v2
        with:
          image: ${{ env.APP_NAME }}
          tags: dev-${{ github.sha }}
          containerfiles: |
            ./Dockerfile

      - name: Push To Registry
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ steps.build-image.outputs.image }}
          tags: dev-${{ github.sha }}
          registry: ${{ env.REGISTRY }}/${{ env.ORG }}
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_PASSWORD }}

There are several actions in the Marketplace to consider. In the above example, it uses Buildah to build the application and Podman to push it to the registry with the tag ‘dev-${{ github.sha }}’, ready for scanning.

  compliance:
    name: Image Scan
    needs: [test-code, build-and-push]
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - name: Run Image Scan
        uses: aquasecurity/trivy-action@0.15.0
        with:
          image-ref: '${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.APP_NAME }}:dev-${{ github.sha }}'
          severity: 'CRITICAL'

As shown above, multiple action providers can be utilized to harness the best of each one. Aqua Security supports trivy-actions to integrate and implement image scanning in your GitHub Actions workflows. The scan will fail if it finds a critical vulnerability in the image.

This example also serves to introduce the ‘needs’ clause. It is used to create dependencies between jobs; the image scan cannot run if the code test or image building is still in progress. It will wait until the jobs specified in the ‘needs’ clause are completed.

If everything goes well, it is then time to promote to production.

      - name: Push to Prod
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ env.ORG }}/${{ env.APP_NAME }}
          tags: prod-${{ github.sha }}
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_PASSWORD }}

Managing Secrets

Like any other CI/CD tool, it is not expected for secrets or sensitive environment variables to be stored in plain text in the workflow files. Secrets can be accessed by prefixing ‘secrets.’ to the variable, as previously shown for the Docker login username and password. To create a secret, go to the repository settings.

Then create the secret

Secrets can now be accesed using the following format:

password: ${{ secrets.DOCKER_HUB_PASSWORD }}

Executing the workflow

From the moment you push the .github/workflow/release.yml file into the repository, every new push against the main branch will trigger the entire workflow. You can check the results or follow the process by going to the Actions tab and reviewing the name of the merged or pushed commit.

Given the dependencies created, the test-code and build-and-push steps will be executed independently and in parallel. This is an important consideration when evaluating the performance of large deployments or extensive lifecycles. You can check the result of each step; for example, the Image Scan.

2023-12-08T02:15:06.904Z	INFO	Detected OS: alpine
2023-12-08T02:15:06.904Z	WARN	This OS version is not on the EOL list: alpine 3.19
2023-12-08T02:15:06.904Z	INFO	Detecting Alpine vulnerabilities...
2023-12-08T02:15:06.905Z	INFO	Number of language-specific files: 0

By the end of the execution, the code is tested, the image is built, and the security scan performed. Both images should then be available in the registry.

Conclusion and Key Takeaways

In summary, GitHub Actions is a powerful tool for automating workflows in software development. With its versatile features and easy integration, you can create their processes and improve efficiency.

By the integration of GitHub Actions, you can automate tasks, run tests, build and deploy applications, and more. Overall, GitHub Actions is a valuable asset for modern development practices.

REFS:

Github Actions docs: https://docs.github.com/en/actions
Example app repository: https://github.com/gmontalvoy/example-app.git
Actions Marketplace: https://github.com/marketplace?type=actions

Leave a comment