From a004f4956a1ee2913ce16f6e1627afc9cf2221d3 Mon Sep 17 00:00:00 2001 From: Yashvardhan Nanavati Date: Mon, 2 Dec 2024 22:51:44 -0800 Subject: [PATCH] feat: add fips-operator-check task Refers to CVP-4333. This task uses the check-payload tool to verify if an operator bundle image is FIPS compliant.It utilizes Tekton stepAction because the code will be reused for checking FBC fragments in the fbc-validation check. Signed-off-by: Yashvardhan Nanavati --- .../0.1/README.md | 13 ++ .../0.1/fips-operator-check-step-action.yaml | 114 +++++++++++++++ .../fips-operator-check-step-action/OWNERS | 6 + task/fips-operator-bundle-check/0.1/README.md | 25 ++++ .../0.1/fips-operator-bundle-check.yaml | 136 ++++++++++++++++++ task/fips-operator-bundle-check/OWNERS | 6 + 6 files changed, 300 insertions(+) create mode 100644 stepactions/fips-operator-check-step-action/0.1/README.md create mode 100644 stepactions/fips-operator-check-step-action/0.1/fips-operator-check-step-action.yaml create mode 100644 stepactions/fips-operator-check-step-action/OWNERS create mode 100644 task/fips-operator-bundle-check/0.1/README.md create mode 100644 task/fips-operator-bundle-check/0.1/fips-operator-bundle-check.yaml create mode 100644 task/fips-operator-bundle-check/OWNERS diff --git a/stepactions/fips-operator-check-step-action/0.1/README.md b/stepactions/fips-operator-check-step-action/0.1/README.md new file mode 100644 index 0000000000..ff7bf16f29 --- /dev/null +++ b/stepactions/fips-operator-check-step-action/0.1/README.md @@ -0,0 +1,13 @@ +## fips-operator-check-step-action + +This stepAction scans relatedImages of operator bundle image builds for FIPS compliance using the check-payload tool. The relatedImages are expected to be in a file located at `/tekton/home/unique_related_images.txt` + +## Results: + +| name | description | +|--------------------|--------------------------------------| +| TEST_OUTPUT | Tekton task test output. | + + +## Additional links: +https://github.com/openshift/check-payload \ No newline at end of file diff --git a/stepactions/fips-operator-check-step-action/0.1/fips-operator-check-step-action.yaml b/stepactions/fips-operator-check-step-action/0.1/fips-operator-check-step-action.yaml new file mode 100644 index 0000000000..f27c12b981 --- /dev/null +++ b/stepactions/fips-operator-check-step-action/0.1/fips-operator-check-step-action.yaml @@ -0,0 +1,114 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: StepAction +metadata: + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: "konflux" + name: fips-operator-check-step-action +spec: + description: >- + This stepAction scans relatedImages of operator bundle image builds for FIPS compliance using the check-payload tool. + results: + - name: TEST_OUTPUT + description: Tekton task test output. + image: quay.io/redhat-appstudio/konflux-test:v1.4.9@sha256:eee855e60b437d9a55a30e63f2eb7f95d9fd6d3b111c32cac8730c9b7a071394 + securityContext: + capabilities: + add: + - SETFCAP + script: | + #!/usr/bin/env bash + set -euo pipefail + # shellcheck source=/dev/null + . /utils.sh + + success_counter=0 + warnings_counter=0 + error_counter=0 + failure_counter=0 + + if [ ! -e "/tekton/home/unique_related_images.txt" ]; then + echo "No relatedImages to process" + exit 0 + fi + + related_images=$(cat /tekton/home/unique_related_images.txt) + echo "Related images are : ${related_images}" + + for related_image in ${related_images}; do + echo "Processing related image : ${related_image}" + + image_labels=$(skopeo inspect docker://"${related_image}" --config | jq -r '.config.Labels // {} | to_entries[] | "\(.key)=\(.value)"') + component_label=$(echo "${image_labels}" | grep 'com.redhat.component=' | cut -d= -f2 || true) + echo "Component label is ${component_label}" + + if [ -z "${component_label}" ]; then + echo "Error: Could not get com.redhat.component label for ${related_image}" + error_counter=$((error_counter + 1)) + continue + fi + + # Convert image to OCI format since umoci can only handle the OCI format + if ! skopeo copy --remove-signatures "docker://${related_image}" "oci:///tekton/home/${component_label}:latest"; then + echo "Error: Could not convert image ${related_image} to OCI format" + error_counter=$((error_counter + 1)) + continue + fi + + # Unpack OCI image + if ! umoci raw unpack --rootless \ + --image "/tekton/home/${component_label}:latest" \ + "/tekton/home/unpacked-${component_label}"; then + echo "Error: Could not unpack OCI image ${related_image}" + error_counter=$((error_counter + 1)) + continue + fi + + echo "Now RUNNING SCAN ON THE IMAGE ${related_image}" + + # Run check-payload on the unpacked image + # The check-payload command fails with exit 1 when the scan for an image is unsuccessful + # or when the image is not FIPS compliant. Hence, count those as failures and not errors + if ! check-payload scan local \ + --path="/tekton/home/unpacked-${component_label}" \ + --components="${component_label}" \ + --output-format=csv \ + --output-file="/tekton/home/report-${component_label}.csv"; then + echo "check-payload scan failed for ${related_image}" + failure_counter=$((failure_counter + 1)) + continue + fi + + if [ -f "/tekton/home/report-${component_label}.csv" ]; then + if grep -q -- "---- Successful run" "/tekton/home/report-${component_label}.csv"; then + echo "check-payload scan was successful for ${related_image}" + success_counter=$((success_counter + 1)) + elif grep -q -- "---- Successful run with warnings" "/tekton/home/report-${component_label}.csv"; then + echo "check-payload scan was successful with warnings for ${related_image}" + warnings_counter=$((warnings_counter + 1)) + fi + fi + + done + + note="Task $(context.task.name) failed: Some images could not be scanned. For details, check Tekton task log." + ERROR_OUTPUT=$(make_result_json -r ERROR -t "$note") + + note="Task $(context.task.name) completed: Check result for task result." + if [[ "$error_counter" == 0 ]]; + then + if [[ "${failure_counter}" -gt 0 ]]; then + RES="FAILURE" + elif [[ "${warnings_counter}" -gt 0 ]]; then + RES="WARNING" + else + RES="SUCCESS" + fi + TEST_OUTPUT=$(make_result_json \ + -r "${RES}" \ + -s "${success_counter}" -f "${failure_counter}" -w "${warnings_counter}" -t "$note") + fi + echo "${TEST_OUTPUT:-${ERROR_OUTPUT}}" | tee "$(step.results.TEST_OUTPUT.path)" diff --git a/stepactions/fips-operator-check-step-action/OWNERS b/stepactions/fips-operator-check-step-action/OWNERS new file mode 100644 index 0000000000..437134a9da --- /dev/null +++ b/stepactions/fips-operator-check-step-action/OWNERS @@ -0,0 +1,6 @@ +approvers: + - integration-team + - yashvardhannanavati +reviewers: + - integration-team + - yashvardhannanavati diff --git a/task/fips-operator-bundle-check/0.1/README.md b/task/fips-operator-bundle-check/0.1/README.md new file mode 100644 index 0000000000..ba9cab082b --- /dev/null +++ b/task/fips-operator-bundle-check/0.1/README.md @@ -0,0 +1,25 @@ +# fips-operator-bundle-check task + +## Description: +The fips-operator-bundle-check task uses the check-payload tool to verify if an operator bundle image is FIPS compliant. +It only scans operator bundle images which either claim to be FIPS compliant by setting the `features.operators.openshift.io/fips-compliant` +label to `"true"` on the bundle image or require one of `OpenShift Kubernetes Engine, OpenShift Platform Plus or OpenShift Container Platform` +subscriptions to run the operator on an Openshift cluster. + +## Params: + +| name | description | default | +|--------------------------|------------------------------------------------------------------------|---------------| +| image-digest | Image digest to scan. | None | +| image-url | Image URL. | None | + +## Results: + +| name | description | +|--------------------|------------------------------| +| TEST_OUTPUT | Tekton task test output. | +| IMAGES_PROCESSED | Images processed in the task.| + + +## Additional links: +https://github.com/openshift/check-payload \ No newline at end of file diff --git a/task/fips-operator-bundle-check/0.1/fips-operator-bundle-check.yaml b/task/fips-operator-bundle-check/0.1/fips-operator-bundle-check.yaml new file mode 100644 index 0000000000..eea89c11af --- /dev/null +++ b/task/fips-operator-bundle-check/0.1/fips-operator-bundle-check.yaml @@ -0,0 +1,136 @@ +--- +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: fips-operator-bundle-check +spec: + description: >- + Checks operator bundle image builds for FIPS compliance using the check-payload tool. + params: + - name: image-digest + description: Image digest to scan. + - name: image-url + description: Image URL. + results: + - name: TEST_OUTPUT + description: Tekton task test output. + value: $(steps.fips-operator-check-step-action.results.TEST_OUTPUT) + - name: IMAGES_PROCESSED + description: Images processed in the task. + steps: + - name: get-unique-related-images + image: quay.io/redhat-appstudio/konflux-test:v1.4.9@sha256:eee855e60b437d9a55a30e63f2eb7f95d9fd6d3b111c32cac8730c9b7a071394 + computeResources: + limits: + memory: 512Mi + cpu: 200m + requests: + memory: 256Mi + cpu: 100m + env: + - name: IMAGE_URL + value: $(params.image-url) + - name: IMAGE_DIGEST + value: $(params.image-digest) + securityContext: + capabilities: + add: + - SETFCAP + script: | + #!/usr/bin/env bash + set -euo pipefail + # shellcheck source=/dev/null + . /utils.sh + + image_without_tag=$(echo -n "${IMAGE_URL}" | sed "s/\(.*\):.*/\1/") + # strip new-line escape symbol from parameter and save it to variable + image_and_digest="${image_without_tag}@${IMAGE_DIGEST}" + + image_and_digest_labels=$(skopeo inspect docker://"${image_and_digest}" --config | jq -r '.config.Labels // {} | to_entries[] | "\(.key)=\(.value)"') + if ! echo "${image_and_digest_labels}" | grep -q 'operators.operatorframework.io.bundle.mediatype.v1='; then + echo "The image $image_and_digest is not an operator bundle. Skipping FIPS static check..." + exit 0 + fi + + # Run the FIPS check only if the bundle is part of the Openshift Subscription or has the fips label set + image_and_digest_render_out=$(opm render "$image_and_digest") + subscription_label=$(echo "${image_and_digest_render_out}" | jq -r '.properties[] | select(.value.annotations["operators.openshift.io/valid-subscription"] != null) | (.value.annotations["operators.openshift.io/valid-subscription"] | fromjson)[]') + fips_label=$(echo "${image_and_digest_labels}" | grep 'features.operators.openshift.io/fips-compliant=' | cut -d= -f2 || true) + + if ! echo "${subscription_label}" | grep -e "OpenShift Kubernetes Engine" -e "OpenShift Container Platform" -e "OpenShift Platform Plus"; then + echo "OpenShift Kubernetes Engine, OpenShift Platform Plus or OpenShift Container Platform are not present in operators.openshift.io/valid-subscription." + echo "Subscription labels are : $subscription_label" + if [ -z "${fips_label}" ] || [ "${fips_label}" != "true" ]; then + echo "The label features.operators.openshift.io/fips-compliant is also not set to true. Skipping the FIPS static check..." + exit 0 + else + echo "The label features.operators.openshift.io/fips-compliant is set to true. Running the FIPS static check..." + fi + else + echo "OpenShift Kubernetes Engine, OpenShift Platform Plus or OpenShift Container Platform are present in operators.openshift.io/valid-subscription. Running the FIPS static check..." + fi + + unique_related_images=() + digests_processed=() + images_processed_template='{"image": {"pullspec": "'"$IMAGE_URL"'", "digests": [%s]}}' + + echo "Inspecting raw image manifest $image_and_digest." + # Get the arch and image manifests by inspecting the image. This is mainly for identifying image indexes + image_manifests=$(get_image_manifests -i "${image_and_digest}") + echo "Image manifests are $image_manifests" + + declare -A seen_related_images + # Extract relatedImages from the bundle image + while read -r _ arch_sha; do + digests_processed+=("\"$arch_sha\"") + bundle_render_out=$(opm render "$image_without_tag@$arch_sha") + manifest_related_images=$(echo "${bundle_render_out}" | jq -r '.relatedImages[]?.image') + if [ -n "$manifest_related_images" ]; then + for img in $manifest_related_images; do + if [ -z "${seen_related_images["$img"]:-}" ]; then + unique_related_images+=("$img") + seen_related_images["$img"]=1 + fi + done + fi + done < <(echo "$image_manifests" | jq -r 'to_entries[] | "\(.key) \(.value)"') + + echo "Unique related images: ${unique_related_images[*]}" + echo "${unique_related_images[*]}" > /tekton/home/unique_related_images.txt + + # If the image is an Image Index, also add the Image Index digest to the list. + if [[ "${digests_processed[*]}" != *"$IMAGE_DIGEST"* ]]; then + digests_processed+=("\"$IMAGE_DIGEST\"") + fi + digests_processed_string=$(IFS=,; echo "${digests_processed[*]}") + + echo "${images_processed_template/\[%s]/[$digests_processed_string]}" > /tekton/home/images_processed.txt + + - name: fips-operator-check-step-action + computeResources: + limits: + memory: 512Mi + cpu: 200m + requests: + memory: 256Mi + cpu: 100m + ref: + name: fips-operator-check-step-action + + - name: parse-images-processed-result + image: quay.io/redhat-appstudio/konflux-test:v1.4.9@sha256:eee855e60b437d9a55a30e63f2eb7f95d9fd6d3b111c32cac8730c9b7a071394 + script: | + #!/usr/bin/env bash + set -euo pipefail + + if [ -e "/tekton/home/images_processed.txt" ]; then + cat /tekton/home/images_processed.txt | tee $(results.IMAGES_PROCESSED.path) + else + echo "Task was skipped. Exiting" + exit 0 + fi diff --git a/task/fips-operator-bundle-check/OWNERS b/task/fips-operator-bundle-check/OWNERS new file mode 100644 index 0000000000..0beda903ed --- /dev/null +++ b/task/fips-operator-bundle-check/OWNERS @@ -0,0 +1,6 @@ +approvers: + - integration-team + - yashvardhannanavati +reviewers: + - integration-team + - yashvardhannanavati \ No newline at end of file