From 683e232c0d0f95bc3c3b1b959d3183135c47a0de Mon Sep 17 00:00:00 2001 From: Martin Jediny Date: Mon, 11 Nov 2024 10:55:14 +0100 Subject: [PATCH] feat(ISV-5130): push SBOMs to Atlas The rh-advisories pipeline now supports generating product-level SBOMs at release time and enhancing component-level SBOMs created at build time with additional release-time data. Signed-off-by: Martin Jediny --- pipelines/rh-advisories/README.md | 8 ++ pipelines/rh-advisories/rh-advisories.yaml | 125 +++++++++++++++++- schema/dataKeys.json | 14 ++ tasks/collect-atlas-params/README.md | 13 ++ .../collect-atlas-params.yaml | 69 ++++++++++ .../test-collect-atlas-params-bad-value.yaml | 45 +++++++ ...test-collect-atlas-params-nonexistent.yaml | 66 +++++++++ .../tests/test-collect-atlas-params-prod.yaml | 73 ++++++++++ .../test-collect-atlas-params-stage.yaml | 74 +++++++++++ tasks/create-product-sbom/README.md | 4 + .../create-product-sbom.yaml | 37 ++++-- .../tests/test-create-product-sbom-basic.yaml | 32 ++--- ...st-create-product-sbom-multiple-purls.yaml | 40 +++--- tasks/upload-sbom-to-atlas/README.md | 7 +- .../test-upload-sbom-to-atlas-cyclonedx.yaml | 20 +-- .../tests/test-upload-sbom-to-atlas-spdx.yaml | 18 +-- .../upload-sbom-to-atlas.yaml | 58 ++------ 17 files changed, 591 insertions(+), 112 deletions(-) create mode 100644 tasks/collect-atlas-params/README.md create mode 100644 tasks/collect-atlas-params/collect-atlas-params.yaml create mode 100644 tasks/collect-atlas-params/tests/test-collect-atlas-params-bad-value.yaml create mode 100644 tasks/collect-atlas-params/tests/test-collect-atlas-params-nonexistent.yaml create mode 100644 tasks/collect-atlas-params/tests/test-collect-atlas-params-prod.yaml create mode 100644 tasks/collect-atlas-params/tests/test-collect-atlas-params-stage.yaml diff --git a/pipelines/rh-advisories/README.md b/pipelines/rh-advisories/README.md index 58d707ab8..2dfc2a3a8 100644 --- a/pipelines/rh-advisories/README.md +++ b/pipelines/rh-advisories/README.md @@ -23,6 +23,14 @@ the rh-push-to-registry-redhat-io pipeline. | taskGitUrl | The url to the git repo where the release-service-catalog tasks to be used are stored | Yes | https://github.com/konflux-ci/release-service-catalog.git | | taskGitRevision | The revision in the taskGitUrl repo to be used | No | - | +## Changes in 1.6.0 +* Add collect-atlas-params task to fetch Atlas parameters from the RPA. +* Add create-product-sbom task to create product-level SBOMs. +* Add update-component-sbom task to update component-level SBOMs with release + info. +* Add upload-product-sbom task to push the product SBOM to Atlas. +* Add upload-component-sbom task to push the component-level SBOMs to Atlas. + ## Changes in 1.5.6 * new mandatory parameter resultsDirPath added to run-file-updates task diff --git a/pipelines/rh-advisories/rh-advisories.yaml b/pipelines/rh-advisories/rh-advisories.yaml index d363de12d..8d7794c7c 100644 --- a/pipelines/rh-advisories/rh-advisories.yaml +++ b/pipelines/rh-advisories/rh-advisories.yaml @@ -4,7 +4,7 @@ kind: Pipeline metadata: name: rh-advisories labels: - app.kubernetes.io/version: "1.5.6" + app.kubernetes.io/version: "1.6.0" annotations: tekton.dev/pipelines.minVersion: "0.12.1" tekton.dev/tags: release @@ -505,6 +505,60 @@ spec: workspaces: - name: data workspace: release-workspace + - name: update-component-sbom + when: + - input: "$(tasks.collect-atlas-params.results.secretName)" + operator: notin + values: [""] + taskRef: + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/update-component-sbom/update-component-sbom.yaml + resolver: git + workspaces: + - name: data + workspace: release-workspace + params: + - name: dataJsonPath + value: "$(tasks.collect-data.results.data)" + - name: downloadedSbomPath + value: "$(tasks.push-rpm-data-to-pyxis.results.sbomPath)" + runAfter: + - collect-data + - collect-atlas-params + - push-rpm-data-to-pyxis + - name: upload-component-sbom + when: + - input: "$(tasks.collect-atlas-params.results.secretName)" + operator: notin + values: [""] + params: + - name: sbomDir + value: "$(tasks.push-rpm-data-to-pyxis.results.sbomPath)" + - name: atlasSecretName + value: "$(tasks.collect-atlas-params.results.secretName)" + - name: ssoTokenUrl + value: "$(tasks.collect-atlas-params.results.ssoTokenUrl)" + - name: bombasticApiUrl + value: "$(tasks.collect-atlas-params.results.bombasticApiUrl)" + taskRef: + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml + resolver: git + workspaces: + - name: data + workspace: release-workspace + runAfter: + - update-component-sbom - name: check-data-keys params: - name: dataPath @@ -526,6 +580,75 @@ spec: workspace: release-workspace runAfter: - populate-release-notes-images + - name: collect-atlas-params + taskRef: + resolver: "git" + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/collect-atlas-params/collect-atlas-params.yaml + params: + - name: dataPath + value: "$(tasks.collect-data.results.data)" + workspaces: + - name: data + workspace: release-workspace + runAfter: + - collect-data + - name: create-product-sbom + when: + - input: "$(tasks.collect-atlas-params.results.secretName)" + operator: notin + values: [""] + params: + - name: dataJsonPath + value: "$(tasks.collect-data.results.data)" + taskRef: + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/create-product-sbom/create-product-sbom.yaml + resolver: git + workspaces: + - name: data + workspace: release-workspace + runAfter: + - collect-atlas-params + - populate-release-notes-images + - name: upload-product-sbom + when: + - input: "$(tasks.collect-atlas-params.results.secretName)" + operator: notin + values: [""] + params: + - name: sbomDir + value: "$(tasks.create-product-sbom.results.productSBOMPath)" + - name: atlasSecretName + value: "$(tasks.collect-atlas-params.results.secretName)" + - name: ssoTokenUrl + value: "$(tasks.collect-atlas-params.results.ssoTokenUrl)" + - name: bombasticApiUrl + value: "$(tasks.collect-atlas-params.results.bombasticApiUrl)" + taskRef: + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml + resolver: git + workspaces: + - name: data + workspace: release-workspace + runAfter: + - create-product-sbom - name: create-advisory retries: 5 params: diff --git a/schema/dataKeys.json b/schema/dataKeys.json index 892585352..c89b1958e 100644 --- a/schema/dataKeys.json +++ b/schema/dataKeys.json @@ -318,6 +318,20 @@ } } }, + "atlas": { + "type": "object", + "additionalProperties": false, + "properties": { + "server": { + "type": "string", + "description": "The release Atlas server to push SBOMs to", + "enum": [ + "stage", + "production" + ] + } + } + }, "slack": { "type": "object", "properties": { diff --git a/tasks/collect-atlas-params/README.md b/tasks/collect-atlas-params/README.md new file mode 100644 index 000000000..47890a636 --- /dev/null +++ b/tasks/collect-atlas-params/README.md @@ -0,0 +1,13 @@ +# collect-atlas-params + +Tekton task that collects the Atlas server option from the data file. Based on +the value of the "atlas.server" field ("stage" or "production"), outputs results +used to push SBOMs to Atlas. If no Atlas fields are present in the RPA, it +outputs empty strings as results, indicating that the Atlas push should be +skipped. + +## Parameters + +| Name | Description | Optional | Default value | +|----------|----------------------------------------------------------------------------------------------------------------|----------|---------------| +| dataPath | Path to the merged data JSON file generated by collect-data task and containing the Atlas configuration option | No | - | diff --git a/tasks/collect-atlas-params/collect-atlas-params.yaml b/tasks/collect-atlas-params/collect-atlas-params.yaml new file mode 100644 index 000000000..349c71d9c --- /dev/null +++ b/tasks/collect-atlas-params/collect-atlas-params.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: collect-atlas-params + labels: + app.kubernetes.io/version: "0.1.0" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: release +spec: + description: >- + Tekton task that collects Atlas API configuration options from the data file. + params: + - name: dataPath + type: string + description: | + Path to the JSON string of the merged data containing the Atlas config. + workspaces: + - name: data + results: + - name: bombasticApiUrl + type: string + description: | + URL of the bombastic API. + - name: ssoTokenUrl + type: string + description: | + URL of the SSO token issuer. + - name: secretName + type: string + description: | + The kubernetes secret to use to authenticate to bombastic. + steps: + - name: collect-atlas-params + image: + quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + script: | + #!/usr/bin/env bash + set -x + + DATA_FILE="$(workspaces.data.path)/$(params.dataPath)" + if [ ! -f "${DATA_FILE}" ] ; then + echo "ERROR: No valid data file was provided." + exit 1 + fi + + atlasServer=$(jq -r '.atlas.server' "$DATA_FILE") + if [ "$atlasServer" = "null" ]; then + # In this case, SBOM processing will be skipped. + bombasticApiUrl="" + ssoTokenUrl="" + secretName="" + elif [ "$atlasServer" = "stage" ]; then + bombasticApiUrl="https://sbom.atlas.release.stage.devshift.net" + ssoTokenUrl="https://auth.stage.redhat.com/auth/realms/EmployeeIDP/protocol/openid-connect/token" + secretName="atlas-staging-sso-secret" + elif [ "$atlasServer" = "production" ]; then + bombasticApiUrl="https://sbom.atlas.release.devshift.net" + ssoTokenUrl="https://auth.redhat.com/auth/realms/EmployeeIDP/protocol/openid-connect/token" + secretName="atlas-prod-sso-secret" + else + echo "ERROR: Unknown .atlas.server value '$atlasServer'. Expected 'stage' or 'production'." + exit 1 + fi + + echo -n "$bombasticApiUrl" > "$(results.bombasticApiUrl.path)" + echo -n "$ssoTokenUrl" > "$(results.ssoTokenUrl.path)" + echo -n "$secretName" > "$(results.secretName.path)" diff --git a/tasks/collect-atlas-params/tests/test-collect-atlas-params-bad-value.yaml b/tasks/collect-atlas-params/tests/test-collect-atlas-params-bad-value.yaml new file mode 100644 index 000000000..035ea9faa --- /dev/null +++ b/tasks/collect-atlas-params/tests/test-collect-atlas-params-bad-value.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-collect-atlas-params-bad-value + annotations: + test/assert-task-failure: "run-task" +spec: + description: | + Run the collect-atlas-params task with a bad value as atlasServer. + workspaces: + - name: tests-workspace + tasks: + - name: setup + workspaces: + - name: data + workspace: tests-workspace + taskSpec: + workspaces: + - name: data + steps: + - name: setup-values + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + script: | + #!/usr/bin/env bash + set -eux + + cat > "$(workspaces.data.path)/data.json" << EOF + { + "atlas": { + "server": "invalid" + } + } + EOF + - name: run-task + taskRef: + name: collect-atlas-params + params: + - name: dataPath + value: data.json + workspaces: + - name: data + workspace: tests-workspace + runAfter: + - setup diff --git a/tasks/collect-atlas-params/tests/test-collect-atlas-params-nonexistent.yaml b/tasks/collect-atlas-params/tests/test-collect-atlas-params-nonexistent.yaml new file mode 100644 index 000000000..3db891b11 --- /dev/null +++ b/tasks/collect-atlas-params/tests/test-collect-atlas-params-nonexistent.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-collect-atlas-params-nonexistent +spec: + description: | + Run the collect-atlas-params task with a missing atlasServer key. + workspaces: + - name: tests-workspace + tasks: + - name: setup + workspaces: + - name: data + workspace: tests-workspace + taskSpec: + workspaces: + - name: data + steps: + - name: setup-values + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + script: | + #!/usr/bin/env bash + set -eux + echo "{}" > "$(workspaces.data.path)/data.json" + - name: run-task + taskRef: + name: collect-atlas-params + params: + - name: dataPath + value: data.json + workspaces: + - name: data + workspace: tests-workspace + runAfter: + - setup + - name: check-result + params: + - name: secretName + value: $(tasks.run-task.results.secretName) + - name: ssoTokenUrl + value: $(tasks.run-task.results.ssoTokenUrl) + - name: bombasticApiUrl + value: $(tasks.run-task.results.bombasticApiUrl) + taskSpec: + params: + - name: secretName + - name: ssoTokenUrl + - name: bombasticApiUrl + steps: + - name: check-result + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + env: + - name: "SECRET_NAME" + value: '$(params.secretName)' + - name: "SSO_TOKEN_URL" + value: '$(params.ssoTokenUrl)' + - name: "BOMBASTIC_API_URL" + value: '$(params.bombasticApiUrl)' + script: | + #!/usr/bin/env bash + set -eux + + test "$SECRET_NAME" = "" + test "$SSO_TOKEN_URL" = "" + test "$BOMBASTIC_API_URL" = "" diff --git a/tasks/collect-atlas-params/tests/test-collect-atlas-params-prod.yaml b/tasks/collect-atlas-params/tests/test-collect-atlas-params-prod.yaml new file mode 100644 index 000000000..4c3b16b61 --- /dev/null +++ b/tasks/collect-atlas-params/tests/test-collect-atlas-params-prod.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-collect-atlas-params-prod +spec: + description: | + Run the collect-atlas-params task and verify the results. + workspaces: + - name: tests-workspace + tasks: + - name: setup + workspaces: + - name: data + workspace: tests-workspace + taskSpec: + workspaces: + - name: data + steps: + - name: setup-values + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + script: | + #!/usr/bin/env bash + set -eux + + cat > "$(workspaces.data.path)/data.json" << EOF + { + "atlas": { + "server": "production" + } + } + EOF + - name: run-task + taskRef: + name: collect-atlas-params + params: + - name: dataPath + value: data.json + workspaces: + - name: data + workspace: tests-workspace + runAfter: + - setup + - name: check-result + params: + - name: secretName + value: $(tasks.run-task.results.secretName) + - name: ssoTokenUrl + value: $(tasks.run-task.results.ssoTokenUrl) + - name: bombasticApiUrl + value: $(tasks.run-task.results.bombasticApiUrl) + taskSpec: + params: + - name: secretName + - name: ssoTokenUrl + - name: bombasticApiUrl + steps: + - name: check-result + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + env: + - name: "SECRET_NAME" + value: '$(params.secretName)' + - name: "SSO_TOKEN_URL" + value: '$(params.ssoTokenUrl)' + - name: "BOMBASTIC_API_URL" + value: '$(params.bombasticApiUrl)' + script: | + #!/usr/bin/env bash + set -eux + + test "$SECRET_NAME" = "atlas-prod-sso-secret" + test "$SSO_TOKEN_URL" = "https://auth.redhat.com/auth/realms/EmployeeIDP/protocol/openid-connect/token" + test "$BOMBASTIC_API_URL" = "https://sbom.atlas.release.devshift.net" diff --git a/tasks/collect-atlas-params/tests/test-collect-atlas-params-stage.yaml b/tasks/collect-atlas-params/tests/test-collect-atlas-params-stage.yaml new file mode 100644 index 000000000..41afaf041 --- /dev/null +++ b/tasks/collect-atlas-params/tests/test-collect-atlas-params-stage.yaml @@ -0,0 +1,74 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-collect-atlas-params-stage +spec: + description: | + Run the collect-atlas-params task and verify the results. + workspaces: + - name: tests-workspace + tasks: + - name: setup + workspaces: + - name: data + workspace: tests-workspace + taskSpec: + workspaces: + - name: data + steps: + - name: setup-values + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + script: | + #!/usr/bin/env bash + set -eux + + cat > "$(workspaces.data.path)/data.json" << EOF + { + "atlas": { + "server": "stage" + } + } + EOF + - name: run-task + taskRef: + name: collect-atlas-params + params: + - name: dataPath + value: data.json + workspaces: + - name: data + workspace: tests-workspace + runAfter: + - setup + - name: check-result + params: + - name: secretName + value: $(tasks.run-task.results.secretName) + - name: ssoTokenUrl + value: $(tasks.run-task.results.ssoTokenUrl) + - name: bombasticApiUrl + value: $(tasks.run-task.results.bombasticApiUrl) + taskSpec: + params: + - name: secretName + - name: ssoTokenUrl + - name: bombasticApiUrl + steps: + - name: check-result + image: quay.io/konflux-ci/release-service-utils:d320c36f3d707cd5bfe55fe783f70236c06cc2e5 + env: + - name: "SECRET_NAME" + value: '$(params.secretName)' + - name: "SSO_TOKEN_URL" + value: '$(params.ssoTokenUrl)' + - name: "BOMBASTIC_API_URL" + value: '$(params.bombasticApiUrl)' + script: | + #!/usr/bin/env bash + set -eux + + test "$SECRET_NAME" = "atlas-staging-sso-secret" + test "$SSO_TOKEN_URL" = "https://auth.stage.redhat.com/auth/realms\ + /EmployeeIDP/protocol/openid-connect/token" + test "$BOMBASTIC_API_URL" = "https://sbom.atlas.release.stage.devshift.net" diff --git a/tasks/create-product-sbom/README.md b/tasks/create-product-sbom/README.md index 86aac5b8b..ac6dace86 100644 --- a/tasks/create-product-sbom/README.md +++ b/tasks/create-product-sbom/README.md @@ -9,5 +9,9 @@ releaseNotes content. | ------------ | ----------------------------------------------------------------------- | -------- | ------------- | | dataJsonPath | Path to the JSON string of the merged data containing the release notes | No | - | +## Changes in 0.2.0 +* Output directory path instead of a file path. + ## Changes in 0.1.1 * The release-service-utils image was updated to include a fix when generating name of product level SBOM - it should be based on "{product name} {product version}" + diff --git a/tasks/create-product-sbom/create-product-sbom.yaml b/tasks/create-product-sbom/create-product-sbom.yaml index 5cd1c030a..411fc9c11 100644 --- a/tasks/create-product-sbom/create-product-sbom.yaml +++ b/tasks/create-product-sbom/create-product-sbom.yaml @@ -4,7 +4,7 @@ kind: Task metadata: name: create-product-sbom labels: - app.kubernetes.io/version: "0.1.1" + app.kubernetes.io/version: "0.2.0" annotations: tekton.dev/pipelines.minVersion: "0.12.1" tekton.dev/tags: release @@ -20,19 +20,38 @@ spec: description: Workspace to save the product-level SBOM to. results: - name: productSBOMPath - description: Relative path to the created product-level SBOM in the data workspace. + description: >- + Relative path to the directory containing the created product-level SBOM + in the data workspace. steps: - name: create-sbom - image: quay.io/konflux-ci/release-service-utils:8684920ccae6c73bd9f3f23367490a9c04653a09 + image: quay.io/konflux-ci/release-service-utils:221d71a4f6b1a50b36b685aa20d86d7df9de33fc script: | #!/usr/bin/env bash set -eux - SBOM_FILE="product_sbom.json" - SBOM_PATH="$(dirname "$(params.dataJsonPath)")/${SBOM_FILE}" - OUTPUT_PATH=$(workspaces.data.path)/${SBOM_PATH} - + # the SBOM is first created in a temporary file, because the name of the + # final SBOM depends on its contents; namely the product name and + # version + tmp_sbom="$(mktemp)" create_product_sbom --data-path "$(workspaces.data.path)/$(params.dataJsonPath)" \ - --output-path "$OUTPUT_PATH" + --output-path "$tmp_sbom" + + product_name="$(jq -r '.packages[0].name' "$tmp_sbom")" + product_version="$(jq -r '.packages[0].versionInfo' "$tmp_sbom")" + + # replace whitespace with dashes + normalized_name="$(echo -n "${product_name}" | tr '[:space:]' '-')" + + sbom_dir="product-sboms" + # the combination of name + version is later used as an ID in Atlas + sbom_path="${sbom_dir}/${normalized_name}-${product_version}.json" + + # takes into account the subdirectory of the data.json if any + subdir_sbom_path="$(dirname "$(params.dataJsonPath)")/${sbom_path}" + + output_path=$(workspaces.data.path)/${subdir_sbom_path} + mkdir -p "$(dirname "$output_path")" + mv "$tmp_sbom" "$output_path" - echo -n "$SBOM_PATH" > "$(results.productSBOMPath.path)" + echo -n "$(dirname "$subdir_sbom_path")" > "$(results.productSBOMPath.path)" diff --git a/tasks/create-product-sbom/tests/test-create-product-sbom-basic.yaml b/tasks/create-product-sbom/tests/test-create-product-sbom-basic.yaml index 4307a5bc5..3deea4f2b 100644 --- a/tasks/create-product-sbom/tests/test-create-product-sbom-basic.yaml +++ b/tasks/create-product-sbom/tests/test-create-product-sbom-basic.yaml @@ -18,7 +18,7 @@ spec: - name: data steps: - name: setup - image: quay.io/konflux-ci/release-service-utils:8684920ccae6c73bd9f3f23367490a9c04653a09 + image: quay.io/konflux-ci/release-service-utils:221d71a4f6b1a50b36b685aa20d86d7df9de33fc script: | #!/usr/bin/env sh set -eux @@ -29,16 +29,18 @@ spec: "product_name": "Red Hat Openstack Product", "product_version": "123", "cpe": "cpe:/a:example:openstack:el8", - "images": [ - { - "component": "test-component-1", - "purl": "test-component-1-purl-1" - }, - { - "component": "test-component-2", - "purl": "test-component-2-purl-1" - } - ] + "content": { + "images": [ + { + "component": "test-component-1", + "purl": "test-component-1-purl-1" + }, + { + "component": "test-component-2", + "purl": "test-component-2-purl-1" + } + ] + } } } EOF @@ -58,21 +60,21 @@ spec: - name: data workspace: tests-workspace params: - - name: sbom + - name: sbomDir value: $(tasks.run-task.results.productSBOMPath) taskSpec: params: - - name: sbom + - name: sbomDir workspaces: - name: data steps: - name: check-result - image: quay.io/konflux-ci/release-service-utils:8684920ccae6c73bd9f3f23367490a9c04653a09 + image: quay.io/konflux-ci/release-service-utils:221d71a4f6b1a50b36b685aa20d86d7df9de33fc script: | #!/usr/bin/env sh set -eux - cp "$(workspaces.data.path)/$(params.sbom)" sbom.json + cp "$(workspaces.data.path)/$(params.sbomDir)/Red-Hat-Openstack-Product-123.json" sbom.json test "$(jq -r '.name' sbom.json)" == "Red Hat Openstack Product 123" diff --git a/tasks/create-product-sbom/tests/test-create-product-sbom-multiple-purls.yaml b/tasks/create-product-sbom/tests/test-create-product-sbom-multiple-purls.yaml index 2b32e4c07..d75fbbc9d 100644 --- a/tasks/create-product-sbom/tests/test-create-product-sbom-multiple-purls.yaml +++ b/tasks/create-product-sbom/tests/test-create-product-sbom-multiple-purls.yaml @@ -18,7 +18,7 @@ spec: - name: data steps: - name: setup - image: quay.io/konflux-ci/release-service-utils:8684920ccae6c73bd9f3f23367490a9c04653a09 + image: quay.io/konflux-ci/release-service-utils:221d71a4f6b1a50b36b685aa20d86d7df9de33fc script: | #!/usr/bin/env sh set -eux @@ -29,20 +29,22 @@ spec: "product_name": "Red Hat Openstack Product", "product_version": "123", "cpe": "cpe:/a:example:openstack:el8", - "images": [ - { - "component": "test-component-1", - "purl": "test-component-1-purl-1" - }, - { - "component": "test-component-1", - "purl": "test-component-1-purl-2" - }, - { - "component": "test-component-2", - "purl": "test-component-2-purl-1" - } - ] + "content": { + "images": [ + { + "component": "test-component-1", + "purl": "test-component-1-purl-1" + }, + { + "component": "test-component-1", + "purl": "test-component-1-purl-2" + }, + { + "component": "test-component-2", + "purl": "test-component-2-purl-1" + } + ] + } } } EOF @@ -62,21 +64,21 @@ spec: - name: data workspace: tests-workspace params: - - name: sbom + - name: sbomDir value: $(tasks.run-task.results.productSBOMPath) taskSpec: params: - - name: sbom + - name: sbomDir workspaces: - name: data steps: - name: check-result - image: quay.io/konflux-ci/release-service-utils:8684920ccae6c73bd9f3f23367490a9c04653a09 + image: quay.io/konflux-ci/release-service-utils:221d71a4f6b1a50b36b685aa20d86d7df9de33fc script: | #!/usr/bin/env sh set -eux - cp "$(workspaces.data.path)/$(params.sbom)" sbom.json + cp "$(workspaces.data.path)/$(params.sbomDir)/Red-Hat-Openstack-Product-123.json" sbom.json test "$(jq -r '.name' sbom.json)" == "Red Hat Openstack Product 123" diff --git a/tasks/upload-sbom-to-atlas/README.md b/tasks/upload-sbom-to-atlas/README.md index 9b50cba86..fa504d33e 100644 --- a/tasks/upload-sbom-to-atlas/README.md +++ b/tasks/upload-sbom-to-atlas/README.md @@ -6,11 +6,14 @@ Supports both CycloneDX and SPDX format. | Name | Description | Optional | Default value | |---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------------------------------------------------------| -| productSBOMPath | Directory containing SBOM files. The task will search for CycloneDX JSON SBOMs recursively in this directory and upload them all to Atlas. The path is relative to the 'data' workspace. | Yes | sboms | +| sbomDir | Directory containing SBOM files. The task will search for CycloneDX JSON SBOMs recursively in this directory and upload them all to Atlas. The path is relative to the 'data' workspace. | No | None | | httpRetries | Max HTTP retry count. | Yes | 3 | | atlasSecretName | Name of the Secret containing SSO auth credentials for Atlas. | Yes | atlas-prod-sso-secret | | bombasticApiUrl | URL of the BOMbastic API host of Atlas. | Yes | https://sbom.atlas.devshift.net | | ssoTokenUrl | URL of the SSO token issuer. | Yes | https://auth.redhat.com/auth/realms/EmployeeIDP/protocol/openid-connect/token | -| skipUploadingSbom | If enabled, tasks are skipped and no SBOM is uploaded. User must explicitly enable SBOM uploading by skipUploadingSbom: false. | Yes | true | | supportedCycloneDxVersion | If the SBOM uses a higher CycloneDX version, `syft convert` in the task will convert all SBOMs to this CycloneDX version before uploading them to Atlas. If the SBOM is already in this version or lower, it will be uploaded as is. | Yes | 1.4 | | supportedSpdxVersion | If the SBOM uses a higher SPDX version, `syft convert` in the task will convert all SBOMs to this SPDX version before uploading them to Atlas. If the SBOM is already in this version or lower, it will be uploaded as is. | Yes | 2.3 | + +## Changes in 0.2.0 +Remove option to skip uploading SBOMs. Skipping will be handled via Tekton. +Rename productSBOMPath parameter to sbomDir. Use SBOM file names as Atlas IDs. diff --git a/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-cyclonedx.yaml b/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-cyclonedx.yaml index 4a0df2d4c..601505367 100644 --- a/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-cyclonedx.yaml +++ b/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-cyclonedx.yaml @@ -15,7 +15,7 @@ spec: workspace: tests-workspace taskSpec: params: - - name: productSBOMPath + - name: sbomDir default: "sboms" description: Directory containing generated SBOM files. type: string @@ -29,11 +29,11 @@ spec: set -eux # creating directory for generated SBOM-s - mkdir "$(workspaces.data.path)/$(params.productSBOMPath)" + mkdir "$(workspaces.data.path)/$(params.sbomDir)" # creating working directory mkdir "$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" workdir="$(workspaces.data.path)/workdir" # minimal CycloneDX SBOM - no need for conversion stays as 1.2 @@ -45,7 +45,7 @@ spec: "components": [] } EOF - sbom_id_1_2="sha256:$(sha256sum "$sbomsDir/cyclonedx_minimal_1_2.json" | cut -d ' ' -f 1)" + sbom_id_1_2="cyclonedx_minimal_1_2" echo "$sbom_id_1_2" > "$workdir/sbom_id_1_2" # minimal CycloneDX SBOM - no need for conversion @@ -57,7 +57,7 @@ spec: "components": [] } EOF - sbom_id_1_4="sha256:$(sha256sum "$sbomsDir/cyclonedx_minimal_1_4.json" | cut -d ' ' -f 1)" + sbom_id_1_4="cyclonedx_minimal_1_4" echo "$sbom_id_1_4" > "$workdir/sbom_id_1_4" # minimal CycloneDX SBOM - needs to be converted to supported 1.4 @@ -69,7 +69,7 @@ spec: "components": [] } EOF - sbom_id_1_6="sha256:$(sha256sum "$sbomsDir/cyclonedx_minimal_1_6.json" | cut -d ' ' -f 1)" + sbom_id_1_6="cyclonedx_minimal_1_6" echo "$sbom_id_1_6" > "$workdir/sbom_id_1_6" # Random JSON file that will be skipped @@ -88,8 +88,8 @@ spec: params: - name: atlasSecretName value: atlas-test-sso-secret - - name: skipUploadingSbom - value: "false" + - name: sbomDir + value: "sboms" workspaces: - name: data workspace: tests-workspace @@ -104,7 +104,7 @@ spec: - run-task taskSpec: params: - - name: productSBOMPath + - name: sbomDir default: "sboms" description: Directory containing generated SBOM files. type: string @@ -118,7 +118,7 @@ spec: set -eux workdir="$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" # Check count of curl calls if [ "$(wc -l < "$workdir/mock_curl.txt")" -ne 6 ]; then diff --git a/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-spdx.yaml b/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-spdx.yaml index b6119eecc..143790db0 100644 --- a/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-spdx.yaml +++ b/tasks/upload-sbom-to-atlas/tests/test-upload-sbom-to-atlas-spdx.yaml @@ -17,7 +17,7 @@ spec: workspaces: - name: data params: - - name: productSBOMPath + - name: sbomDir default: "sboms" description: Directory containing generated SBOM files. type: string @@ -29,11 +29,11 @@ spec: set -eux # creating directory for generated SBOM-s - mkdir "$(workspaces.data.path)/$(params.productSBOMPath)" + mkdir "$(workspaces.data.path)/$(params.sbomDir)" # creating working directory mkdir "$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" workdir="$(workspaces.data.path)/workdir" # minimal SPDX SBOM 2.2 - no need for conversion @@ -47,7 +47,7 @@ spec: "documentDescribes": [] } EOF - sbom_id_2_2="sha256:$(sha256sum "$sbomsDir/spdx_minimal_2_2.json" | cut -d ' ' -f 1)" + sbom_id_2_2="spdx_minimal_2_2" echo "$sbom_id_2_2" > "$workdir/sbom_id_2_2" # minimal SPDX SBOM 2.3 - currently highest existing version (supported by Atlas) @@ -61,7 +61,7 @@ spec: "documentDescribes": [] } EOF - sbom_id_2_3="sha256:$(sha256sum "$sbomsDir/spdx_minimal_2_3.json" | cut -d ' ' -f 1)" + sbom_id_2_3="spdx_minimal_2_3" echo "$sbom_id_2_3" > "$workdir/sbom_id_2_3" # Random JSON file that will be skipped @@ -80,8 +80,8 @@ spec: params: - name: atlasSecretName value: atlas-test-sso-secret - - name: skipUploadingSbom - value: "false" + - name: sbomDir + value: "sboms" workspaces: - name: data workspace: tests-workspace @@ -96,7 +96,7 @@ spec: - run-task taskSpec: params: - - name: productSBOMPath + - name: sbomDir default: "sboms" description: Directory containing generated SBOM files. type: string @@ -110,7 +110,7 @@ spec: set -eux workdir="$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" # Check count of calls if [ "$(wc -l < "$workdir/mock_curl.txt")" -ne 4 ]; then diff --git a/tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml b/tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml index 664ac093d..528bfe271 100644 --- a/tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml +++ b/tasks/upload-sbom-to-atlas/upload-sbom-to-atlas.yaml @@ -4,7 +4,7 @@ kind: Task metadata: name: upload-sbom-to-atlas labels: - app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/version: "0.2.0" annotations: tekton.dev/pipelines.minVersion: "0.12.1" tekton.dev/tags: release @@ -12,12 +12,11 @@ spec: description: |- Tekton task for uploading a SBOM file to Atlas. params: - - name: productSBOMPath - default: "sboms" + - name: sbomDir description: >- Directory containing SBOM files. The task will search for CycloneDX JSON SBOMs recursively in this directory and upload them all to Atlas. - The path is relative to the 'sboms' workspace. + The path is relative to the 'data' workspace. type: string - name: httpRetries default: "3" @@ -35,11 +34,6 @@ spec: default: "https://auth.redhat.com/auth/realms/EmployeeIDP/protocol/openid-connect/token" description: URL of the SSO token issuer type: string - - name: skipUploadingSbom - default: "true" - description: >- - If enabled, all steps of the task are skipped. - type: string - name: supportedCycloneDxVersion default: "1.4" description: >- @@ -72,14 +66,8 @@ spec: # creating working directory mkdir -p "$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" workdir="$(workspaces.data.path)/workdir" - - skipUploadingSbom=$(params.skipUploadingSbom) - if [[ "$skipUploadingSbom" == "true" ]]; then - echo "Skipping SBOM uploading - skipUploadingSbom == true" - exit 0 - fi version_lesser_equal() { local first @@ -121,16 +109,9 @@ spec: continue # Skip further processing for non-SBOM files fi - # The 'id' of each SBOM is checksum of the original content, before (possibly) - # downgrading the CycloneDX version. The conversion always updates some metadata - # (timestamp, UUID), changing the checksum. To avoid duplication, use the original - # checksum. - echo "Calculating SBOM ID for $file_relpath" - sbom_id="sha256:$(sha256sum "$filepath" | cut -d ' ' -f 1)" - - # Symlink the discovered SBOMS to $workdir/${sbom_id}.json so that subsequent steps + # Symlink the discovered SBOMS to $workdir so that subsequent steps # don't have to look for them again. - sbom_path="$workdir/$sbom_id.json" + sbom_path="$workdir/$(basename "$filepath")" ln -s "$(realpath "$filepath")" "$sbom_path" # Check if SBOM is CycloneDX or SPDX @@ -164,12 +145,7 @@ spec: set -o errexit -o pipefail -o nounset workdir="$(workspaces.data.path)/workdir" - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" - - skipUploadingSbom=$(params.skipUploadingSbom) - if [[ "$skipUploadingSbom" == "true" ]]; then - exit 0 - fi + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" # Return zero matches when a glob doesn't match rather than returning the glob itself shopt -s nullglob @@ -200,14 +176,9 @@ spec: #!/usr/bin/env bash set -o errexit -o pipefail -o nounset - sbomsDir="$(workspaces.data.path)/$(params.productSBOMPath)" + sbomsDir="$(workspaces.data.path)/$(params.sbomDir)" workdir="$(workspaces.data.path)/workdir" - skipUploadingSbom=$(params.skipUploadingSbom) - if [[ "$skipUploadingSbom" == "true" ]]; then - exit 0 - fi - shopt -s nullglob sboms_to_upload=("$workdir"/*.json) @@ -219,13 +190,8 @@ spec: httpRetries=$(params.httpRetries) curl_opts=(--silent --show-error --fail-with-body --retry "$httpRetries") - # Access the base64-encoded secret values from the mounted volume - sso_account_base64="$(cat /secrets/sso_account)" - sso_token_base64="$(cat /secrets/sso_token)" - - # Decode the base64-encoded values - sso_account=$(echo "$sso_account_base64" | base64 --decode) - sso_token=$(echo "$sso_token_base64" | base64 --decode) + sso_account="$(cat /secrets/sso_account)" + sso_token="$(cat /secrets/sso_token)" for sbom_path in "${sboms_to_upload[@]}"; do original_sbom_relpath="$(realpath "$sbom_path" --relative-base="$sbomsDir")" @@ -244,7 +210,6 @@ spec: # https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 access_token="$(jq -r .access_token <<< "$token_response")" - token_type="$(jq -r .token_type <<< "$token_response")" expires_in="$(jq -r ".expires_in // empty" <<< "$token_response")" retry_max_time=0 # no limit @@ -252,7 +217,6 @@ spec: retry_max_time="$expires_in" fi - # This sbom_id is the one created in the gather-sboms step - sha256:${checksum} sbom_id="$(basename -s .json "$sbom_path")" supported_version_of_sbom="${sbom_path}.supported_version" @@ -261,7 +225,7 @@ spec: curl -X PUT "${curl_opts[@]}" \ --retry-max-time "$retry_max_time" \ - -H "authorization: $token_type $access_token" \ + -H "authorization: Bearer $access_token" \ -H "transfer-encoding: chunked" \ -H "content-type: application/json" \ --data "@$supported_version_of_sbom" \