Skip to content

Jenkins CI Integration

jenkins-logo

In this guide we'll show you how to set up a Jenkins Pipeline so Mayhem can automatically test your code or API on every push.

You will need the following to run Mayhem in your Jenkins pipeline:

  1. Create a Mayhem API token.
  2. Add the newly created token as a "Secret text" entry in Jenkins Credentials named MAPI_TOKEN

Pipeline Configuration for Mayhem with Jenkins

Create a Jenkinsfile to configure a Jenkins scripted pipeline to test your code or API and collect results as a junit report:

 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
pipeline {
    agent any

    stages {
        stage('Setup') {
            steps {
                echo 'Setting up..'
                withCredentials([usernamePassword(credentialsId: 'MAYHEM_CREDENTIALS', usernameVariable: 'MAYHEM_USERNAME', passwordVariable: 'MAYHEM_TOKEN')]) {
                    sh """
                      # Setup aarch64 (preinstalled) and x86_64 (download to install)
                      mkdir -p ~/bin
                      export PATH=\${PATH}:~/bin
                      curl -Lo ~/bin/mayhem-x86_64 ${MAYHEM_URL}/cli/Linux/mayhem  && chmod +x ~/bin/mayhem-x86_64

                      # Login to mayhem and docker
                      mayhem-\$(arch) login --url ${MAYHEM_URL} --token ${MAYHEM_TOKEN}
                      REGISTRY=\$(mayhem-\$(arch) docker-registry)
                      echo "${MAYHEM_TOKEN}" | docker login -u ${MAYHEM_USERNAME} --password-stdin \${REGISTRY}
                    """
                }

            }
        }
        stage('Build') {
            steps {
                echo 'Building..'
                sh """
                    echo "Compiling the code..."
                    export PATH=\${PATH}:~/bin
                    REGISTRY=\$(mayhem-\$(arch) docker-registry)
                    docker build --platform=linux/amd64 -t \${REGISTRY}/lighttpd:${env.BRANCH_NAME} .
                    docker push \${REGISTRY}/lighttpd:${env.BRANCH_NAME}
                    echo "Compile complete."
                  """
            }
        }
        stage('Mayhem for Code') {
            matrix {
                agent any
                axes {
                    axis {
                        name 'TARGET'
                        values 'lighttpd', 'mayhemit'
                    }
                }
                stages {
                    stage('Mayhem for Code') {
                        steps {
                            echo 'Scanning..'
                            sh """#!/bin/bash
                                  export PATH=\${PATH}:~/bin
                                  REGISTRY=\$(mayhem-\$(arch) docker-registry)

                                  # Run Mayhem
                                  echo "mayhem-\$(arch) --verbosity info run . --project forallsecure/mcode-action-examples --owner forallsecure --image \${REGISTRY}/lighttpd:${env.BRANCH_NAME} --file mayhem/Mayhemfile.${TARGET} --duration 60 --branch-name ${env.BRANCH_NAME} --revision ${env.GIT_COMMIT} 2>/dev/null"
                                  run=\$(mayhem-\$(arch) --verbosity info run . --project forallsecure/mcode-action-examples --owner forallsecure --image \${REGISTRY}/lighttpd:${env.BRANCH_NAME} --file mayhem/Mayhemfile.${TARGET} --duration 60 --branch-name ${env.BRANCH_NAME} --revision ${env.GIT_COMMIT} 2>/dev/null);
                                  # Fail if no output was given
                                  if [ -z "\${run}" ]; then exit 1; fi

                                  # Determine run name
                                  runName=\$(echo \${run} | awk -F / '{ print \$(NF-1) }');

                                  # Wait for job to complete and artifacts to be ready
                                  mayhem-\$(arch) --verbosity info wait \${run} --owner forallsecure --sarif sarif-\${runName}.sarif --junit junit-\${runName}.xml;
                                  status=\$(mayhem-\$(arch) --verbosity info show --owner forallsecure --format json \${run} | jq '.[0].status')
                                  if [[ \${status} == *"stopped"* || \${status} == *"failed"* ]]; then exit 2; fi
                                  defects=\$(mayhem-\$(arch) --verbosity info show --owner forallsecure --format json \${run} | jq '.[0].defects|tonumber')
                                  if [[ \${defects} -gt 0 ]]; then echo "\${defects} defects found!"; exit 3; fi
                              """
                        }
                    }
                }
                post {
                    always {
                        echo 'Archive....'
                        archiveArtifacts artifacts: 'junit-*.xml, sarif-*.sarif',
                          allowEmptyArchive: true,
                          fingerprint: true,
                          onlyIfSuccessful: false
                        junit 'junit-*.xml'
                        recordIssues(
                            enabledForFailure: true,
                            tool: sarif(id: "sarif-${TARGET}", pattern: 'sarif-*.sarif')
                        )
                    }
                }
            }
        }
    }
}
 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
// Run the build on a node with the 'docker' label
node("docker") {
  checkout scm

  // MAPI_TOKEN - The API Token secret text added to Credentials
  withCredentials([
      string(credentialsId: "${MAPI_TOKEN}", variable: "MAPI_TOKEN")
      ]) {

    //
    // 1. BUILD AND TEST YOUR API HERE
    //

    stage("Run Mayhem") {
        //
        // 2. Start your API
        //    eg. http://localhost:8080/api
        //

        //
        // 3. Download the CLI (or use Jenkins Tools
        // see: https://github.com/jenkinsci/custom-tools-plugin/blob/master/README.md)
        // Replace $(MAYHEM_URL) with your instance's URL, e.g., https://app.mayhem.security
        //
        sh '''
        curl -Lo mapi $(MAYHEM_URL)/cli/mapi/linux-musl/latest/mapi \
          && chmod +x mapi
        '''

        //
        // 4. Check your API
        //
        sh '''
          mapi run my-api auto <path_to_openapi_spec> \
            --url 'http://localhost:8080/api' \
            --junit results.xml
        '''

        //
        // 5.  Collect junit results
        //
        junit testResults: 'results.xml'

    }
  }
}

Integrating Mayhem with Jenkins

Now that we've shown you the Jenkinsfile that you'll need to properly integrate Mayhem with Jenkins, 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 Jenkins for the underlying targets.

github-repo

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

  1. MAYHEM_CREDENTIALS: Takes a username and password, where username is your Mayhem user and password is your 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 Jenkinsfile configuration, we build and push our Docker image to the GitHub Container Registry. Therefore, make sure to set your project's visibility to Public to allow Mayhem to pull from the GitHub Container Registry for the repository Docker image.

repo-visibility

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

successful-pipeline