Skip to content

GitLab CI/CD Integration

gitlab-ci-cd

In this guide we'll show you how to set up a GitLab CI/CD pipeline so that Mayhem can automatically test your code or API on every push.

You will need the following to run Mayhem in your GitLab CI/CD pipeline:

  1. Create a Mayhem API token.
  2. Add the newly created token as a "Secret Variable" in the pipeline's variables named MAYHEM_TOKEN.

Pipeline Configuration for Mayhem with GitLab CI/CD

Create a .gitlab-ci.yml file to configure a GitLab CI/CD pipeline and test your code or API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
services:
  - "docker:dind"

variables:
  # Set env vars, unable to use cmd substitution here, see https://gitlab.com/gitlab-org/gitlab/-/issues/17251
  REGISTRY: "registry.gitlab.com"
  REPO_SLUG: "${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}"

stages:
  - build
  - test

build-job:
  stage: build
  image: docker:stable
  before_script:
    # Set <username>/<repo> to lowercase
    - LOWER_REPO_SLUG=$(echo $REPO_SLUG | tr '[:upper:]' '[:lower:]')
  script:
    # Build and push Docker image to registry
    - echo $CI_REGISTRY_PASSWORD | docker login $REGISTRY -u $CI_REGISTRY_USER --password-stdin
    - docker build --platform=linux/amd64 -t ${REGISTRY}/${LOWER_REPO_SLUG}:${CI_COMMIT_REF_NAME} .
    - docker push ${REGISTRY}/${LOWER_REPO_SLUG}:${CI_COMMIT_REF_NAME}

mcode-test-job:
  stage: test
  image: debian:bullseye
  before_script:
    # Get GitLab username and set <username>/<repo> to lowercase
    - GITLAB_USERNAME=$(echo $REPO_SLUG | cut -d '/' -f1)
    - LOWER_REPO_SLUG=$(echo $REPO_SLUG | tr '[:upper:]' '[:lower:]')
  script:
    # Download Mayhem CLI and log in to the Mayhem server
    - apt-get update && apt-get install -y curl jq
    - mkdir -p ~/bin
    - export PATH=${PATH}:~/bin
    - curl --no-progress-meter -Lo ~/bin/mayhem ${MAYHEM_URL}/cli/Linux/mayhem  && chmod +x ~/bin/mayhem
    - MAYHEM_PROMPT=1 mayhem login --url ${MAYHEM_URL} --token ${MAYHEM_TOKEN}
    # Execute Mayhem run and fail if no run was executed
    - run=$(mayhem --verbosity info run . --project $LOWER_REPO_SLUG --owner andrew5194 --image ${REGISTRY}/${LOWER_REPO_SLUG}:${CI_COMMIT_REF_NAME} --file ${MAYHEMFILE} --duration 60 --branch-name ${CI_COMMIT_REF_NAME} --revision ${CI_COMMIT_SHA} --ci-url ${CI_PIPELINE_URL} 2>/dev/null);
    - if [ -z "${run}" ]; then exit 1; fi
    # Otherwise, determine run name and wait for job to complete and artifacts to be ready
    - runName=$(echo ${run} | awk -F / '{ print $(NF-1) }');
    - mayhem --verbosity info wait $run --owner $GITLAB_USERNAME --sarif sarif-${runName}.sarif --junit junit-${runName}.xml;
    - status=$(mayhem --verbosity info show --owner $GITLAB_USERNAME --format json $run | jq '.[0].status')
    - if [[ ${status} == *"stopped"* || ${status} == *"failed"* ]]; then exit 2; fi
    # Fail if defects were found
    - defects=$(mayhem --verbosity info show --owner $GITLAB_USERNAME --format json ${run} | jq '.[0].defects|tonumber')
    - if [[ ${defects} -gt 0 ]]; then echo "${defects} defects found!"; exit 3; fi
  parallel:
    matrix:
      # Specify one or more Mayhemfiles for testing
      - MAYHEMFILE: ['mayhem/Mayhemfile.lighttpd', 'mayhem/Mayhemfile.mayhemit']
  artifacts:
    when: always
    paths:
      - 'sarif-*.sarif'
      - 'junit-*.xml'
    reports:
      junit: 'junit-*.xml'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
image: python:3.9

cache:
  paths:
    - .cache/pip
    - .coverage

before_script:
  - pip install --cache-dir .cache/pip -r requirements.txt

stages:
  - test
  - coverage

test-job:
  stage: test
  script:
    - FASTAPI_ENV=test python3 -m coverage run -m uvicorn src.main:app &
    - curl -Lo mapi $MAYHEM_URL/cli/mapi/linux-musl/latest/mapi && chmod +x mapi
    - ./mapi login $MAPI_TOKEN
    - ./mapi run forallsecure/mapi-action-examples/fastapi auto "http://localhost:8000/openapi.json" --url "http://localhost:8000/" --junit junit.xml --sarif mapi.sarif --html mapi.html
    - pgrep python3 | xargs kill || true
  artifacts:
    when: always
    paths:
      - junit.xml
      - mapi.html
      - mapi.sarif
    reports:
      junit: junit.xml

coverage-report:
  stage: coverage
  when: always
  script:
    - python3 -m coverage report
    - python3 -m coverage xml
    - python3 -m coverage html -d coverage_html_report
  coverage: '/TOTAL.*\s+(\d+\%)$/' # regex to extract coverage percentage
  artifacts:
    when: always
    paths:
      - coverage_html_report/
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

Integrating Mayhem with GitLab CI/CD

Now that we've shown you the .gitlab-ci.yml file that you'll need to properly integrate Mayhem with GitLab CI/CD, let's walk through a working example.

Info

For this example we are forking the assets located at mcode-action-examples and integrating Mayhem into the GitLab CI/CD pipeline for the underlying targets.

gitlab-repo

To get the config working in GitLab, you will have to first set up the Secret Variables. For this example, we've set the following pipeline variables:

  1. MAYHEM_TOKEN: Your user generated Mayhem API token.
  2. MAYHEM_URL: The URL to the Mayhem server. Here we set https://app.mayhem.security.

secret-variables

In addition, for the above .gitlab-ci.yml configuration, we build and push our Docker image to the GitLab Container Registry. Therefore, make sure to set your project's visibility to Public to allow Mayhem to pull from the GitLab Container Registry for the repository Docker image.

repo-visibility

Once you've completed the above steps, you should be able to run the GitLab CI/CD pipeline, which will execute a Mayhem run against the underlying targets to test for vulnerabilities.

successful-pipeline