Implement a DevSecOps Pipeline with GitHub Actions
Tags: devsecops, githubactions, node, cicd
Published: 2024-06-16 06:24:22

## 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

1. Create a free SNYK account at [SNYK](https://snyk.io/ )
2. Navigate to Account Settings
3. Generate Auth Token
4. 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. 

1. Create a free Sonar account at [SonarCloud](https://www.sonarsource.com/products/sonarcloud/ )
2. Create a new **Organisation** and **Project**
3. Navigate to My Account, Security Tab
4. Generate a new Token
5. 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


![GitHub Actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8l9pijj06fkgbu88wpe.png)


![Sample GitHub Actions Workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2288ui6ut5ht7xyaxcy9.png)

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](https://github.com/herjean7/DevSecOpsTest) 

Cheers!