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 0000000000..04c71b693a --- /dev/null +++ b/task/sast-coverity-check/0.1/README.md @@ -0,0 +1,46 @@ +# 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 ( `SEVERITY_THRESHOLD`) is provided to override this configuration. +- The csdiff/v1 SARIF fingerprints are provided for all findings +- [Known false positives](https://gitlab.cee.redhat.com/osh/known-false-positives/) are eliminated by default. A parameter ( `KFP_GIT_URL`) is provided to disable this feature or to configure a custom known false positives repository. + +> NOTE: This task is executed only if there is a Coverity license set up in the environment. 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 | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| COV_CAPTURE_ARGS | Append arguments to the Coverity Capture CLI command | +| COV_ANALYZE_ARGS | Append arguments to the cov-analyze CLI command | +| COV_LICENSE | Name of secret which contains the Coverity license | +| SEVERITY_THRESHOLD | Report only vulnerabilities at the specified level or higher. Default is 1 (Report important findings). To report all findings, specify "0" | +| KFP_GIT_URL | Known False Positives git URL, optionally taking a revision delimited by #; If empty, filtering of known false positives is disabled. | +| PROJECT_NVR | Name-Version-Release (NVR) of the scanned project, used to find path exclusions (it is optional) | +| RECORD_EXCLUDED | File to store all excluded findings to (it is optional) | + +## 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 0000000000..6a517f2ed5 --- /dev/null +++ b/task/sast-coverity-check/0.1/sast-coverity-check.yaml @@ -0,0 +1,226 @@ +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 doesn't 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: "" + - description: Arguments to be appenden to the coverity capture command + name: COV_CAPTURE_ARGS + type: string + default: "" + - description: Arguments to be appenden 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: SEVERITY_THRESHOLD + type: string + description: Report only vulnerabilities at the specified level or higher. Default is 1 (report only important findings) + default: "1" + - name: KFP_GIT_URL + type: string + description: URL from repository to download known false positives files + default: "https://gitlab.cee.redhat.com/osh/known-false-positives.git" + - 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: File to store all excluded findings to (it is optional) + default: "" + volumes: + - name: cov-license + secret: + secretName: $(params.COV_LICENSE) + optional: false + steps: + - name: sast-coverity-check + # TODO: Change image + image: quay.io/redhat-user-workloads/sast-tenant/sast-scanner/coverity@sha256:ce98d4a80a2b77cd1b51fb11d563515226331fdf9521daa1b82da5b0a99e8d22 + 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" + 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: SEVERITY_THRESHOLD + value: $(params.SEVERITY_THRESHOLD) + - 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/cov-scan/cov + COVERITY_RESULTS_FILE=coverity-buildless-results.js + COV_LICENSE_PATH=/etc/secrets/cov-license + # Installing Coverity license + cp /etc/secrets/cov-license /opt/coverity/bin/license.dat + if test -z "$(find /opt -name 'license.dat')"; then + echo "No license file for Coverity was detected. Exiting..." + exit 1 + fi + + # Installation of Red Hat certificates for cloning Red Hat internal repositories + curl -sS https://certs.corp.redhat.com/certs/2015-IT-Root-CA.pem > /etc/pki/ca-trust/source/anchors/2015-RH-IT-Root-CA.crt + curl -sS https://certs.corp.redhat.com/certs/2022-IT-Root-CA.pem > /etc/pki/ca-trust/source/anchors/2022-IT-Root-CA.pem + update-ca-trust + + # Create configuration file for coverity buildless + echo -e 'capture:\n build-command-inference: false' > $SOURCE_CODE_DIR/coverity.yml + + # Captureless scan + env COV_HOST=konflux HOME=/tmp/cov-scan/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 + + /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. + csgrep --mode=json --imp-level="$SEVERITY_THRESHOLD" --prepend-path-prefix=$SOURCE_CODE_DIR/ $COVERITY_RESULTS_FILE \ + | csgrep --mode=json --remove-duplicates --embed-context 3 \ + | csgrep --mode=json --strip-path-prefix="$SOURCE_CODE_DIR"/source/ \ + | csgrep --mode=json --strip-path-prefix="/tmp/cov-scan/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 [[ -n "${RECORD_EXCLUDED}" ]]; then + CMD+=(--record-excluded="${RECORD_EXCLUDED}") + 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 + SCAN_RESULT="filtered_sast_unicode_check_out.json" + 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 + + UPLOAD_FILE=coverity-buildless-results.sarif + MEDIA_TYPE=application/sarif+json + + 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 record_excluded.json" + for UPLOAD_FILE in ${UPLOAD_FILES}; do + if [ ! -f "${UPLOAD_FILE}" ]; then + echo "No ${UPLOAD_FILE} exists. Skipping upload." + exit 0; + 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}" + workspaces: + - name: workspace diff --git a/task/sast-coverity-check/OWNERS b/task/sast-coverity-check/OWNERS new file mode 100644 index 0000000000..27203edec2 --- /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