Step 3 - SonarQube

- Thomas Jungbauer Thomas Jungbauer ( Lastmod: 2024-07-02 ) - 5 min read

After the Pipeline has been created and tested we will add another Task to verify the source code and check for possible security issues, leveraging the tool SonarQube by Sonar.

Goals

The goals of this step are:

  • Install and configure SonarQube

  • Add a Task to scan our source code for vulnerabilities

  • Verify the results.

SonarQube

SonarQube by Sonar helps developers to deliver clean code. With the integration into our CICD pipeline it will detect issues and reports them back to the developers. The results will be shown in a dashboard. We will install the Community version of SonarQube, which is enough for our showcase.

SonarQube Installation

To install SonarQube I have prepared a Helm Chart that I use with GitOps when I deploy a new lab environment. Feel free to use it. It simply calls the Chart that is provided by Sonar. In addition, it creates a Job that changes the default administrator password to a different one.

The values file is good to go, the only item you must change is the route in the very first line. Also, if you prefer not to deploy any plugins (for example, the German language pack), you can remove the appropriate line.

Before you run the Helm, you need to provide a Sealed Secret (or manually create a Secret) like the following:

kind: Secret
apiVersion: v1
metadata:
  name: credentials
  namespace: sonarqube
data:
  adminpass: <your base64 password string>
type: Opaque

This password will be used by the Job "change-admin-password" and will configure a new password for the user "admin"

Once everything is installed (this will take several minutes), you can access SonarQube using the URL you defined in the values file.

SonarQube
Figure 1. SonarQube

SonarQube Create Token

Our Pipeline will talk to SonarQube and request a scan of the source code. To be able to do this, we need to create a Token in SonarQube and store it as a Secret in our ci namespace.

Click on "My Account" (Upper right corner) > Security and create a new token:

SonarQube Token
Figure 2. SonarQube Token

Copy the token and create the following Secret:

kind: Secret
apiVersion: v1
metadata:
  name: globex-ui-sonarqube-secret
  namespace: ci
stringData:
  token: <your SonarQube Token> (1)
type: Opaque
1The generated Token NOT base64 encrypted (I am using stringData in this case)

Modify the Pipeline

