Integrating GitLab-CI with DefectDojo

Stefan Steinert
7 min readFeb 9, 2021

Nowadays you often read about the “Shift-Left” of security tests in DevOps. Since I often see security as an underestimated topic in many projects I love to see that development and the increasing amount of tools to integrate security checks into your CI/CD pipelines.

But this also leads to issues in my eyes. Each and every tool is giving its own reports and/or lets your pipeline fail and you have to check each tool individual for the results. Also sometimes you just want to accept the risk which exists in your applications, because you maybe have mitigated the issue by using an earlier barrier (e.G. a web application firewall) (You should nevertheless fix any known security issue!).

Two common tools I use in many of my projects are the GitLab-CI SAST tests and Anchore Engine for scanning the container images (Yes, if I would own an Ultimate license for GitLab I could also have that directly within my instance with GitLab Container Scanning 😉 ).

GitLab SAST reports are visibile within GitLab itself, Anchore Engine produces Json and HTML reports as separate artifacts. Also I maybe want to have more tools in the future reporting issues in my applications or provide manual audits.

OWASP DefectDojo

During the search for a tool which could help me to visualize the results of my scanners, while integrating all of them easily, I stumbled upon DefectDojo in the OWASP Project list.

An open source vulnerability management tool that streamlines the testing process by offering templating, report generation, metrics, and baseline self-service tools. — OWASP

In general DefectDojo offers a centralized overview of your scanner results, while giving you the ability to accept risks, listing affected endpoints, create and answer manual questionnaires on your application, directly report to Jira and a bit more (Features).

The amount of supported scanners is huge, but you have to bring in your reports (except for SonarQube). There is no “click & collect” to automatically fetch the reports from your other security tools. It seems that they want to change that in the future, since you are already able to provide tools and their connection configuration in the settings.

To bypass that “issue” a very powerful api is offered which you can use to fully integrate with your CI/CD pipeline to import your scanners reports into DefectDojo.

GitLab-CI integration

Since I heavily use GitLab-CI I will integrate the DefectDojo API with a pipeline there.

I assume that you are a bit familiar with DefectDojo and have created your product (application) already. Otherwise read their QuickStart Guide.

For accessing the API we will also need the API-V2 endpoint of our DefectDojo installation, the API-Key of the user which should perform the actions and the ID of your product.

