How to Embed Trivy into GitHub Actions
Use Trivy tool to scan containers for vulnerabilities in GitHub Actions
A simple CI/CD pipeline
You’ve learned about CI/CD systems using GitLab and Jenkins. Both are good systems, but they also have different features and use cases. We will look into another CI/CD system named GitHub Actions that debuted on 13 November 2019. GitHub Actions is a CI/CD system that is built-in into GitHub with free and paid offerings.
Let’s get started!
1. Create a new repository
If you haven’t registered for a GitHub account, please sign up for an account here
First, we need to create a repository in our GitHub account by visiting https://github.com/new.
Create a repository named django.nv
, you can also check the box with Public or Private options, and please ignore Initialize this repository with a section for now.
Click the Create repository button.
2. Create a Personal Access Token (PAT)
Next, we will create and use PAT for git authentication in DevSecOps Box because GitHub will not support account passwords starting in August 2021.
Let’s create PAT by visiting https://github.com/settings/tokens,then click Generate new token button and give your token a name e.g. django
.
Select the repo option to access repositories from the command line, and scroll down to generate a new token.
The token will have a format like this ghp_xxxxxxxxx.
Once you have the token, please copy and save it as a file in DevSecOps Box, so we can use the token whenever needed.
3. Initial git setup
To work with git repositories via Command Line Interface(CLI), aka terminal/command prompt, we need to set up a user and an email. We can use the git config command to configure the git user and email.
git config --global user.email "your_email@gmail.com"
git config --global user.name "your_username"
You need to use your email and username, which are registered in GitHub.
Please don’t use your company’s GitHub credentials or token to practice these exercises.
4. Download the repository
Let’s start by cloning django.nv in DevSecOps Box.
git clone https://gitlab.practical-devsecops.training/pdso/django.nv.git
By cloning the above repository, we created a local copy of the remote repository.
Let’s cd into this repository to explore its content.
cd django.nv
Since this repository was cloned from Gitlab, the remote URL of this Git repository is going to point to the GitLab URL. Let’s rename the repository’s Git URL to point to GitHub, enabling us to push our code to GitHub.
git remote rename origin old-origin
In the command below, please change “username” with your GitHub username.
git remote add origin https://github.com/username/django.nv.git
Let’s check the status of our git repository.
git status
On branch master
Your branch is up to date with 'old-origin/master'.nothing to commit, working tree clean
We are in the master branch and we need to create one more branch called main as a default branch.
git checkout -b main
Why do we need a new branch? Because in this exercise we will use the main branch as a control to run the pipeline in every commit. If you don’t do this, you will not be able to see any pipeline in your repository.
Read more about Renaming the default branch from master.
Then, let’s push the code to the GitHub repository.
git push -u origin main
And enter your GitHub credentials when prompted (please use Personal Access Token as a password), then the code will be pushed to the GitHub repository.
5. Add a workflow file to the repository
To use GitHub Actions, you need to create .github/workflows directory and create a new YAML file named demo.yaml
or any other desired name because each file in the .github/workflows directory which has a .yaml extension will define a workflow.
Let’s create a simple workflow by entering the following commands in DevSecOps Box.
mkdir -p .github/workflows
cat >.github/workflows/demo.yaml<<EOF
name: Django # workflow nameon:
push:
branches: # similar to "only" in GitLab
- mainjobs:
build:
runs-on: ubuntu-latest # similar to "image" in GitLab
steps:
- run: echo "This is a build step" # similar to "script" in GitLab test:
runs-on: ubuntu-latest
needs: build
steps:
- run: echo "This is a test step" integration:
runs-on: ubuntu-latest
needs: container_scanning
steps:
- run: echo "This is an integration step"
- run: exit 1
continue-on-error: true prod:
runs-on: ubuntu-latest
needs: integration
steps:
- run: echo "This is a deploy step"
EOF
If you are not comfortable with the syntax, explore the GitHub Actions syntax athttps://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows
Let’s add this file to the repository and commit the changes.
git add .github/workflows/demo.yaml
git commit -m "Add github workflows"
6. Push the changes to the repository
Since git is a decentralized source code management system, all changes are made in your local git repository. You have to push these changes to the remote server for the committed changes to reflect on the remote git repository.
Let’s push the changes to the remote git repository using the git push command.
git push origin main
Counting objects: 5, done.
Delta compression using up to 16 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 577 bytes | 577.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/username/django.nv.git
df066a2..98e754f main -> main
Any change to the repo will kick-start the pipeline.
We can see the result of the pipeline by visiting our django.nv repository. Click the Actions tab, and select the appropriate workflow name to see the output.
You will notice that the integration has exit 1 and hence failed the job, but other jobs are still running. Why?
You can find more details at https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions#jobs.
Let’s move to the next step.
Embed Trivy in GitHub Actions
In GitHub Actions, we can use the Trivy scan actions available from the GitHub Marketplace.
Go back to the DevSecOps Box machine, and copy the below content to the .github/workflows/main.yaml file under the test job.
container_scanning:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v2 - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 - name: build local container
uses: docker/build-push-action@v2
with:
tags: django.nv:${{ github.sha }}
push: false
load: true - name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'django.nv:${{ github.sha }}'
format: 'json'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'MEDIUM'
Please visit the following link to see the details of Aqua Security Trivy action here.
Commit, and push the changes to GitHub.
Any change to the repo will kick-start the pipeline.
We can see the result of the pipeline by visiting our django.nv repository, clicking the Actions tab, and selecting the appropriate workflow name to see the output.
As we used action instead of the docker run command, the result of the scan will be available in the Artifacts section.
Let’s move to the next step.
Allow the job failure
You do not want to fail the builds in DevSecOps Maturity Levels 1 and 2. If a security tool fails a build upon security findings, you would want to allow it to fail and not block the pipeline as there would be false positives in the results.
You can use the continue-on-error syntax to not fail the build even though the tool found security issues.
container_scanning:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v2 - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 - name: build local container
uses: docker/build-push-action@v2
with:
tags: django.nv:${{ github.sha }}
push: false
load: true - name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'django.nv:${{ github.sha }}'
format: 'json'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'MEDIUM'
continue-on-error: true # allow the build to fail, similar to "allow_failure: true" in GitLab
The final pipeline would look like the following:
name: Django # workflow nameon:
push:
branches: # similar to "only" in GitLab
- mainjobs:
build:
runs-on: ubuntu-latest # similar to "image" in GitLab
steps:
- run: echo "This is a build step" # similar to "script" in GitLab test:
runs-on: ubuntu-latest
needs: build
steps:
- run: echo "This is a test step" container_scanning:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v2 - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 - name: build local container
uses: docker/build-push-action@v2
with:
tags: django.nv:${{ github.sha }}
push: false
load: true - name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'django.nv:${{ github.sha }}'
format: 'json'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'MEDIUM'
continue-on-error: true # allow the build to fail, similar to "allow_failure: true" in GitLab integration:
runs-on: ubuntu-latest
needs: container_scanning
steps:
- run: echo "This is an integration step"
- run: exit 1
continue-on-error: true prod:
runs-on: ubuntu-latest
needs: integration
steps:
- run: echo "This is a deploy step"
Go ahead and add the above content to the .github/workflows/main.yaml file to run the pipeline.
As discussed, any change to the repo will kick-start the pipeline.
We can see the result of the pipeline by visiting our django.nv repository, clicking the Actions tab, and selecting the appropriate workflow name to see the output.
You will notice that the container_scanning job has failed but didn’t block the other jobs from running.