Now it is time to bring the SonarQube scan task into our Pipeline. This requires some modifications to existing objects and the creation of some new ones.

  1. Modify the TriggerBinding.

    Add the following lines to globex-ui TriggerBinding

        - name: sonarqubeHostUrl
          value: http://sonarqube.apps.ocp.aws.ispworld.at/ (1)
    1The URL of SonarQube

    So, it will look like this:

    apiVersion: triggers.tekton.dev/v1alpha1
    kind: TriggerBinding
    metadata:
      name: globex-ui
      namespace: ci
    spec:
      params:
        - name: tlsVerify
          value: 'false'
        - name: gitRepoHost
          value: github.com
        - name: sonarqubeHostUrl
          value: https://sonarqube.apps.ocp.aws.ispworld.at/ (1)
    1The URL of SonarQube
  2. Modify the TriggerTemplate

    Add the following lines:

    spec:
      params: (1)
        - description: Sonarqube host url
          name: sonarqubeHostUrl
    ...
      resourcetemplates:
        - apiVersion: tekton.dev/v1beta1
          spec:
            params: (2)
              - name: SONARQUBE_HOST_URL
                value: $(tt.params.sonarqubeHostUrl)
              - name: SONARQUBE_PROJECT_KEY
                value: globex-ui (3)
              - name: SONARQUBE_PROJECT_SECRET
                value: globex-ui-sonarqube-secret (4)
    ...
    1Parameters provided by the TriggerBinding.
    2Parameters provided to the Pipeline.
    3Project that will be created in SonarQube.
    4Secret of the SonarQube token.

    The result should look like the following:

    apiVersion: triggers.tekton.dev/v1alpha1
    kind: TriggerTemplate
    metadata:
      name: app-globex-ui-template
      namespace: ci
    spec:
      params:
        - description: The git repository URL.
          name: gitrepositoryurl
        - description: The repository name for this PullRequest.
          name: fullname
        - description: The git branch for this PR.
          name: io.openshift.build.commit.ref
        - description: the specific commit SHA.
          name: io.openshift.build.commit.id
        - description: The date at which the commit was made
          name: io.openshift.build.commit.date
        - description: The commit message
          name: io.openshift.build.commit.message
        - description: The name of the github user handle that made the commit
          name: io.openshift.build.commit.author
        - description: The host name of the git repo
          name: gitRepoHost
        - description: Enable image repository TLS certification verification.
          name: tlsVerify
        - description: Extra parameters passed for the push command when pushing images.
          name: build_extra_args
        - description: Target image repository name
          name: imageRepo
        - description: Sonarqube host url
          name: sonarqubeHostUrl
      resourcetemplates:
        - apiVersion: tekton.dev/v1beta1
          kind: PipelineRun
          metadata:
            generateName: secure-supply-chain-
          spec:
            params:
              - name: REPO_HOST
                value: $(tt.params.gitRepoHost)
              - name: GIT_REPO
                value: $(tt.params.gitrepositoryurl)
              - name: TLSVERIFY
                value: $(tt.params.tlsVerify)
              - name: BUILD_EXTRA_ARGS
                value: $(tt.params.build_extra_args)
              - name: IMAGE_REPO
                value: $(tt.params.imageRepo)
              - name: IMAGE_TAG
                value: >-
                  $(tt.params.io.openshift.build.commit.ref)-$(tt.params.io.openshift.build.commit.id)
              - name: COMMIT_SHA
                value: $(tt.params.io.openshift.build.commit.id)
              - name: GIT_REF
                value: $(tt.params.io.openshift.build.commit.ref)
              - name: COMMIT_DATE
                value: $(tt.params.io.openshift.build.commit.date)
              - name: COMMIT_AUTHOR
                value: $(tt.params.io.openshift.build.commit.author)
              - name: COMMIT_MESSAGE
                value: $(tt.params.io.openshift.build.commit.message)
              - name: SONARQUBE_HOST_URL
                value: $(tt.params.sonarqubeHostUrl)
              - name: SONARQUBE_PROJECT_KEY
                value: globex-ui
              - name: SONARQUBE_PROJECT_SECRET
                value: globex-ui-sonarqube-secret
            pipelineRef:
              name: secure-supply-chain
            serviceAccountName: pipeline
            workspaces:
              - name: shared-data
                volumeClaimTemplate:
                  metadata:
                    creationTimestamp: null
                  spec:
                    accessModes:
                      - ReadWriteOnce
                    resources:
                      requests:
                        storage: 3Gi
                  status: {}
  3. Create the Task scan-source. This task will use the pulled source code and uses SonarQube to let it scan our code.

    apiVersion: tekton.dev/v1beta1
    kind: Task
    metadata:
      name: scan-code
      namespace: ci
    spec:
      description: >-
        Source code scan using sonar-scanner and SonarQube.
      params:
        - default: 'docker.io/sonarsource/sonar-scanner-cli:latest' (1)
          name: scanImage
          type: string
        - default: 'https://sonarqube-sonarqube.myplaceholder.com/' (2)
          name: sonarqubeHostUrl
          type: string
        - default: object-detection-rest
          name: sonarqubeProjectKey
          type: string
        - default: object-detection-rest-sonarqube-secret
          name: sonarqubeProjectSecret
          type: string
        - default: 'true'
          name: verbose
          type: string
      steps:
        - env:
            - name: SONAR_TOKEN_WEB_UI (3)
              valueFrom:
                secretKeyRef:
                  key: token
                  name: $(params.sonarqubeProjectSecret) (4)
          image: $(params.scanImage)
          name: scan-code
          resources: {}
          script: > (5)
            set -x
    
            echo $(ls -a)
    
            sonar-scanner -X -Dsonar.projectKey=$(params.sonarqubeProjectKey)
            -Dsonar.sources=./ -Dsonar.host.url=$(params.sonarqubeHostUrl)
            -Dsonar.login=$SONAR_TOKEN_WEB_UI
          workingDir: /workspace/repository
      workspaces: (6)
        - name: repository
    1Image containing SonarQube command line tool. The cluster must be able to connect to docker.io.
    2Default parameters for this Task that might be overwritten.
    3The Secret with the token.
    4Parameter as set by the PipelineRun which gets the value from the TriggerTemplate.
    5Script that is executed to scan the source code.
    6The workspace where we can find the source code.
  4. Update your Pipeline and add the following task:

spec:
  params:
    ...
    - name: SONARQUBE_HOST_URL
      type: string
    - name: SONARQUBE_PROJECT_KEY
      type: string
    - name: SONARQUBE_PROJECT_SECRET
      type: string
  tasks:
...
    - name: scan-source
      params: (1)
        - name: sonarqubeHostUrl
          value: $(params.SONARQUBE_HOST_URL)
        - name: sonarqubeProjectKey
          value: $(params.SONARQUBE_PROJECT_KEY)
        - name: sonarqubeProjectSecret
          value: $(params.SONARQUBE_PROJECT_SECRET)
      runAfter: (2)
        - pull-source-code
      taskRef: (3)
        kind: Task
        name: scan-code
      workspaces: (4)
        - name: repository
          workspace: shared-data
1Parameters that shall be provided for the Task.
2The task should run AFTER the source has been pulled …​ which makes sense.
3Reference to the Task we created above.
4Workspace shared-data where the source code was pulled from the previous Task.

The full pipeline objects now look like the following:

The Pipeline now has a second task:

Pipeline
Figure 3. Pipeline

Execute the Pipeline

Let’s update the README.md of our source code again to trigger another PipelineRun. After the code has been pulled it should now perform the second task and scan the quality of the source code.

You can monitor the progress of the PipelineRun again:

PipelineRun Details
Figure 4. PipelineRun Details

Once the PipelineRun executed both tasks successfully, we can check SonarQube.

The project globex-ui has been created which shows the results of our scan:

SonarQube Results
Figure 5. SonarQube Results

Summary

We have now added a Task to our Pipeline that performs a code analysis of our source code. The results are shown in SonarQube and the developers can react accordingly.