Implement a DevSecOps Pipeline with GitHub Actions
Hejun Wong
Posted on June 16, 2024
Introduction - Birth of DevSecOps
The story of DevSecOps follows the the story of Software Development closely. We saw how the industry moved from Waterfall to Agile and everything changed after Agile. With much shorter development cycles, there was also a need for faster deployments to production.
It was no longer feasible for Security teams to get the Dev / Ops teams to wait till Vulnerability Assessment and Penetration Testing (VAPT) was complete before changes could be pushed to production. If not, we nullify the advantage the team had with speed and agility.
DevOps is a set of practices intended to reduce the time between committing a change to a system and the change being placed into production, while ensuring high quality - Bass, Weber and Zhu
By definition, DevOps already includes Security as part of Operations but the Security industry wanted more focus and emphasis on Security hence the term DevSecOps or Secure DevOps came about.
Security Terms
Before diving into the implementation phase, let's familiarise ourselves with these 3 security terms.
SCA - stands for Software Composition Analysis. It is a technique used to find security vulnerabilities in third-party components that we use in our projects / products. They can be libraries, packages that we install.
We will be using Snyk (pronounced as "Sneak") for our SCA tool. Snyk is a developer-first SCA solution, helping developers find, prioritize and fix security vulnerabilities and license issues in open source dependencies
- Create a free SNYK account at SNYK
- Navigate to Account Settings
- Generate Auth Token
- This key would be your SNYK_TOKEN. Store it within your GitHub Actions Secrets.
SAST - stands for Static Application Security Testing. It is a technique used to analyse source codes, binary and byte codes for security vulnerabilities without running the code. Since the codes are not running but examined in static state, it is called static analysis. SAST, SCA and Linting are typical examples of static analysis
For SAST, we will be using Sonar. SonarCloud is a cloud based static analysis tool for your CI/CD pipeline. It supports dozens of popular languages, development frameworks and IaC platforms.
- Create a free Sonar account at SonarCloud
- Create a new Organisation and Project
- Navigate to My Account, Security Tab
- Generate a new Token
- This token would be your SONAR_TOKEN. Store it within your GitHub Actions Secrets.
DAST - stands for Dynamic Application Security Testing. This is a technique used to analyse the running application for security vulnerabilities. Since the application is running and being examined dynamically, it is called dynamic analysis.
For DAST, we will be using OWASP ZAP. ZAP is the world’s most widely used web app scanner. It is a free, open-source penetration testing tool and at its core, ZAP is known as a “man-in-the-middle proxy”. You would find 3 Github actions belonging to OWASP ZAP within the GitHub Marketplace.
GitHub Actions
GitHub Actions is a CI/CD platform that allows us to automate our build, test and deployment pipeline
You can find it within your code repository on GitHub. It is the Actions Tab.
And when you click onto any of these workflow runs, you would be able to see the jobs that ran under that workflow
Above is a sample workflow.
Workflows are automated processes that you can configure to run one or more jobs. Workflows are defined by YAML files checked into your repository. These yaml files are stored in the .github/workflows
directory. You can have multiple workflows, each of them doing a different set of tasks
The workflow can be triggered by an event (e.g. a pull request / when a developer pushes a change into the code repository)
When that happens, one or more jobs will start running.
Jobs are steps are executed in order and are dependent on each other. You can share data from one step to another as they are ran on the same runner.
Runners are servers that run your workflows when triggered and Github provides Linux, Windows and MacOS virtual machines for you to run your workflows.
Our Workflow
Our DevSecOps pipeline will consist of 3 jobs.
Build - requests for the latest ubuntu server, installs the latest github actions (v4), installs nodeJS version 20, installs our project's dependencies npm run install
, runs our unit test npm run test
and performs SAST using Sonar.
SCA - requests for the latest ubuntu server and for this job to start running, it needs the build job to be complete. It will install the latest github actions (v4) and runs Snyk against our code repository.
DAST - requests for latest ubuntu server, waits for SCA job to be complete, checks out the latest github actiosn (v4) and runs OWASP ZAP against a sample website (example.com).
The entire CICD pipeline can be implemented in just 50 lines of codes below.
name: Build code, run unit test, run SAST, SCA, DAST security scans for NodeJs App
on: push
jobs:
Build:
runs-on: ubuntu-latest
name: Unit Test and SAST
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: npm
- name: Install dependencies
run: npm install
- name: Test and coverage
run: npm run test
- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.organization=[YOUR_SONAR_ORGANISATION]
-Dsonar.projectKey=[YOUR_SONAR_PROJECT]
SCA:
runs-on: ubuntu-latest
needs: Build
name: SCA - SNYK
steps:
- uses: actions/checkout@v4
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
DAST:
runs-on: ubuntu-latest
needs: SCA
name: DAST - ZAP
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: ZAP Scan
uses: zaproxy/action-baseline@v0.11.0
with:
target: 'http://example.com/'
Troubleshooting
You would notice that your unit test coverage report isn't being uploaded into SonarCloud. To fix this, create a sonar-project.properties file in the root of your repository. The file will inform Sonar where to retrieve your code coverage reports.
sonar.organization=[INPUT_YOUR_ORGANISATION]
sonar.projectKey=[INPUT_YOUR_PROJECT_KEY]
# relative paths to source directories. More details and properties are described
# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/
sonar.sources=.
sonar.exclusions=**/tests/*.js
sonar.language=js
sonar.javascript.lcov.reportPaths=./coverage/lcov.info
sonar.testExecutionReportPaths=./test-report.xml
sonar.sourceEncoding=UTF-8
Resources
For a working example, you can refer to my repository
Cheers!
Posted on June 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.