diff --git a/Dockerfile b/Dockerfile index d4e977a26..b7e9d94df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,10 @@ COPY --from=build "/build/dist/ec_${TARGETOS}_${TARGETARCH}" /usr/local/bin/ec COPY --from=build "/build/dist/kubectl_${TARGETOS}_${TARGETARCH}" /usr/local/bin/kubectl # Copy reduce-snapshot script needed for single component mode -COPY hack/reduce-snapshot.sh /usr/local/bin +COPY hack/reduce-snapshot.sh /usr/local/bin/ + +# Copy pin-konflux-policy-bundle script needed for policy bundle digest pinning +COPY hack/pin-konflux-policy-bundle.sh /usr/local/bin/ # OpenShift preflight check requires a license COPY --from=build /build/LICENSE /licenses/LICENSE diff --git a/Dockerfile.dist b/Dockerfile.dist index 94992f6d5..47899a09e 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -76,7 +76,10 @@ COPY --from=build "/build/dist/ec_${TARGETOS}_${TARGETARCH}" /usr/local/bin/ec COPY --from=build "/build/dist/kubectl_${TARGETOS}_${TARGETARCH}" /usr/local/bin/kubectl # Copy reduce-snapshot script needed for single component mode -COPY hack/reduce-snapshot.sh /usr/local/bin +COPY hack/reduce-snapshot.sh /usr/local/bin/ + +# Copy pin-konflux-policy-bundle script needed for policy bundle digest pinning +COPY hack/pin-konflux-policy-bundle.sh /usr/local/bin/ # OpenShift preflight check requires a license COPY --from=build /build/LICENSE /licenses/LICENSE diff --git a/acceptance/kubernetes/kind/acceptance.Dockerfile b/acceptance/kubernetes/kind/acceptance.Dockerfile index a3a987ed8..df0e87262 100644 --- a/acceptance/kubernetes/kind/acceptance.Dockerfile +++ b/acceptance/kubernetes/kind/acceptance.Dockerfile @@ -27,6 +27,7 @@ ARG KUBECTL_BINARY COPY ${EC_BINARY} /usr/local/bin/ec COPY ${KUBECTL_BINARY} /usr/local/bin/kubectl COPY hack/reduce-snapshot.sh /usr/local/bin/ +COPY hack/pin-konflux-policy-bundle.sh /usr/local/bin/ RUN ln -s /usr/local/bin/ec /usr/local/bin/conforma diff --git a/docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc b/docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc index 7d1e3e6d4..8a3846ae2 100644 --- a/docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc +++ b/docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc @@ -57,6 +57,9 @@ paths can be provided by using the `:` separator. + *Default*: `now` *EXTRA_RULE_DATA* (`string`):: Merge additional Rego variables into the policy data. Use syntax "key=value,key2=value2..." +*POLICY_BUNDLE_DIGEST* (`string`):: Optional OCI digest to pin the release policy bundle. When provided, the policy configuration is resolved and the reference oci::quay.io/conforma/release-policy:konflux is replaced with oci::quay.io/conforma/release-policy@. Accepts a full digest (sha256:abc123...) or just the hex hash (abc123...). ++ +*Default*: `sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f` *WORKERS* (`string`):: Number of parallel workers to use for policy evaluation. + diff --git a/docs/modules/ROOT/pages/verify-enterprise-contract.adoc b/docs/modules/ROOT/pages/verify-enterprise-contract.adoc index 9f4a46e0f..757e608eb 100644 --- a/docs/modules/ROOT/pages/verify-enterprise-contract.adoc +++ b/docs/modules/ROOT/pages/verify-enterprise-contract.adoc @@ -68,6 +68,9 @@ paths can be provided by using the `:` separator. + *Default*: `now` *EXTRA_RULE_DATA* (`string`):: Merge additional Rego variables into the policy data. Use syntax "key=value,key2=value2..." +*POLICY_BUNDLE_DIGEST* (`string`):: Optional OCI digest to pin the release policy bundle. When provided, the policy configuration is resolved and the reference oci::quay.io/conforma/release-policy:konflux is replaced with oci::quay.io/conforma/release-policy@. Accepts a full digest (sha256:abc123...) or just the hex hash (abc123...). ++ +*Default*: `sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f` *WORKERS* (`string`):: Number of parallel workers to use for policy evaluation. + *Default*: `1` diff --git a/features/__snapshots__/task_validate_image.snap b/features/__snapshots__/task_validate_image.snap index 294e4927f..83f120538 100755 --- a/features/__snapshots__/task_validate_image.snap +++ b/features/__snapshots__/task_validate_image.snap @@ -33,6 +33,12 @@ Error: Get "http://tuf.invalid/root.json": dial tcp: lookup tuf.invalid on 10.96 } --- +[Golden container image:pin-policy-bundle - 1] +Applying policy bundle digest override: sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f +'oci::quay.io/conforma/release-policy:konflux' not found in policy configuration, nothing to do. + +--- + [Golden container image:show-config - 1] { "policy": { @@ -734,3 +740,32 @@ results.tufUrl: "TEST_OUTPUT": "{\"timestamp\":\"${TIMESTAMP}\",\"namespace\":\"\",\"successes\":0,\"failures\":2,\"warnings\":0,\"result\":\"FAILURE\"}\n" } --- + +[Pin policy bundle digest:pin-policy-bundle - 1] +Applying policy bundle digest override: sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f +Replaced: oci::quay.io/conforma/release-policy:konflux + with: oci::quay.io/conforma/release-policy@sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f +Modified policy written to: /tekton/home/policy-with-pinned-bundle.yaml + +--- +[Pin policy bundle digest:show-config - 1] +{ + "policy": { + "sources": [ + { + "policy": [ + "oci::quay.io/conforma/release-policy@sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f" + ], + "config": { + "include": [ + "slsa_provenance_available" + ] + } + } + ], + "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhr8Zj4dZW67zucg8fDr11M4lmRp\nzN6SIcIjkvH39siYg1DkCoa2h2xMUZ10ecbM3/ECqvBV55YwQ2rcIEa7XQ==\n-----END PUBLIC KEY-----" + }, + "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhr8Zj4dZW67zucg8fDr11M4lmRp\nzN6SIcIjkvH39siYg1DkCoa2h2xMUZ10ecbM3/ECqvBV55YwQ2rcIEa7XQ==\n-----END PUBLIC KEY-----\n", + "effective-time": "${TIMESTAMP}" +} +--- diff --git a/features/task_validate_image.feature b/features/task_validate_image.feature index 631506535..77ce51bec 100644 --- a/features/task_validate_image.feature +++ b/features/task_validate_image.feature @@ -34,10 +34,43 @@ Feature: Verify Enterprise Contract Tekton Tasks | STRICT | true | | IGNORE_REKOR | true | Then the task should succeed + And the task logs for step "pin-policy-bundle" should match the snapshot And the task logs for step "report" should match the snapshot And the task results should match the snapshot And the task logs for step "show-config" should match the snapshot + Scenario: Pin policy bundle digest + Given a working namespace + Given a cluster policy with content: + ``` + { + "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhr8Zj4dZW67zucg8fDr11M4lmRp\nzN6SIcIjkvH39siYg1DkCoa2h2xMUZ10ecbM3/ECqvBV55YwQ2rcIEa7XQ==\n-----END PUBLIC KEY-----", + "sources": [ + { + "policy": [ + "oci::quay.io/conforma/release-policy:konflux" + ], + "config": { + "include": [ + "slsa_provenance_available" + ] + } + } + ] + } + ``` + When version 0.1 of the task named "verify-enterprise-contract" is run with parameters: + | IMAGES | {"components": [{"containerImage": "quay.io/hacbs-contract-demo/golden-container@sha256:e76a4ae9dd8a52a0d191fd34ca133af5b4f2609536d32200a4a40a09fdc93a0d"}]} | + | POLICY_CONFIGURATION | ${NAMESPACE}/${POLICY_NAME} | + | POLICY_BUNDLE_DIGEST | sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f | + | STRICT | false | + | IGNORE_REKOR | true | + Then the task should succeed + And the task logs for step "pin-policy-bundle" should match the snapshot + And the task logs for step "show-config" should match the snapshot + # The show-config step is enough to confirm the ECP was modified. No need + # to look at the other output + Scenario: Extra rule data provided to task Given a working namespace Given a cluster policy with content: diff --git a/hack/pin-konflux-policy-bundle.sh b/hack/pin-konflux-policy-bundle.sh new file mode 100755 index 000000000..09921fd32 --- /dev/null +++ b/hack/pin-konflux-policy-bundle.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# Copyright The Conforma Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# +# This script resolves a policy configuration and replaces the release +# policy OCI tag reference with a digest-pinned reference. It requires +# the following environment variables: +# +# - POLICY_CONFIGURATION: Policy reference (k8s name, inline JSON, or file path). +# - POLICY_BUNDLE_DIGEST: OCI digest to pin to (e.g. "sha256:abc123..." or "abc123..."). +# If empty, the script is a no-op. +# - HOME: Home directory (defaults to /tekton/home in Tekton tasks). + +set -o errexit +set -o nounset +set -o pipefail + +if [[ -z "${POLICY_BUNDLE_DIGEST}" ]]; then + echo "POLICY_BUNDLE_DIGEST is empty, skipping policy bundle digest override." + exit 0 +fi + +# Normalize digest: prepend sha256: if not present +if [[ "${POLICY_BUNDLE_DIGEST}" != sha256:* ]]; then + POLICY_BUNDLE_DIGEST="sha256:${POLICY_BUNDLE_DIGEST}" +fi + +echo "Applying policy bundle digest override: ${POLICY_BUNDLE_DIGEST}" + +WORKING_POLICY="$(mktemp /tmp/policy.XXXXXX)" + +if [[ "${POLICY_CONFIGURATION}" == "{"* ]]; then + # Inline JSON + printf "%s" "${POLICY_CONFIGURATION}" > "$WORKING_POLICY" + +elif [[ -f "${POLICY_CONFIGURATION}" ]]; then + # File path + cp "${POLICY_CONFIGURATION}" "$WORKING_POLICY" + +else + # Treat as k8s EnterpriseContractPolicy reference (namespace/name or name) + if [[ "${POLICY_CONFIGURATION}" == *"/"* ]]; then + NAMESPACE="${POLICY_CONFIGURATION%%/*}" + NAME="${POLICY_CONFIGURATION##*/}" + kubectl get enterprisecontractpolicy/"${NAME}" -n "${NAMESPACE}" -o json \ + | jq '.spec' > "$WORKING_POLICY" || \ + { echo "Failed to get EnterpriseContractPolicy: ${POLICY_CONFIGURATION}"; exit 1; } + else + kubectl get enterprisecontractpolicy/"${POLICY_CONFIGURATION}" -o json \ + | jq '.spec' > "$WORKING_POLICY" || \ + { echo "Failed to get EnterpriseContractPolicy: ${POLICY_CONFIGURATION}"; exit 1; } + fi +fi + +ORIGINAL="oci::quay.io/conforma/release-policy:konflux" +REPLACEMENT="oci::quay.io/conforma/release-policy@${POLICY_BUNDLE_DIGEST}" + +if ! grep -q "${ORIGINAL}" "$WORKING_POLICY"; then + echo "'${ORIGINAL}' not found in policy configuration, nothing to do." + exit 0 +fi + +sed -i "s|${ORIGINAL}|${REPLACEMENT}|g" "$WORKING_POLICY" +echo "Replaced: ${ORIGINAL}" +echo " with: ${REPLACEMENT}" + +POLICY_PATH="${HOME}/policy-with-pinned-bundle.yaml" +cp "$WORKING_POLICY" "${POLICY_PATH}" +echo "Modified policy written to: ${POLICY_PATH}" diff --git a/hack/update-policy-digest-in-tasks.sh b/hack/update-policy-digest-in-tasks.sh new file mode 100755 index 000000000..13eef1d3b --- /dev/null +++ b/hack/update-policy-digest-in-tasks.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Copyright The Conforma Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Update the POLICY_BUNDLE_DIGEST default value in tekton task definitions. + +set -o errexit +set -o nounset +set -o pipefail + +IMAGE="${IMAGE:-"quay.io/conforma/release-policy:konflux"}" + +FILES=( + tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml + tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml + docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc + docs/modules/ROOT/pages/verify-enterprise-contract.adoc + features/__snapshots__/task_validate_image.snap + features/task_validate_image.feature +) + +MANIFEST=$(skopeo inspect --raw "docker://${IMAGE}") +HASH=$(echo -n "${MANIFEST}" | sha256sum | awk '{print $1}') +NEW_DIGEST="sha256:${HASH}" + +OLD_DIGEST=$(sed -n '/- name: POLICY_BUNDLE_DIGEST$/,/- name: /{s/.*default: "\(sha256:[a-f0-9]*\)".*/\1/p;}' "${FILES[0]}") + +echo "Old digest: ${OLD_DIGEST}" +echo "New digest: ${NEW_DIGEST}" + +if [[ "${OLD_DIGEST}" == "${NEW_DIGEST}" ]]; then + echo "Already up to date." + exit 0 +fi + +for f in "${FILES[@]}"; do + if [[ ! -f "${f}" ]]; then + echo "Warning: ${f} not found, skipping" >&2 + continue + fi + sed -i "s|${OLD_DIGEST}|${NEW_DIGEST}|g" "${f}" + echo "Updated ${f}" +done diff --git a/tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml b/tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml index 30a6ce574..9849e0a14 100644 --- a/tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml +++ b/tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml @@ -157,6 +157,21 @@ spec: description: Merge additional Rego variables into the policy data. Use syntax "key=value,key2=value2..." default: "" + - name: POLICY_BUNDLE_DIGEST + type: string + description: >- + Optional OCI digest to pin the release policy bundle. When provided, + the policy configuration is resolved and the reference + oci::quay.io/conforma/release-policy:konflux is replaced with + oci::quay.io/conforma/release-policy@. Accepts a full digest + (sha256:abc123...) or just the hex hash (abc123...). + # For Konflux stability we want to try pinning the policy bundle rather + # than use the floating oci::quay.io/conforma/release-policy:konflux tag. + # Instead of needing to bump this in hundreds of separate ECPs, we'll do + # it here instead. If you don't want this behavior then provide an empty + # string value for this param. + default: "sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f" + - name: WORKERS type: string description: > @@ -326,6 +341,15 @@ spec: onError: continue # progress even if the step fails so we can see the debug logs command: [reduce-snapshot.sh] + - name: pin-policy-bundle + env: + - name: POLICY_CONFIGURATION + value: $(params.POLICY_CONFIGURATION) + - name: POLICY_BUNDLE_DIGEST + value: $(params.POLICY_BUNDLE_DIGEST) + image: quay.io/conforma/cli:latest + command: [pin-konflux-policy-bundle.sh] + - name: validate image: quay.io/conforma/cli:latest onError: continue # progress even if the step fails so we can see the debug logs @@ -337,6 +361,11 @@ spec: export SSL_CERT_FILE="/mnt/trusted-ca/ca-bundle.crt" fi + # Use policy override file if pin-policy-bundle produced one + if [[ -f "${HOMEDIR}/policy-with-pinned-bundle.yaml" ]]; then + POLICY_CONFIGURATION="${HOMEDIR}/policy-with-pinned-bundle.yaml" + fi + cmd_args=( validate image diff --git a/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml b/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml index cb4a05b76..f536fb3ad 100644 --- a/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml +++ b/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml @@ -164,6 +164,21 @@ spec: description: Merge additional Rego variables into the policy data. Use syntax "key=value,key2=value2..." default: "" + - name: POLICY_BUNDLE_DIGEST + type: string + description: >- + Optional OCI digest to pin the release policy bundle. When provided, + the policy configuration is resolved and the reference + oci::quay.io/conforma/release-policy:konflux is replaced with + oci::quay.io/conforma/release-policy@. Accepts a full digest + (sha256:abc123...) or just the hex hash (abc123...). + # For Konflux stability we want to try pinning the policy bundle rather + # than use the floating oci::quay.io/conforma/release-policy:konflux tag. + # Instead of needing to bump this in hundreds of separate ECPs, we'll do + # it here instead. If you don't want this behavior then provide an empty + # string value for this param. + default: "sha256:1b296a925b4021f4b4959ea289596925a8735540e554f3ba7754a651731a216f" + - name: WORKERS type: string description: Number of parallel workers to use for policy evaluation. @@ -272,6 +287,21 @@ spec: onError: continue # progress even if the step fails so we can see the debug logs command: [reduce-snapshot.sh] + - name: pin-policy-bundle + computeResources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 256Mi + env: + - name: POLICY_CONFIGURATION + value: $(params.POLICY_CONFIGURATION) + - name: POLICY_BUNDLE_DIGEST + value: $(params.POLICY_BUNDLE_DIGEST) + image: quay.io/conforma/cli:latest + command: [pin-konflux-policy-bundle.sh] + - name: validate image: quay.io/conforma/cli:latest onError: continue # progress even if the step fails so we can see the debug logs @@ -279,6 +309,11 @@ spec: #!/bin/bash set -euo pipefail + # Use policy override file if pin-policy-bundle produced one + if [[ -f "${HOMEDIR}/policy-with-pinned-bundle.yaml" ]]; then + POLICY_CONFIGURATION="${HOMEDIR}/policy-with-pinned-bundle.yaml" + fi + cmd_args=( validate image