コンテンツにスキップ

Jenkins CI との統合

jenkins-logo

このガイドでは、プッシュのたびに Mayhem が自動的に API をテストできるよう Jenkins Pipeline をセットアップする方法を説明します。

Jenkins パイプラインで Mayhem を実行するには、以下が必要です

  1. Mayhem API トークン を作成します。
  2. 作成したトークンを MAPI_TOKEN という名前の「シークレット テキスト」エントリとして Jenkins Credentials に追加します。

Jenkins と Mayhem を連携するためのパイプライン構成

コードまたは API をテストして JUnit レポートとして結果を収集するよう Jenkinsスクリプト パイプライン を構成するには、Jenkinsfile を作成します。

 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'

    }
  }
}

Mayhem と Jenkins の統合

Mayhem と Jenkins を適切に統合するのに必要な Jenkinsfile について説明したので、次は、動作するサンプルを確認します。

Info

このサンプルでは、mcode-action-examples にあるアセットをフォークし、ターゲットの Jenkins に Mayhem を統合しています。

github-repo

Jenkins で構成を動作させるには、Jenkins で次の認証情報を設定する必要があります。このサンプルでは、次のパイプライン変数が設定されています。

  1. MAYHEM_CREDENTIALS: ユーザー名とパスワードを受け取ります。ユーザー名は Mayhem ユーザーであり、パスワードは Mayhem API トークンです。
  2. MAYHEM_URL: Mayhem サーバーの URL です。https://app.mayhem.security を設定します。

secret-variables

さらに、上記の Jenkinsfile 構成では、Docker イメージをビルドして GitHub Container Registry にプッシュします。そのため、プロジェクトの可視性が Public に設定されていることを確認し、Mayhem が GitHub Container Registry からリポジトリの Docker イメージをプルできるようにします。

repo-visibility

上記の手順を完了すると、Jenkins パイプラインを実行できるようになります。パイプラインはターゲットに対して Mayhem ランを実行し、脆弱性をテストします。

successful-pipeline