CodeSonar Analysis in a GitLab Pipeline on Linux

The following instructions are for Linux, but should be adaptable to some other POSIX operating systems.

We provide separate instructions for other environments:

These instructions assume you are using CodeSonar 7.3 or later, where the codesonar analyze -remote-archive option is supported.

Prerequisites

These instructions assume that you have satisfied the prerequisites as described in the setup overview document. In particular, make sure you have access to the following.

Overview

There are six main steps.

A. Prepare an example project (zlib)

For the sake of an easy, but realistic example, we will assume you want to build and analyze zlib v1.2.13: https://github.com/madler/zlib.

Prepare the example repository on your GitLab server as follows.

  1. On your developer workstation, clone the zlib repository.

    git clone https://github.com/madler/zlib
    
  2. Create a new repository project named "zlib" (or similar) in your GitLab server instance with the following properties:

    • Empty ("blank") repository.
    • Private or Internal visibility (not public).
    • Do not initialize with a README.
    • Enable Static Application Security Testing (SAST) (if available).
  3. Follow the instructions provided by the GitLab repository project page to "push an existing Git repository" in order to push your local zlib repository clone to your GitLab server.

After following the GitLab instructions, your local zlib repository should be configured so that its "origin" is on your GitLab server, and not the original GitHub.com location.

You should now have an example zlib project in your GitLab server which you can build and analyze in the steps that follow.

The instructions in this document were tested with zlib v1.2.13. You can "check out" v1.2.13 by creating a branch for the Git tag 'v1.2.13' and checking out the branch:

git branch example v1.2.13
git checkout example

B. Create and install an analysis data server

NOTE If you plan to analyze your code using a remote-managed or SaaS analysis service, then you do not need to install an analysis data server. Go on to C. Create and install a pipeline build runner.

Otherwise, set up an analysis data server now.

