CodeSonar analysis in a GitLab pipeline on Linux with Docker

The following instructions are for Linux containers using Docker.

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

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.

    This "CI builder" machine is distinct from the "analysis data server" machine prepared previously, however it is possible to use the same machine for both analysis data server and CI builder.

    • This can be a physical machine or a virtual machine.
  3. Log in to your CI builder machine as root.

  4. If it is not already installed, use your package manager to install Docker Engine.

    See https://docs.docker.com/engine/install/#server for more information

  5. If GitLab Runner is not already installed, install and register a GitLab Runner container.

    (If you prefer, you can run a GitLab Runner instance directly on the builder host machine. These instructions assume that you want to run it in a container. If the runner runs in a container, then it will start pipeline job containers using a "bind-mounted docker socket".)

    Instructions for configuring a GitLab Runner for Docker-based pipelines that itself runs inside a Docker container will be provided below. See the GitLab documentation for additional information:

    To create and initialize the runner container, do the following.

    1. Get a GitLab Runner registration token from the "CI/CD" Runner settings page on your GitLab instance.

      Note that you can register a runner at the project, group, or global level.

    2. Set up variables.

      CI_RUNNER_NAME=gitlab-runner
      CI_RUNNER_VOL=$CI_RUNNER_NAME.config
      CI_RUNNER_CONTAINER=$CI_RUNNER_NAME
      
    3. Create a Docker volume to save runner configuration files. The data in this volume will be persisted even when the container stops.

      docker volume create $CI_RUNNER_VOL
      
    4. Start the runner as a restartable background process: this will initialize the configuration in the configuration volume.

      docker run \
         --detach \
         --restart unless-stopped \
         --name $CI_RUNNER_CONTAINER \
         -v /var/run/docker.sock:/var/run/docker.sock \
         -v $CI_RUNNER_VOL:/etc/gitlab-runner \
         gitlab/gitlab-runner:latest
      
    5. Register the runner using a temporary container.

      docker run --rm -it \
         -v $CI_RUNNER_VOL:/etc/gitlab-runner \
         gitlab/gitlab-runner:latest register
      

      You will be prompted to provide runner registration information.

      • Registration token: enter the runner registration token you obtained from your GitLab server instance.
      • Tags: Linux, Docker. These tags will help ensure your pipeline jobs execute on the correct runner.
      • Executor: docker.

      This will update the runner configuration volume with runner registration information.

    6. Restart the background runner so that it will read the new configuration.

      docker restart $CI_RUNNER_CONTAINER
      
  6. Log out of the CI builder machine.

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.

You don't need to be logged in to your runner machine to create pipeline job images.