The API-V2 endpoint is simply the base URL of your DefectDojo instance + “/api/v2” (e.G. https://defectdojo.example.com/api/v2) .

The API-Key can be retrieved from the user menu in the top right corner.

The ID of your product can be taken from the URL path when you access the product in the UI.

DefectDojo collects tests in so called “Engagements”. An engagement can contain multiple test results of different types of tests and reflects a point in time when testing took place.

To follow this structure we will first create a new engagement at the beginning of the pipeline, which will contain all test results of the current pipeline execution. We’ll do this in the “.pre” stage. In return of the API call we will receive the engagement ID which we’ll store in a new environment variable and pass it to the following DefectDojo related tasks by using the “artifacts:reports:dotenv” artifact.

variables:
DEFECTDOJO_ENGAGEMENT_PERIOD: 7
DEFECTDOJO_ENGAGEMENT_STATUS: "Not Started"
DEFECTDOJO_ENGAGEMENT_BUILD_SERVER: "null"
DEFECTDOJO_ENGAGEMENT_SOURCE_CODE_MANAGEMENT_SERVER: "null"
DEFECTDOJO_ENGAGEMENT_ORCHESTRATION_ENGINE: "null"
DEFECTDOJO_ENGAGEMENT_DEDUPLICATION_ON_ENGAGEMENT: "false"
DEFECTDOJO_ENGAGEMENT_THREAT_MODEL: "true"
DEFECTDOJO_ENGAGEMENT_API_TEST: "true"
DEFECTDOJO_ENGAGEMENT_PEN_TEST: "true"
DEFECTDOJO_ENGAGEMENT_CHECK_LIST: "true"
DEFECTDOJO_NOT_ON_MASTER: "false"
defectdojo_create_engagement:
stage: .pre
image: alpine
variables:
GIT_STRATEGY: none
rules:
- if: '$DEFECTDOJO_NOT_ON_MASTER == "true" && $CI_COMMIT_BRANCH == "master"'
when: never
- when: always
before_script:
- apk add curl jq coreutils
- TODAY=`date +%Y-%m-%d`
- ENDDAY=$(date -d "+${DEFECTDOJO_ENGAGEMENT_PERIOD} days" +%Y-%m-%d)
script:
- |
ENGAGEMENTID=`curl --fail --location --request POST "${DEFECTDOJO_URL}/engagements/" \
--header "Authorization: Token ${DEFECTDOJO_TOKEN}" \
--header 'Content-Type: application/json' \
--data-raw "{
\"tags\": [\"GITLAB-CI\"],
\"name\": \"#${CI_PIPELINE_ID}\",
\"description\": \"${CI_COMMIT_DESCRIPTION}\",
\"version\": \"${CI_COMMIT_REF_NAME}\",
\"first_contacted\": \"${TODAY}\",
\"target_start\": \"${TODAY}\",
\"target_end\": \"${ENDDAY}\",
\"reason\": \"string\",
\"tracker\": \"${CI_PROJECT_URL}/-/issues\",
\"threat_model\": \"${DEFECTDOJO_ENGAGEMENT_THREAT_MODEL}\",
\"api_test\": \"${DEFECTDOJO_ENGAGEMENT_API_TEST}\",
\"pen_test\": \"${DEFECTDOJO_ENGAGEMENT_PEN_TEST}\",
\"check_list\": \"${DEFECTDOJO_ENGAGEMENT_CHECK_LIST}\",
\"status\": \"${DEFECTDOJO_ENGAGEMENT_STATUS}\",
\"engagement_type\": \"CI/CD\",
\"build_id\": \"${CI_PIPELINE_ID}\",
\"commit_hash\": \"${CI_COMMIT_SHORT_SHA}\",
\"branch_tag\": \"${CI_COMMIT_REF_NAME}\",
\"deduplication_on_engagement\": \"${DEFECTDOJO_ENGAGEMENT_DEDUPLICATION_ON_ENGAGEMENT}\",
\"product\": \"${DEFECTDOJO_PRODUCTID}\",
\"source_code_management_uri\": \"${CI_PROJECT_URL}\",
\"build_server\": ${DEFECTDOJO_ENGAGEMENT_BUILD_SERVER},
\"source_code_management_server\": ${DEFECTDOJO_ENGAGEMENT_SOURCE_CODE_MANAGEMENT_SERVER},
\"orchestration_engine\": ${DEFECTDOJO_ENGAGEMENT_ORCHESTRATION_ENGINE}
}" | jq -r '.id'`
- echo "DEFECTDOJO_ENGAGEMENTID=${ENGAGEMENTID}" >> defectdojo.env
artifacts:
reports:
dotenv: defectdojo.env

The above example code describes a full example with the possibility to configure all API fields using GitLab-CI variables.

The following ones are mandatory and have to be set, otherwise the request will fail:

  • DEFECTDOJO_URL
  • DEFECTDOJO_TOKEN
  • DEFECTDOJO_PRODUCTID

For the first steps you can leave the others on default values and adjust them later to your needs.

As result of this you will get the job which create the engagement in the “.pre” stage or your pipeline

Followed by a new engagement in DefectDojo named by your pipeline ID (CI_PIPELINE_ID)

Since we want to integrate with the GitLab-CI SAST tests we have to include the respective template and add the “test” stage to the pipeline.

include:
- template: Security/SAST.gitlab-ci.yml
stages:
- build
- packaging
- test
- deploy

Depending on your project the template will add SAST related jobs (*-sast)to your pipeline.

You can view the rules for that in the original template file of GitLab: https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml

When you have a look at the template you will see that the report artifacts are only published as “artifacts:reports:sast”. Those artifacts are not shared with other jobs. Only with the GitLab Instance for displaying the reports. Due to that we have to extend the “SAST” base job to also publish the reports as “artifacts:path”. This is rather simple since GitLab-CI will combine job definitions with the same job name to one large job. With knowledge of this behavior we just have to create a job which contains the artifacts definition.

sast:
artifacts:
paths:
- gl-sast-report.json

With that in place we have now all dependencies ready to import the scan results into DefectDojo and attach them to the engagement which we created earlier.

This is also done by a simple API call. We will perform the upload of the reports to DefectDojo in the “.post” stage at the end of our pipeline. We do that since it should be independent of all other jobs, but we still limit it by putting the same rules in place as the original GitLab SAST template uses to prevent creation of unnecessary jobs.

defectdojo_publish_gitlab_sast:
stage: .post
needs: ["defectdojo_create_engagement", "spotbugs-sast"]
image: alpine
allow_failure: true
variables:
DEFECTDOJO_SCAN_MINIMUM_SEVERITY: "Info"
DEFECTDOJO_SCAN_ACTIVE: "true"
DEFECTDOJO_SCAN_VERIFIED: "true"
DEFECTDOJO_SCAN_CLOSE_OLD_FINDINGS: "true"
DEFECTDOJO_SCAN_PUSH_TO_JIRA: "false"
DEFECTDOJO_SCAN_ENVIRONMENT: "Default"
DEFECTDOJO_ANCHORE_DISABLE: "false"
DEFECTDOJO_SCAN_TEST_TYPE: "GitLab-CI Spotbugs"
before_script:
- apk add curl coreutils
- TODAY=`date +%Y-%m-%d`
script:
- |
curl --fail --location --request POST "${DEFECTDOJO_URL}/import-scan/" \
--header "Authorization: Token ${DEFECTDOJO_TOKEN}" \
--form "scan_date=\"${TODAY}\"" \
--form "minimum_severity=\"${DEFECTDOJO_SCAN_MINIMUM_SEVERITY}\"" \
--form "active=\"${DEFECTDOJO_SCAN_ACTIVE}\"" \
--form "verified=\"${DEFECTDOJO_SCAN_VERIFIED}\"" \
--form "scan_type=\"${DEFECTDOJO_SCAN_TYPE}\"" \
--form "engagement=\"${DEFECTDOJO_ENGAGEMENTID}\"" \
--form "file=@${DEFECTDOJO_SCAN_FILE}" \
--form "close_old_findings=\"${DEFECTDOJO_SCAN_CLOSE_OLD_FINDINGS}\"" \
--form "push_to_jira=\"${DEFECTDOJO_SCAN_PUSH_TO_JIRA}\"" \
--form "test_type=\"${DEFECTDOJO_SCAN_TEST_TYPE}\"" \
--form "environment=\"${DEFECTDOJO_SCAN_ENVIRONMENT}\""
rules:
- if: '$DEFECTDOJO_NOT_ON_MASTER == "true" && $CI_COMMIT_BRANCH == "master"'
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /spotbugs/
when: never
- if: $SAST_DEFAULT_ANALYZERS =~ /mobsf/ &&
$SAST_EXPERIMENTAL_FEATURES == 'true'
exists:
- '**/AndroidManifest.xml'
when: never
- if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /spotbugs/
exists:
- '**/*.groovy'
- '**/*.java'
- '**/*.scala'

The above example is showing a request for adding the report resulting from the “Spotbugs” test. You can extend this for any of the reports available from GitLab SAST scanning.

When everything is done right you will see the test and their results within your engagement in DefectDojo.

With those simple steps you are able to generate a central overview of the current security state of your application and plan further steps to resolve them.

I hope this helps you to improve the security of your application while not increasing the overhead for testing dramatically.

Example Code

In the following repository you’ll find a working example which includes all GitLab SAST scans and in addition to that I have added Anchore Engine, since I often use it.

Refer the readme of the repository for detailed usage instructions.

https://gitlab.com/bomba19881/gitlabci-defectdojo

Links

--

--

Stefan Steinert

System Operator and DevOPs Architect since childhood