C. Create and install a pipeline build runner

  1. If needed, create a CodeSonar CI hub user.

    You will need a CodeSonar hub user account that can be used for automated pipeline jobs. If you previously created a hub user for an analysis data server, then you can use that account here too. Otherwise, create a new hub user account.

    The remainder of these instructions will assume that the hub user name is cshub_ci.

  2. If you have not already done so, identify a suitable CI builder host machine.

    You will need root access to a machine that can run pipeline jobs to build and analyze your code. This can be a physical machine, virtual machine, or container. This "CI builder" machine is distinct from the "analysis data server" machine from part B.

  3. Log in to your CI builder machine as root.

  4. Install compilers and build tools.

    In general, your CI builder machine must have all compilers and tools required to build your project(s). The CI builder machine must also have Git, since it is required for all GitLab runners

    To build zlib on Linux, you will need the following.

    • Git
    • GNU Make
    • GCC

    Install these programs now using your package manager (apt, yum, etc.) or appropriate software installers.

    On Linux systems that use apt-get, you can use the following command.

    apt-get install git make gcc
    
  5. Create a CI user account.

    The CI user will be responsible for building your code and running the CodeSonar analysis. The remainder of these instructions will assume that the CI user name is ci_runner.

    CI_USER_UID=1002
    CI_USER=ci_runner
    CI_USER_GID=1002
    CI_USER_GROUP=ci_runner
    CI_USER_HOME=/home/$CI_USER
    
    groupadd -g $CI_USER_GID $CI_USER_GROUP || true
    useradd -g $CI_USER_GROUP -u $CI_USER_UID -d $CI_USER_HOME -ms /bin/bash $CI_USER
    # Remove .bash_logout since it tries to clear console,
    #  and causes GitLab runner shell process to fail.
    #  https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading
    rm -fv $CI_USER_HOME/.bash_logout
    
  6. Install GitLab Runner.

    This runner will be responsible for running pipeline jobs that build your code.

    Official instructions: https://docs.gitlab.com/runner/install/.

    CI_RUNNER_DIR=/usr/local/bin
    CI_RUNNER_BINARY=gitlab-runner-linux-amd64
    CI_RUNNER=$CI_RUNNER_DIR/gitlab-runner
    
    mkdir -p $CI_RUNNER_DIR
    curl -L --output $CI_RUNNER "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/$CI_RUNNER_BINARY"
    chmod +x $CI_RUNNER
    
  7. Log in to your CI builder machine as your new CI user (ci_runner).

  8. Register the GitLab Runner instance.

    The runner instance must be registered exactly once. If the host machine is restarted, the runner will not need to be re-registered. Note that you can have many runners on a single machine, but each runner must have a separate home directory.

    You will need to modify some or all of the variable settings.

    Variable Setting
    CI_RUNNER Your GitLab Runner binary.
    CI_SERVER_URL GitLab server URL.
    CI_RUNNER_REG_TOKEN Provided by your GitLab site under the "CI/CD" Runner settings.
    CI_RUNNER_DESCRIPTION Name of runner to display in GitLab UI.
    CI_RUNNER_TAGS Used by the pipeline to identifiy the runner. In this example, the tags "Linux" and "GCC" indicate that this runner has GCC installed on a Linux platform.
    CI_RUNNER_HOMEDIR Base directory where runner files should be stored on the local machine.
    CI_RUNNER_WORKDIR Directory where runner will execute jobs.
    CI_RUNNER_CONFIGDIR Directory where runner configuration files will be saved.
    CI_RUNNER_CONFIG Path to runner configuration file. This file stores the runner instance settings.
    CI_RUNNER=/usr/local/bin/gitlab-runner
    CI_SERVER_URL=https://gitlab.example.com
    CI_RUNNER_REG_TOKEN=abc123def
    CI_RUNNER_DESCRIPTION=linux_ci_runner_02
    CI_RUNNER_TAGS=Linux,GCC
    CI_RUNNER_HOMEDIR=$HOME/gitlab-runner_02
    CI_RUNNER_WORKDIR=$CI_RUNNER_HOMEDIR/work
    CI_RUNNER_CONFIGDIR=$CI_RUNNER_HOMEDIR/config
    CI_RUNNER_CONFIG="$CI_RUNNER_CONFIGDIR/config.toml"
    
    mkdir -p $CI_RUNNER_CONFIGDIR && mkdir -p $CI_RUNNER_WORKDIR
    
    $CI_RUNNER register \
        --config "$CI_RUNNER_CONFIG" \
        --non-interactive \
        --url "$CI_SERVER_URL" \
        --registration-token "$CI_RUNNER_REG_TOKEN" \
        --description "$CI_RUNNER_DESCRIPTION" \
        --tag-list "$CI_RUNNER_TAGS" \
        --executor shell \
        --shell bash \
        --output-limit 10000
    
  9. Start the GitLab Runner instance.

    You should arrange for the GitLab Runner to be started whenever the system starts. For simplicity, we show how to start a simple runner process below, but you may eventually want to install the runner as a service. The gitlab-runner program provides the runner install and runner start subcommands which can be used to create a runner service. See the GitLab documentation for more information: https://docs.gitlab.com/runner/install/linux-manually.html.

    CI_RUNNER=/usr/local/bin/gitlab-runner
    CI_RUNNER_HOMEDIR=$HOME/gitlab-runner_02
    CI_RUNNER_WORKDIR=$CI_RUNNER_HOMEDIR/work
    CI_RUNNER_CONFIGDIR=$CI_RUNNER_HOMEDIR/config
    CI_RUNNER_CONFIG="$CI_RUNNER_CONFIGDIR/config.toml"
    
    $CI_RUNNER run --config "$CI_RUNNER_CONFIG" --working-directory "$CI_RUNNER_WORKDIR"
    

D. Create a basic pipeline that can build your code

To start, we must define a pipeline job that can build the code.

The pipeline definition is specified in a .gitlab-ci.yml file in the root of your code repository. For information about the .gitlab-ci.yml file see: https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html.

