From 9878cfcd6951165d39d2fc88ded95698cb69585a Mon Sep 17 00:00:00 2001 From: jperezde Date: Thu, 26 Sep 2024 20:55:48 +0200 Subject: [PATCH] sast: initial task for Coverity Buildless Solves: https://issues.redhat.com/browse/OSH-740 Initial version of the Coverity Buildless task. In introduces two different tasks: A task checking the availability of Coverity license and authentication token, and a task for scanning the code. The code will be scanned using coverity buildless mode, then the results are processing using csgrep and the results are later filtered using csfilter-kfp. --- pipelines/template-build/template-build.yaml | 39 +++ .../coverity-availability-check/0.1/README.md | 25 ++ .../0.1/coverity-availability-check.yaml | 78 +++++ task/coverity-availability-check/OWNERS | 5 + task/sast-coverity-check/0.1/README.md | 47 +++ .../0.1/sast-coverity-check.yaml | 267 ++++++++++++++++++ task/sast-coverity-check/OWNERS | 5 + 7 files changed, 466 insertions(+) create mode 100644 task/coverity-availability-check/0.1/README.md create mode 100644 task/coverity-availability-check/0.1/coverity-availability-check.yaml create mode 100644 task/coverity-availability-check/OWNERS create mode 100644 task/sast-coverity-check/0.1/README.md create mode 100644 task/sast-coverity-check/0.1/sast-coverity-check.yaml create mode 100644 task/sast-coverity-check/OWNERS diff --git a/pipelines/template-build/template-build.yaml b/pipelines/template-build/template-build.yaml index 6e8ff4d61..33e78842d 100644 --- a/pipelines/template-build/template-build.yaml +++ b/pipelines/template-build/template-build.yaml @@ -238,6 +238,45 @@ spec: value: $(tasks.build-image-index.results.IMAGE_DIGEST) - name: image-url value: $(tasks.build-image-index.results.IMAGE_URL) + - name: coverity-availability-check + runAfter: + - build-image-index + taskRef: + # FIXME: This needs testing as it can not be tested on our local instance. + name: coverity-availability-check + version: "0.1" + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + workspaces: + - name: workspace + workspace: workspace + - name: sast-coverity-check + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + runAfter: + - coverity-availability-check + taskRef: + taskRef: + # FIXME: This needs testing as it can not be tested on our local instance. + name: sast-coverity-check + version: "0.1" + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - input: $(tasks.coverity-availability-check.results.TASK_OUTPUT) + operator: in + values: [ "success" ] + workspaces: + - name: workspace + workspace: workspace - name: apply-tags runAfter: - build-image-index diff --git a/task/coverity-availability-check/0.1/README.md b/task/coverity-availability-check/0.1/README.md new file mode 100644 index 000000000..361170691 --- /dev/null +++ b/task/coverity-availability-check/0.1/README.md @@ -0,0 +1,25 @@ +# coverity-availability-check task + +## Description: + +This task performs needed checks in order to use Coverity image in the pipeline. It will check for a Coverity license secret and an authentication secret for pulling the image. + +The characteristics of these tasks are: + +- It will check for a secret called "auth-token-coverity-image" where the authentication token for pulling Coverity image is pulled. +- It will check for a secret called "cov-license" where the Coverity license is stored. + +> NOTE: If any of these tasks fails, the sast-coverity-task check won't be executed. The Coverity license can be used by Red Hat employees only and it needs to be protected such that external users cannot access the license. + +## Params: + +| name | description | default value | required | +|-----------------------------|----------------------------------------------------------------------------------------|----------------------------|----------| +| AUTH_TOKEN_COVERITY_IMAGE | Name of secret which contains the authentication token for pulling the Coverity image | auth-token-coverity-image | yes | +| COV_LICENSE | Name of secret which contains the Coverity license | cov-license | yes | + +## Results: + +| name | description | +|-------------|--------------------------| +| TASK_OUTPUT | Tekton task test output. | diff --git a/task/coverity-availability-check/0.1/coverity-availability-check.yaml b/task/coverity-availability-check/0.1/coverity-availability-check.yaml new file mode 100644 index 000000000..7363456bf --- /dev/null +++ b/task/coverity-availability-check/0.1/coverity-availability-check.yaml @@ -0,0 +1,78 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "konflux" + name: sast-coverity-check +spec: + description: >- + This task performs needed checks in order to use Coverity image in the pipeline. It will check for a Coverity license secret and an authentication secret for pulling the image. + results: + - description: Tekton task result output. + name: TASK_OUTPUT + params: + - name: COV_LICENSE + description: Name of secret which contains the Coverity license + default: cov-license + - name: AUTH_TOKEN_COVERITY_IMAGE + description: Name of secret which contains the authentication token for pulling the Coverity image. + default: "auth-token-coverity-image" + volumes: + - name: cov-license + secret: + secretName: $(params.COV_LICENSE) + optional: true + - name: auth-token-coverity-image + secret: + secretName: $(params.AUTH_TOKEN_COVERITY_IMAGE) + optional: true + steps: + - name: coverity-availability-check + image: quay.io/redhat-appstudio/konflux-test:v1.4.7@sha256:cf6808a3bd605630a5d9f20595ff7c43f8645c00381219d32f5a11e88fe37072 + onError: stopAndFail + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + workingDir: $(workspaces.workspace.path)/hacbs/$(context.task.name) + volumeMounts: + - name: cov-license + mountPath: "/etc/secrets/cov" + readOnly: true + - name: auth-token-coverity-image + mountPath: "/etc/secrets/auth/config.json" + subPath: .dockerconfigjson + env: + - name: COV_LICENSE + value: $(params.COV_LICENSE) + - name: AUTH_TOKEN_COVERITY_IMAGE + value: $(params.AUTH_TOKEN_COVERITY_IMAGE) + script: | + #!/usr/bin/env bash + set -eo pipefail + + # Checking Coverity license + COV_LICENSE_PATH=/etc/secrets/cov/cov-license + if [ -f "${COV_LICENSE_PATH}" ] && [ -s "${COV_LICENSE_PATH}" ]; then + echo "Coverity license detected!" + else + echo "No license file for Coverity was detected. Coverity scan won't be executed..." + echo "Please, create a secret called "cov-license" with a key called "cov-license" and the value containing the Coverity license." + exit 0 + fi + + # Checking authentication token for downloading coverity image + AUTH_TOKEN_COVERITY_IMAGE_PATH=/etc/secrets/auth/config.json + if [ -f "${AUTH_TOKEN_COVERITY_IMAGE_PATH}" ] && [ -s "${AUTH_TOKEN_COVERITY_IMAGE_PATH}" ]; then + echo "Authentication token detected!" + else + echo "No authentication token for downloading Coverity image detected. Coverity scan won't be executed..." + echo "Please, create an imagePullSecret named "auth-token-coverity-image" with the authentication token for pulling the Coverity image." + exit 0 + fi + + echo -n "success" | tee "$(results.TASK_OUTPUT.path)" + + workspaces: + - name: workspace diff --git a/task/coverity-availability-check/OWNERS b/task/coverity-availability-check/OWNERS new file mode 100644 index 000000000..27203edec --- /dev/null +++ b/task/coverity-availability-check/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs: https://go.k8s.io/owners +approvers: + - integration-team +reviewers: + - integration-team diff --git a/task/sast-coverity-check/0.1/README.md b/task/sast-coverity-check/0.1/README.md new file mode 100644 index 000000000..c93e6a476 --- /dev/null +++ b/task/sast-coverity-check/0.1/README.md @@ -0,0 +1,47 @@ +# sast-coverity-check task + +## Description: + +The sast-coverity-check task uses Coverity tool to perform Static Application Security Testing (SAST). In this task, we use the buildless mode, where Coverity has the ability to capture source code without the need of building the product. + +The documentation for this mode can be found here: https://sig-product-docs.synopsys.com/bundle/coverity-docs/page/commands/topics/coverity_capture.html + +The characteristics of these tasks are: + +- Perform buildless scanning with Coverity +- The whole source code is scanned (by scanning `$(workspaces.source.path)` ) +- Only important findings are reported by default. A parameter ( `IMP_FINDINGS_ONLY`) is provided to override this configuration. +- The csdiff/v1 SARIF fingerprints are provided for all findings +- A parameter ( `KFP_GIT_URL`) is provided to remove false positives providing a known false positives repository. By default, no repository is provided. + +> NOTE: This task is executed only if there is a Coverity license set up in the environment. Please check coverity-availability-check task for more information. + +## Params: + +| name | description | default value | required | +|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------|---------------------------|----------| +| COV_CAPTURE_ARGS | Append arguments to the Coverity Capture CLI command | "" | yes | +| COV_ANALYZE_ARGS | Append arguments to the cov-analyze CLI command | "" | yes | +| COV_LICENSE | Name of secret which contains the Coverity license | cov-license | yes | +| AUTH_TOKEN_COVERITY_IMAGE | Name of secret which contains the authentication token for pulling the Coverity image | auth-token-coverity-image | yes | +| IMP_FINDINGS_ONLY | Report only important findings. Default is true. To report all findings, specify "false" | true | yes | +| KFP_GIT_URL | Known False Positives git URL, optionally taking a revision delimited by #; If empty, filtering of known false positives is disabled. | "" | no | +| PROJECT_NVR | Name-Version-Release (NVR) of the scanned project, used to find path exclusions (it is optional) | "" | no | +| RECORD_EXCLUDED | If set to `true`, excluded findings will be written to a file named `excluded-findings.json` for auditing purposes. | false | no | + +## Results: + +| name | description | +|-------------------|--------------------------| +| TEST_OUTPUT | Tekton task test output. | + +## Source repository for image: + +// TODO: Add reference to private repo for the container image once the task is migrated to repo + + +## Additional links: + +* https://sig-product-docs.synopsys.com/bundle/coverity-docs/page/commands/topics/coverity_capture.html +* https://scan.coverity.com/ +* https://sig-product-docs.synopsys.com/bundle/coverity-docs/page/cli/topics/options_reference.html diff --git a/task/sast-coverity-check/0.1/sast-coverity-check.yaml b/task/sast-coverity-check/0.1/sast-coverity-check.yaml new file mode 100644 index 000000000..e54cf4009 --- /dev/null +++ b/task/sast-coverity-check/0.1/sast-coverity-check.yaml @@ -0,0 +1,267 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "konflux" + name: sast-coverity-check +spec: + description: >- + Scans source code for security vulnerabilities, including common issues such as SQL injection, cross-site scripting (XSS), and code injection attacks using Coverity. At the moment, this task only uses the captureless mode does not build the project in order to analyze it. + results: + - description: Tekton task test output. + name: TEST_OUTPUT + params: + - description: Image URL. + name: image-url + type: string + # In a future 0.2 version of the task, drop the default to make this required + default: "" + - description: Image digest to report findings for. + name: image-digest + type: string + # In a future 0.2 version of the task, drop the default to make this required + default: "" + - name: caTrustConfigMapName + type: string + description: The name of the ConfigMap to read CA bundle data from. + default: trusted-ca + - name: caTrustConfigMapKey + type: string + description: The name of the key in the ConfigMap that contains the CA bundle data. + default: ca-bundle.crt + - description: Arguments to be appended to the coverity capture command + name: COV_CAPTURE_ARGS + type: string + default: "" + - description: Arguments to be appended to the coverity analyze command + name: COV_ANALYZE_ARGS + type: string + default: "--enable HARDCODED_CREDENTIALS --security --concurrency --spotbugs-max-mem=4096" + - name: COV_LICENSE + description: Name of secret which contains the Coverity license + default: cov-license + - name: AUTH_TOKEN_COVERITY_IMAGE + description: Name of secret which contains the authentication token for pulling the Coverity image. + default: "auth-token-coverity-image" + - name: IMP_FINDINGS_ONLY + type: string + description: Report only important findings. Default is true. To report all findings, specify "false" + default: "true" + - name: KFP_GIT_URL + type: string + description: URL from repository to download known false positives files + default: "" # FIXME: Red Hat internal projects will default to https://gitlab.cee.redhat.com/osh/known-false-positives.git when KONFLUX-4530 is resolved + - name: PROJECT_NVR + type: string + description: Name-Version-Release (NVR) of the scanned project, used to find path exclusions (it is optional) + default: "" + - name: RECORD_EXCLUDED + type: string + description: Write excluded records in file. Useful for debugging (it is optional). + default: "false" + volumes: + - name: cov-license + secret: + secretName: $(params.COV_LICENSE) + optional: false + - name: auth-token-coverity-image + secret: + secretName: $(params.AUTH_TOKEN_COVERITY_IMAGE) + optional: false + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true + steps: + - name: sast-coverity-check + onError: stopAndFail + # TODO: Change image + #image: $(steps.secrets-check.results.image) + image: quay.io/redhat-user-workloads/sast-tenant/sast-scanner/coverity@sha256:1e6d26ef45da80128c6bbb4f29a29ac3e547e934e533cc5c1dd37e40439269fe + computeResources: + requests: + memory: "16Gi" + cpu: "8" + limits: + memory: "32Gi" + cpu: "16" + # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting + # the cluster will set imagePullPolicy to IfNotPresent + workingDir: $(workspaces.workspace.path)/hacbs/$(context.task.name) + volumeMounts: + - name: cov-license + mountPath: "/etc/secrets/cov" + readOnly: true + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + env: + - name: COV_ANALYZE_ARGS + value: $(params.COV_ANALYZE_ARGS) + - name: COV_CAPTURE_ARGS + value: $(params.COV_CAPTURE_ARGS) + - name: KFP_GIT_URL + value: $(params.KFP_GIT_URL) + - name: COV_LICENSE + value: $(params.COV_LICENSE) + - name: IMP_FINDINGS_ONLY + value: $(params.IMP_FINDINGS_ONLY) + - name: PROJECT_NVR + value: $(params.PROJECT_NVR) + - name: RECORD_EXCLUDED + value: $(params.RECORD_EXCLUDED) + script: | + #!/usr/bin/env bash + set -eo pipefail + . /usr/local/share/konflux-test/utils.sh + trap 'handle_error $(results.TEST_OUTPUT.path)' EXIT + + echo 'Starting Coverity buildless scan' + SOURCE_CODE_DIR=$(workspaces.workspace.path) + COVERITY_DIR=/tmp/coverity/idir + COVERITY_RESULTS_FILE=$(workspaces.workspace.path)/hacbs/$(context.task.name)/coverity-buildless-results.json + COV_LICENSE_PATH=/etc/secrets/cov/cov-license + # Installing Coverity license + cp "$COV_LICENSE_PATH" /opt/coverity/bin/license.dat + set +e + if test -z "$(find /opt -name 'license.dat')"; then + echo "No license file for Coverity was detected. Exiting..." + exit 1 + fi + set -e + + # Installation of Red Hat certificates for cloning Red Hat internal repositories + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + cp -vf $ca_bundle /etc/pki/ca-trust/source/anchors + update-ca-trust + fi + + # Create configuration file for coverity buildless + echo -e 'capture:\n build-command-inference: false' > "$SOURCE_CODE_DIR"/coverity.yml + + set +e + # Captureless scan + env COV_HOST=konflux HOME=/var/tmp/coverity/home /opt/coverity/bin/coverity capture $COV_CAPTURE_ARGS --project-dir $SOURCE_CODE_DIR --dir $COVERITY_DIR + COV_CAPTURE_EXIT_CODE=$? + + + if [[ "$COV_CAPTURE_EXIT_CODE" -eq 0 ]]; then + echo "Coverity capture scan finished successfully" + else + echo "Coverity capture command failed with exit code ${COV_CAPTURE_EXIT_CODE}. Exiting..." + exit 1 + fi + + # Analysis phase + /opt/coverity/bin/cov-manage-emit --dir $COVERITY_DIR reset-host-name + /opt/coverity/bin/cov-analyze $COV_ANALYZE_ARGS --dir=$COVERITY_DIR + COV_ANALYZE_EXIT_CODE=$? + + if [[ "$COV_ANALYZE_EXIT_CODE" -eq 0 ]]; then + echo "Coverity analyze scan finished successfully" + else + echo "Coverity analyze scan failed with exit code ${COV_ANALYZE_EXIT_CODE}. Exiting..." + exit 1 + fi + set -e + + /opt/coverity/bin/cov-format-errors --dir=$COVERITY_DIR --json-output-v10 $COVERITY_RESULTS_FILE + # We parse the results, embed context, remove duplicates and store them in SARIF format. + IMP_LEVEL=1 + if [[ "${IMP_FINDINGS_ONLY}" == "false" ]]; then + IMP_LEVEL=0 + fi + + (cd "$SOURCE_CODE_DIR" && csgrep --mode=json --imp-level="$IMP_LEVEL" --remove-duplicates --embed-context=3 $COVERITY_RESULTS_FILE) \ + | csgrep --mode=json --strip-path-prefix="$SOURCE_CODE_DIR"/source/ \ + | csgrep --mode=json --strip-path-prefix="/var/tmp/coverity/home" \ + > sast_coverity_buildless_check_all_findings.json + + echo "Results:" + (set -x; csgrep --mode=evtstat sast_coverity_buildless_check_all_findings.json) + + # We check if the KFP_GIT_URL variable is set to apply the filters or not + if [[ -z "${KFP_GIT_URL}" ]]; then + mv sast_coverity_buildless_check_all_findings.json filtered_sast_coverity_buildless_check_all_findings.json + else + echo "Filtering false positives in results files using csfilter-kfp..." + CMD=( + csfilter-kfp + --verbose + --kfp-git-url="${KFP_GIT_URL}" + ) + if [[ -n "${PROJECT_NVR}" ]]; then + CMD+=(--project-nvr="${PROJECT_NVR}") + fi + if [ "${RECORD_EXCLUDED}" == "true" ]; then + CMD+=(--record-excluded="excluded-findings.json") + fi + + "${CMD[@]}" sast_coverity_buildless_check_all_findings.json > filtered_sast_coverity_buildless_check_all_findings.json + status=$? + if [ "$status" -ne 0 ]; then + echo "Error: failed to filter known false positives" >&2 + return 1 + else + echo "Message: Succeed to filter known false positives" >&2 + fi + + echo "Results after filtering:" + (set -x; csgrep --mode=evtstat filtered_sast_coverity_buildless_check_all_findings.json) + fi + + csgrep --mode=sarif filtered_sast_coverity_buildless_check_all_findings.json > "$(workspaces.workspace.path)"/hacbs/"$(context.task.name)"/coverity-buildless-results.sarif + + if [[ -z "$(csgrep --mode=evtstat filtered_sast_coverity_buildless_check_all_findings.json)" ]]; then + note="Task $(context.task.name) success: No finding was detected" + ERROR_OUTPUT=$(make_result_json -r SUCCESS -t "$note") + else + TEST_OUTPUT= + parse_test_output "$(context.task.name)" sarif "$(workspaces.workspace.path)"/hacbs/"$(context.task.name)"/coverity-buildless-results.sarif || true + note="Task $(context.task.name) failed: For details, check Tekton task log." + ERROR_OUTPUT=$(make_result_json -r ERROR -t "$note") + fi + + echo "${TEST_OUTPUT:-${ERROR_OUTPUT}}" | tee "$(results.TEST_OUTPUT.path)" + - name: upload + image: quay.io/konflux-ci/oras:latest@sha256:99737f436051e6d3866eb8a8706463c35abf72c87f05090ff42ff642f6729661 + workingDir: $(workspaces.workspace.path)/hacbs/$(context.task.name) + env: + - name: IMAGE_URL + value: $(params.image-url) + - name: IMAGE_DIGEST + value: $(params.image-digest) + script: | + #!/usr/bin/env bash + + if [ -z "${IMAGE_URL}" ] || [ -z "${IMAGE_DIGEST}" ]; then + echo 'No image-url or image-digest param provided. Skipping upload.' + exit 0 + fi + UPLOAD_FILES="coverity-buildless-results.sarif excluded-findings.json" + + for UPLOAD_FILE in ${UPLOAD_FILES}; do + if [ ! -f "${UPLOAD_FILE}" ]; then + echo "No ${UPLOAD_FILE} exists. Skipping upload." + continue + fi + if [ "${UPLOAD_FILES}" == "excluded-findings.json" ]; then + MEDIA_TYPE=application/json + else + MEDIA_TYPE=application/sarif+json + fi + echo "Selecting auth" + select-oci-auth "${IMAGE_URL}" > "${HOME}/auth.json" + echo "Attaching to ${IMAGE_URL}" + oras attach --no-tty --registry-config "$HOME/auth.json" --artifact-type "${MEDIA_TYPE}" "${IMAGE_URL}" "${UPLOAD_FILE}:${MEDIA_TYPE}" + done + workspaces: + - name: workspace diff --git a/task/sast-coverity-check/OWNERS b/task/sast-coverity-check/OWNERS new file mode 100644 index 000000000..27203edec --- /dev/null +++ b/task/sast-coverity-check/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs: https://go.k8s.io/owners +approvers: + - integration-team +reviewers: + - integration-team