The steps below should be carried out from your developer workstation.

  1. Create a Docker image for building your code.

    1. Create an empty directory named "zlib.build".

      The zlib.build directory can exist anywhere on disk. It could be saved in the Git repository, but we will not assume so in this example.

    2. Save the content below to a file named "Dockerfile" in the "zlib.build" directory.

      FROM ubuntu:18.04
      RUN apt-get update && apt-get install -y git gcc make
      
    3. Change to the zlib.build directory.

      cd zlib.build
      
    4. Execute the following commands to build the image and push it to the GitLab Docker registry. The CI builder runner will be able to download images from the Docker registry.

      Make any necessary changes to variable settings before executing.

      Variable Setting
      CI_REGISTRY The address of your Docker registry: typically the host name of your GitLab server plus port 5050.
      CI_REGISTRY_IMAGE The fully-qualified Docker image name. This will include the name of the Docker registry where it is found (in this case, CI_REGISTRY).
      CI_IMAGE_VERSION A Docker image tag which indicates the version of the image.
      CI_REGISTRY=gitlab.example.com:5050
      CI_REGISTRY_IMAGE=$CI_REGISTRY/group/project
      CI_IMAGE_VERSION=latest
      
      docker login $CI_REGISTRY
      docker build -t $CI_REGISTRY_IMAGE:$CI_IMAGE_VERSION - < Dockerfile
      docker push $CI_REGISTRY_IMAGE:$CI_IMAGE_VERSION
      
  2. Create a new file named ".gitlab-ci.yml" and save it in your local repository clone.

  3. 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
        - Docker
      image: $CI_REGISTRY_IMAGE:latest
      script:
        - ./configure
        - make all
    
  4. 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
    
  5. 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
    
  6. 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. Download and extract 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. Change to the distro-image subdirectory in the extracted CodeSonar GitLab integration files.

    Inspect the contents. There should be a Dockerfile: this can be used to create a new Docker image from your project's base image with CodeSonar added to it.

  4. Copy your CodeSonar software installation file into the distro-image subdirectory. The name will be something like codesonar-7.3p0.20230330-x86_64-pc-linux.tar.gz (version number and datestamp will vary).

  5. [HTTPS hubs only] Download a copy of your hub's hub server certificate. We will use this copy to ensure that the CodeSonar command line tools in your Docker image will trust your hub.

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

    • Otherwise:

      1. Download the hub server certificate from the hub Configure HTTPS page.

        See the Troubleshooting document for additional information about how to download this file.

      2. Save the certificate in Base-64 ASCII text format (often called "PEM" format) as distro-image/cacert.pem.

  6. Build a new CodeSonar Docker image using distro-image/Dockerfile.

    This new CodeSonar Docker image will be based on your project's builder Docker image. It will be used to introduce CodeSonar into your pipeline. This will allow CodeSonar to observe and analyze your code as it builds.

    Consult the comments in distro-image/Dockerfile for descriptions of the Docker build arguments used in this section. Many of the build arguments used here are optional and are intended to reduce the size of the Docker image. In particular, zlib contains no Java code so we omit all the components used for Java analysis. If you want Java (or .NET) analysis, then remove --buildarg JAVA_ANALYSIS=0 from the command below.

    Make any necessary changes to variable settings before executing. Note that some of the variables used here were initialized in previous steps.

    Variable Setting
    CI_REGISTRY, CI_REGISTRY_IMAGE, CI_IMAGE_VERSION Must have exactly the same values as were used to create the builder image.
    CODESONAR_PACKAGE The name of the CodeSonar installer file that you copied to distro-image
    CI_REGISTRY=gitlab.example.com:5050
    CI_REGISTRY_IMAGE=$CI_REGISTRY/group/project
    CI_IMAGE_VERSION=latest
    CODESONAR_PACKAGE=codesonar-7.3p0.20230330-x86_64-pc-linux.tar.gz
    CODESONAR_IMAGE=$CI_REGISTRY_IMAGE/codesonar
    CODESONAR_IMAGE_VERSION=$CI_IMAGE_VERSION
    
    docker build --tag $CODESONAR_IMAGE:$CODESONAR_IMAGE_VERSION \
       --build-arg BASE_IMAGE=$CI_REGISTRY_IMAGE:$CI_IMAGE_VERSION \
       --build-arg CODESONAR_PACKAGE=$CODESONAR_PACKAGE \
       --build-arg CODESONAR_HUB_CACERT=cacert.pem \
       --build-arg TELEMETRY=1 \
       --build-arg HUB=0 \
       --build-arg JAVA_ANALYSIS=0 \
       --build-arg PYTHON_ANALYSIS=0 \
       --build-arg JAVA_API=0 \
       --build-arg CSHARP_API=0 \
       --build-arg ECLIPSE=0 \
       .
    
  7. Push the new CodeSonar Docker image to your container registry:

    docker login $CI_REGISTRY
    docker push $CODESONAR_IMAGE:$CODESONAR_IMAGE_VERSION
    

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, install it before proceeding. Alternatively, you could start a shell in a temporary Docker container on the CodeSonar analysis image that you built in part E and use the CodeSonar command line tools there.

    1. Start a new container using your CodeSonar Docker image, and open an interactive bash shell prompt.

      CI_REGISTRY=gitlab.example.com:5050
      CI_REGISTRY_IMAGE=$CI_REGISTRY/group/project
      CI_IMAGE_VERSION=latest
      CODESONAR_IMAGE=$CI_REGISTRY_IMAGE/codesonar
      CODESONAR_IMAGE_VERSION=latest
      
      docker run -it "$CODESONAR_IMAGE:$CODESONAR_IMAGE_VERSION" bash
      
    2. 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"
      
    3. 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.

      You can use the standard cat command to show the contents of the certificate and/or key files as needed.

    4. Copy these two files and save them somewhere temporarily.

    5. Exit the bash shell and the container.

      exit
      

  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 image keywords to refer to the "CI builder" and "CodeSonar" Docker images you created earlier.
    • 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
        - Docker
      image: $CI_REGISTRY_IMAGE:latest
      script:
        - ./configure
        - make all
    
    codesonar-sast:
      stage: test
      tags:
        - Linux
        - Docker
      image: $CI_REGISTRY_IMAGE/codesonar:latest
      variables:
        SARIF2SAST: "/opt/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: the contents of the hub user certificate file that you generated above. 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: the contents of the hub user certificate private key file that you generated above. 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.