If you are not familiar with YAML format, you may want to read about it at: https://yaml.org/spec/1.2/spec.html#Preview. Note that YAML is very sensitive to indentation.

  1. Create a new file named ".gitlab-ci.yml" and save it in your local repository clone.

  2. Insert the following content into .gitlab-ci.yml.

    workflow:
      rules:
        - if: $CI_MERGE_REQUEST_IID
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    
    stages:
      - build
    
    build:
      stage: build
      tags:
        - Linux
        - GCC
      script:
        - ./configure
        - make all
    
  3. Create a repository branch named "ci/main" (or whatever else you prefer) where you can save your pipeline definition.

    git checkout -b ci/main
    git add .gitlab-ci.yml
    
  4. Commit and push your .gitlab-ci.yml file to your GitLab server.

    git commit -m "Add gitlab-ci configuration"
    git push -u origin ci/main
    
  5. Test the pipeline.

    1. Create a merge request for the branch you just pushed.

      The git push command from the previous step may print a URL to help you create a merge request. You can also use the GitLab GUI directly to create a new merge request associated with your new branch. When you create a new merge request for your new branch, GitLab will notice the pipeline definition file and will use it to execute a pipeline on your CI builder machine.

    2. Verify that the pipeline executed successfully.

      Use the GitLab "Merge request" web page to find your pipeline status. If everything works correctly, you should see that your pipeline executed and if you inspect the job log, you should see that the code was successfully compiled.

E. Install CodeSonar and integration tools in CI builder environment

At this stage, you should have a working pipeline that compiles your code. The next task is to install CodeSonar tools on your CI builder machine so that you can execute a CodeSonar analysis.

  1. Log in to your CI builder machine as root.

  2. Install CodeSonar on your CI builder machine.

    1. Download the CodeSonar install archive (e.g. codesonar-7.3p0.20230330-x86_64-pc-linux.tar.gz).
    2. Extract the archive.

      cd /opt
      tar -xzf /path/to/codesonar-7.3p0.20230330-x86_64-pc-linux.tar.gz
      
    3. Make a version-independent symlink so that CodeSonar command is easier to find in pipeline scripts.

      ln -s codesonar-7.3p0  codesonar
      
    4. Activate the installation.

      codesonar/codesonar/bin/codesonar activate
      

      You will be prompted to accept the CodeSonar license.

    The example commands above will install CodeSonar so that it can be found at /opt/codesonar/.

  3. Install the CodeSonar-GitLab Integration tools.

    1. Download the CodeSonar-GitLab integration tools package (e.g. codesonar-gitlab-integration-1.6p0.tar.gz).
    2. Extract the package.

      cd /opt
      tar -xzf /path/to/codesonar-gitlab-integration-1.6p0.tar.gz
      
    3. Make a version-independent symlink so that integration tools are easier for pipeline scripts to find.

      ln -s codesonar-gitlab-integration-1.6p0  codesonar-gitlab-integration
      

      The example commands above will install CodeSonar-GitLab integration tools to /opt/codesonar-gitlab-integration.

  4. Log in to your CI builder machine as your CI user (ci_runner).

  5. [HTTPS hubs only] Ensure that CodeSonar trusts your hub's HTTPS certificate.

    If your CodeSonar hub uses plain HTTP (and not secure HTTPS), skip this step.

    This step must be performed by the same OS user that executes the gitlab-runner process. If you have been following these steps exactly, then this will be your CI user (ci_runner) on your CI builder machine.

    1. Run the following command. This command fetches a list of projects from your CodeSonar hub. When contacting your hub it will also check the HTTPS certificate.

      You will need to modify some or all of the variable settings.

      Variable Setting
      CSONAR_HUB Your hub location.
      CSONAR Your CodeSonar installation.
      CSONAR=/opt/codesonar
      CSONAR_HUB=https://codesonar.example.com:7340
      
      $CSONAR/codesonar/bin/codesonar get "$CSONAR_HUB/index.csv" -o -
      
    2. If CodeSonar does not recognize and trust your certificate, then it will prompt you to confirm that you trust the certificate.

      Assuming the correct certificate is received, then you should select "(T)rust certificate forever and connect" at the prompt. This will ensure that when CodeSonar is executed in future pipeline jobs, it will always trust your hub's HTTPS certificate.

      See the Troubleshooting document for additional information about this procedure as well as for alternatives.

  6. In the GitLab settings for your CI builder runner, add a new tag named "CodeSonar".

    This tag will help ensure pipeline jobs which require CodeSonar will be executed on a runner host that has CodeSonar installed. If you create a project-specific runner, then the settings can be found from your GitLab project page. If you create a shared runner, then the settings can be found from the GitLab group page. The settings for your runner can be found under Settings > CI/CD > Runners from the appropriate GitLab page.

F. Update the pipeline job definition to perform CodeSonar analysis

Most of the steps that follow should be performed in your local example (zlib) repository on your developer workstation.

  1. Generate a CodeSonar hub user certificate and key.

    • If you already have a suitable hub user certificate and private key, you do not need to generate new ones. When you set up CODESONAR_HUB_USER_CERT_FILE and CODESONAR_HUB_USER_KEY_FILE later, use your existing files. Go on to the next step.

    In this example we will configure the workflow to perform certificate-based hub authentication.

    If you cannot use certificate-based authentication (for example, because your hub is not configured for HTTPS), skip this step: you will modify the authentication-related command elements in later steps.

    Certificate generation requires the CodeSonar command line tools. If you don't have CodeSonar installed on your developer workstation, then you may prefer to perform these steps on your "CI builder machine".

    1. Execute the following. Make any necessary changes to variable settings before executing.

      Variable Setting
      CSONAR Your CodeSonar installation.
      CSONAR_HUB Your hub location.
      CSONAR_HUBUSER Your hub user account. Note that the command below uses this as both the username of the account that is authorizing certificate generation and the username of the account that is the subject of the certificate.
      CSONAR=/opt/codesonar
      CSONAR_HUB=https://codesonar.example.com:7340
      CSONAR_HUBUSER=cshub_ci
      CSONAR_HUBCERT=$CSONAR_HUBUSER.cert
      CSONAR_HUBKEY=$CSONAR_HUBUSER.key
      
      $CSONAR/codesonar/bin/codesonar generate-hub-cert \
          -foruser "$CSONAR_HUBUSER" \
          -auth password \
          -hubuser "$CSONAR_HUBUSER" \
          -out "$CSONAR_HUBCERT" \
          -outkey "$CSONAR_HUBKEY" \
          "$CSONAR_HUB"
      
    2. Provide the hub user account password when prompted.

    New files $CSONAR_HUBUSER.cert and $CSONAR_HUBUSER.key will be created, where $CSONAR_HUBUSER is the name of your hub CI user account. These files contain your certificate and private key, respectively.

  2. Download a copy of your GitLab server's HTTPS root certificate.

    In some cases, CodeSonar's Python interpreter will refuse to communicate with your GitLab server since it does not trust your GitLab server's HTTPS certificate. This may happen if your GitLab server uses a recently-issued certificate. In order to avoid a potential problem later, you can provide a copy of the certificate to CodeSonar in your pipeline configuration.

    1. Download a copy of the root authority certificate for your GitLab Server's HTTPS certificate.
    2. Save this root certificate to your repository working directory in Base-64 ASCII text format (often called "PEM" format).

    See the "Troubleshooting" document for additional information about how to download this file. We will assume you have saved the root certificate file to the root of your local repository with the name "gitlab.root.cacert".

  3. Configure your CI/CD pipeline to use CodeSonar.

    Modify your .gitlab-ci.yml file to add a codesonar-sast job, using the example below as a template. (Note that some GitLab features will assume that your "SAST scanning" job name is suffixed with "-sast".)

    This example makes use of many environment variables.

    • Some of the variables are Predefined by GitLab.
    • Other variables are defined in the pipeline definition itself.
    • Still others are defined in your GitLab project settings as custom "CI/CD Variables" (a full list is provided in the next step).

    For more information see: https://docs.gitlab.com/ee/ci/variables/.

    Make the following changes to the example.

    • Ensure that the tags for each job match the runner that the job requires.
    • Update the SARIF2SAST, CODESONAR, and CSPYTHON variables so that they refer to the correct locations.
    • Update the CI_SERVER_CAFILE variable if necessary so that it refers to your GitLab Server's HTTPS root authority certificate file.
    • Be sure to add the test item under the stages section of the YAML file.
    • For CodeSonar SaaS or other remote-managed analysis, replace

      -remote-archive  "${CODESONAR_REMOTE_LAUNCHDS}"
      

      with

      -remote  "${CODESONAR_REMOTE_LAUNCHDS}"
      
    • To provide a different name for your analysis, modify the -name value.

    • For password-based authentication, replace each occurrence of

      -auth certificate -hubcert "$CODESONAR_HUB_USER_CERT_FILE" -hubkey "$CODESONAR_HUB_USER_KEY_FILE"
      

      with

      -auth password -hubuser "${CODESONAR_HUBUSER}" -hubpwfile ${CODESONAR_HUBPWFILE}
      

      (note that there are multiple occurrences).

    • The -property arguments are used for adding additional, searchable data to your CodeSonar analysis. They are included here since they are often helpful for searching for analyses related to specific code commits. You may want to add more -property arguments in order to retain more searchable data for the analysis.

    For full details of the codesonar analyze command, see the CodeSonar manual: Using CodeSonar > Building and Analyzing Projects > Command Line Build/Analysis

    Example:

    workflow:
      rules:
        - if: $CI_MERGE_REQUEST_IID
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    
    stages:
      - build
      - test
    
    build:
      stage: build
      tags:
        - Linux
        - GCC
      script:
        - ./configure
        - make all
    
    codesonar-sast:
      stage: test
      tags:
        - Linux
        - GCC
        - CodeSonar
      variables:
        SARIF2SAST: "/opt/codesonar-gitlab-integration/distro-image/codesonar-sarif2sast"
        CODESONAR: "/opt/codesonar/codesonar/bin/codesonar"
        CSPYTHON: "/opt/codesonar/codesonar/bin/cspython"
        CODESONAR_PROJECT_NAME: ${CI_PROJECT_NAME}
        CI_SERVER_CAFILE: "gitlab.root.cacert"
      script:
        - ./configure
        - >
            $CODESONAR analyze
            "${CODESONAR_PROJECT_NAME}"
            -wait
            -remote-archive "${CODESONAR_REMOTE_LAUNCHDS}"
            -auth certificate -hubcert "${CODESONAR_HUB_USER_CERT_FILE}" -hubkey "${CODESONAR_HUB_USER_KEY_FILE}"
            -name "gitlab-ci ref=${CI_COMMIT_REF_NAME} update=${CI_MERGE_REQUEST_IID} job=${CI_PIPELINE_ID}.${CI_JOB_ID} commit=${CI_COMMIT_SHORT_SHA}"
            -property branch "$CI_COMMIT_REF_NAME"
            -property commit "$CI_COMMIT_SHORT_SHA"
            -property os "$CI_RUNNER_EXECUTABLE_ARCH"
            -property ci-job "$CI_JOB_ID"
            -property ci-run "$CI_PIPELINE_ID"
            -property merge-request "$CI_MERGE_REQUEST_IID"
            "${CODESONAR_HUB_URL}"
            make all
            |& tee analysis.log
        - CODESONAR_ANALYSIS_ID=$($CSPYTHON $SARIF2SAST/analysis_id.py "${CODESONAR_PROJECT_NAME}")
        - >
            $CODESONAR get
            -auth certificate -hubcert "${CODESONAR_HUB_USER_CERT_FILE}" -hubkey "${CODESONAR_HUB_USER_KEY_FILE}"
            -o allwarnings.sarif
            "${CODESONAR_HUB_URL}/analysis/${CODESONAR_ANALYSIS_ID}-allwarnings.sarif?filter=\"${CODESONAR_VISIBILITY_FILTER}\""
        - >
            $CSPYTHON $SARIF2SAST/sarif2sast.py
            --sarif allwarnings.sarif
            --output gl-sast-report.json
            --summary-report sast-summary-report.md
            --codesonar-url "${CODESONAR_HUB_URL}"
            --analysis-id ${CODESONAR_ANALYSIS_ID}
            --max ${CODESONAR_MAX_WARNINGS}
            --threshold ${CODESONAR_WARNING_THRESHOLD}
      after_script:
        - >
            $CSPYTHON $SARIF2SAST/upload_gitlab_mr_notes.py
            --api-token-variable GITLAB_TOKEN
            --report sast-summary-report.md
            --cafile "${CI_SERVER_CAFILE}"
      artifacts:
        reports:
          sast: gl-sast-report.json
    
  4. Use the GitLab UI to add all the custom variables used by the .yml file to your GitLab project. These settings are available from your GitLab project page: Settings > CI/CD > Variables.

    Note that these variables are shared by all pipelines in your repository project. They will therefore apply to all pipelines triggered by all merge requests on the repository.

    • When creating these variables, uncheck the "Protect variable" option. "Protected" variables can only be used on "protected" branches, and merge request branches will not typically be protected.
    • Check the "Mask variable" option for sensitive variables such as GITLAB_TOKEN.
    • Note that some of these must be configured as a File-type variables.
    • Consider using a restricted user account for the the hub user and for any GitLab user associated with the GITLAB_TOKEN.

    Some of these variables contain sensitive information that must be secured.

    Type Key Example value Notes
    Variable CODESONAR_HUB_URL https://codesonar.example.com:7340
    Variable CODESONAR_REMOTE_LAUNCHDS /analysis-data-server/* Specifies a remote launch daemon to manage your analysis data after the analysis phase is complete. If your codesonar analyze command specifies -remote instead of remote-archive, the launch daemon will also manage the analysis phase, otherwise it will be managed locally. For CodeSonar SaaS, set to /saas/*. If you set up an analysis data server in part B, set to /analysis-data-server/* (assuming you created a launchd group with path /analysis-data-server).
    File CODESONAR_HUB_USER_CERT_FILE -----BEGIN CERTIFICATE-----\nMIIEabcdefghi ... For certificate-based authentication only. You do not need to escape newline characters when entering the secret value in the CI/CD variable value form. The '\n' characters in the example are not intended to be copied literally.
    File CODESONAR_HUB_USER_KEY_FILE -----BEGIN PRIVATE KEY-----\nMIIEjklmnopqr ... For certificate-based authentication only. You do not need to escape newline characters when entering the secret value in the CI/CD variable value form. The '\n' characters in the example are not intended to be copied literally.
    Variable CODESONAR_HUBUSER cshub_ci For password-based authentication only.
    File CODESONAR_HUBPWFILE p@55W0Rd For password-based authentication only.
    Variable CODESONAR_MAX_WARNINGS 5 The pipeline is configured to fail if there are more than CODESONAR_MAX_WARNINGS warnings with score exceeding CODESONAR_WARNING_THRESHOLD. This provides you with a simple means to alert developers to new, significant warnings affecting their code.
    Variable CODESONAR_VISIBILITY_FILTER active Only warnings satisfying this filter will be evaluated for threshold comparison (as described for CODESONAR_MAX_WARNINGS) and for the GitLab security report. A factory default filter such as "all" or "active" is typical, but you can use the name of any saved search available to your hub user. CodeSonar 6.0 and earlier require the filter to be specified as a numeric ID.
    Variable CODESONAR_WARNING_THRESHOLD 70 See CODESONAR_MAX_WARNINGS.
    Variable GITLAB_TOKEN BSDrHhFPWJkq3F9w5kfy Used to automatically generate a merge request comment containing a summary of the CodeSonar analysis. To use this feature, you will need to generate a GitLab API Access Token. GitLab provides several ways to do this. A Project Access Token is likely the best option, see: https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html. The Access Token will need full "api" (read and write) access.
  5. Create a new branch.

    For example, if you decide to name your branch "ci/codesonar":

    git checkout -b ci/codesonar
    
  6. Add and commit your updated files to your Git repository and push the changes to GitLab.

    If you need to add the GitLab server certificate file to your repository, you should do that here too.

    For example:

    git add .gitlab-ci.yml  gitlab.root.cacert
    git commit -m "Add CodeSonar pipeline job"
    git push -u origin ci/codesonar
    
  7. Follow GitLab instructions to create a merge request for your new branch.

    When the pipeline completes, you should see "Security scanning" results on the merge request "Overview" page.

  8. Click the "View full report" button to see the analysis results.

    Notice that the results shown in the "full report" are associated with a pipeline.

    The analysis report will include hyperlinks to your CodeSonar hub.