diff --git a/tasks/managed/direct-sign-index-image/README.md b/tasks/managed/direct-sign-index-image/README.md new file mode 100644 index 0000000000..554214d473 --- /dev/null +++ b/tasks/managed/direct-sign-index-image/README.md @@ -0,0 +1,33 @@ +# direct-sign-index-image + +Creates InternalRequests to sign FBC index images via the container-signing pipeline + +## Parameters + +| Name | Description | Optional | Default value | +|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------------------| +| dataPath | Path to the JSON string of the merged data to use in the data workspace | No | - | +| requester | Name of the user that requested the signing, for auditing purposes | No | - | +| requestTimeout | InternalRequest timeout | Yes | 1800 | +| pipelineRunUid | The uid of the current pipelineRun. Used as a label value when creating internal requests | No | - | +| fbcResultsPath | Path to the JSON file in the data workspace containing fbc results | No | - | +| concurrentLimit | The maximum number of concurrent signing requests | Yes | 8 | +| ociStorage | The OCI repository where the Trusted Artifacts are stored | Yes | empty | +| ociArtifactExpiresAfter | Expiration date for the trusted artifacts created in the OCI repository. An empty string means the artifacts do not expire | Yes | 1d | +| trustedArtifactsDebug | Flag to enable debug logging in trusted artifacts. Set to a non-empty string to enable | Yes | "" | +| orasOptions | oras options to pass to Trusted Artifacts calls | Yes | "" | +| sourceDataArtifact | Location of trusted artifacts to be used to populate data directory | Yes | "" | +| dataDir | The location where data will be stored | Yes | /var/workdir/release | +| taskGitUrl | The url to the git repo where the release-service-catalog tasks and stepactions to be used are stored | No | - | +| taskGitRevision | The revision in the taskGitUrl repo to be used | No | - | +| pyxisServer | The server type to use. Options are 'production','production-internal','stage-internal' and 'stage' | Yes | production | +| pyxisSecret | The kubernetes secret to use to authenticate to Pyxis. It needs to contain two keys: key and cert | No | - | +| batchLimit | Maximum size in bytes of each base64-encoded signing_requests batch sent via InternalRequest | Yes | 15000 | +| caTrustConfigMapName | The name of the ConfigMap to read CA bundle data from | Yes | trusted-ca | +| caTrustConfigMapKey | The name of the key in the ConfigMap that contains the CA bundle data | Yes | ca-bundle.crt | +| failOnSignatureLookupError | Fail the task when any Pyxis find_signatures lookup fails; when set to "false", log a warning and submit every planned image row for signing without skipping already-signed references | Yes | true | +| signingRepo | Git repository URL containing the signing tasks | Yes | https://gitlab.cee.redhat.com/signing/signing.git | +| signingRevision | Git revision (branch, tag, or commit) in the signing repository | Yes | main | +| signPipeline | Name of the internal pipeline to use for container signing | Yes | container-signing | +| signPipelineServiceAccount | Service account to use for the signing pipeline | Yes | signing-pipeline-sa | +| pipelineImage | The image to use for the signing pipeline | Yes | quay.io/konflux-ci/signing:latest | diff --git a/tasks/managed/direct-sign-index-image/direct-sign-index-image.yaml b/tasks/managed/direct-sign-index-image/direct-sign-index-image.yaml new file mode 100644 index 0000000000..53631a7b5c --- /dev/null +++ b/tasks/managed/direct-sign-index-image/direct-sign-index-image.yaml @@ -0,0 +1,241 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: direct-sign-index-image + annotations: + tekton.dev/pipelines.minVersion: "0.12.1" + tekton.dev/tags: release +spec: + description: |- + Creates InternalRequests to sign FBC index images via the container-signing pipeline + params: + - name: dataPath + description: Path to the JSON string of the merged data to use in the data workspace + type: string + - name: requester + type: string + description: Name of the user that requested the signing, for auditing purposes + - name: requestTimeout + type: string + default: "1800" + description: InternalRequest timeout + - name: pipelineRunUid + type: string + description: The uid of the current pipelineRun. Used as a label value when creating internal requests + - name: fbcResultsPath + type: string + description: Path to the JSON file in the data workspace containing fbc results + - name: concurrentLimit + type: string + description: The maximum number of concurrent signing requests + default: 8 + - name: ociStorage + description: The OCI repository where the Trusted Artifacts are stored + type: string + default: "empty" + - name: ociArtifactExpiresAfter + description: Expiration date for the trusted artifacts created in the + OCI repository. An empty string means the artifacts do not expire + type: string + default: "1d" + - name: trustedArtifactsDebug + description: Flag to enable debug logging in trusted artifacts. Set to a non-empty string to enable + type: string + default: "" + - name: orasOptions + description: oras options to pass to Trusted Artifacts calls + type: string + default: "" + - name: sourceDataArtifact + type: string + description: Location of trusted artifacts to be used to populate data directory + default: "" + - name: dataDir + description: The location where data will be stored + type: string + default: /var/workdir/release + - name: taskGitUrl + type: string + description: The url to the git repo where the release-service-catalog tasks and stepactions to be used are stored + - name: taskGitRevision + type: string + description: The revision in the taskGitUrl repo to be used + - name: pyxisServer + type: string + description: >- + The server type to use. Options are 'production','production-internal','stage-internal' and 'stage' + default: production + - name: pyxisSecret + type: string + description: | + The kubernetes secret to use to authenticate to Pyxis. It needs to contain two keys: key and cert + - name: batchLimit + type: string + description: | + Maximum size in bytes of each base64-encoded signing_requests batch sent via InternalRequest + default: 15000 + - 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 + - name: failOnSignatureLookupError + type: string + default: "true" + description: | + Fail the task when any Pyxis find_signatures lookup fails; when set to "false", log a warning + and submit every planned image row for signing without skipping already-signed references + - name: signingRepo + type: string + description: Git repository URL containing the signing tasks + default: "https://gitlab.cee.redhat.com/signing/signing.git" + - name: signingRevision + type: string + description: Git revision (branch, tag, or commit) in the signing repository + default: "main" + - name: signPipeline + type: string + description: Name of the internal pipeline to use for container signing + default: "container-signing" + - name: signPipelineServiceAccount + type: string + description: Service account to use for the signing pipeline + default: "signing-pipeline-sa" + - name: pipelineImage + type: string + description: The image to use for the signing pipeline + default: quay.io/konflux-ci/signing:latest + results: + - name: sourceDataArtifact + type: string + description: Produced trusted data artifact + volumes: + - name: workdir + emptyDir: {} + - name: pyxis-secret-vol + secret: + secretName: $(params.pyxisSecret) + defaultMode: 0444 + - name: trusted-ca + configMap: + name: $(params.caTrustConfigMapName) + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + optional: true + + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + securityContext: + runAsUser: 1001 + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + - name: "ORAS_OPTIONS" + value: "$(params.orasOptions)" + - name: "DEBUG" + value: "$(params.trustedArtifactsDebug)" + steps: + - name: use-trusted-artifact + computeResources: + limits: + memory: 64Mi + requests: + memory: 64Mi + cpu: 30m + ref: + resolver: "git" + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: stepactions/use-trusted-artifact/use-trusted-artifact.yaml + params: + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(params.sourceDataArtifact) + - name: direct-sign-index-image + image: quay.io/konflux-ci/release-service-utils@sha256:a2c2fbf89bced4c0421f6a2237f467a94fa38c0b722e4c32368c4d291dbc7323 + computeResources: + limits: + memory: 1Gi + requests: + memory: 1Gi + cpu: 250m + volumeMounts: + - name: pyxis-secret-vol + mountPath: "/etc/secrets" + env: + - name: PYXIS_CERT_PATH + value: /etc/secrets/cert + - name: PYXIS_KEY_PATH + value: /etc/secrets/key + command: + - direct_sign_index_image.py + args: + - "--pyxis-server" + - "$(params.pyxisServer)" + - "--fbc-results" + - "$(params.dataDir)/$(params.fbcResultsPath)" + - "--data-file" + - "$(params.dataDir)/$(params.dataPath)" + - "--batch-max-size" + - "$(params.batchLimit)" + - "--fail-on-lookup-error" + - "$(params.failOnSignatureLookupError)" + - "--requester" + - "$(params.requester)" + - "--pipeline" + - "$(params.signPipeline)" + - "--pipeline-image" + - "$(params.pipelineImage)" + - "--service-account" + - "$(params.signPipelineServiceAccount)" + - "--request-timeout" + - "$(params.requestTimeout)" + - "--task-id" + - "$(context.taskRun.uid)" + - "--pipelinerun-uid" + - "$(params.pipelineRunUid)" + - "--signing-repo" + - "$(params.signingRepo)" + - "--signing-revision" + - "$(params.signingRevision)" + - "--concurrent-limit" + - "$(params.concurrentLimit)" + + - name: create-trusted-artifact + computeResources: + limits: + memory: 128Mi + requests: + memory: 128Mi + cpu: 250m + ref: + resolver: "git" + params: + - name: url + value: $(params.taskGitUrl) + - name: revision + value: $(params.taskGitRevision) + - name: pathInRepo + value: stepactions/create-trusted-artifact/create-trusted-artifact.yaml + params: + - name: ociStorage + value: $(params.ociStorage) + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(results.sourceDataArtifact.path) diff --git a/tasks/managed/direct-sign-index-image/tests/mocks.yaml b/tasks/managed/direct-sign-index-image/tests/mocks.yaml new file mode 100644 index 0000000000..29773dcfa8 --- /dev/null +++ b/tasks/managed/direct-sign-index-image/tests/mocks.yaml @@ -0,0 +1,5 @@ +--- +# Declarative mocks for Tekton tests of this task (Python entrypoint). Rendered +# by .github/scripts/render_python_task_mocks_from_yaml.py when +# test_tekton_tasks.sh runs. +version: 1 diff --git a/tasks/managed/direct-sign-index-image/tests/mocks/direct_sign_index_image.py b/tasks/managed/direct-sign-index-image/tests/mocks/direct_sign_index_image.py new file mode 100644 index 0000000000..d8f89eca2e --- /dev/null +++ b/tasks/managed/direct-sign-index-image/tests/mocks/direct_sign_index_image.py @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eux +echo "Mock direct_sign_index_image.py called with: $*" +echo "$*" >> "$(params.dataDir)/mock_direct_sign_index_image.txt" diff --git a/tasks/managed/direct-sign-index-image/tests/pre-apply-task-hook.sh b/tasks/managed/direct-sign-index-image/tests/pre-apply-task-hook.sh new file mode 100755 index 0000000000..36a2962e07 --- /dev/null +++ b/tasks/managed/direct-sign-index-image/tests/pre-apply-task-hook.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Create a dummy pyxis secret (and delete it first if it exists) +kubectl delete secret test-pyxis-image-cert --ignore-not-found +kubectl create secret generic test-pyxis-image-cert --from-literal=cert=mycert --from-literal=key=mykey diff --git a/tasks/managed/direct-sign-index-image/tests/test-direct-sign-index-image.yaml b/tasks/managed/direct-sign-index-image/tests/test-direct-sign-index-image.yaml new file mode 100644 index 0000000000..24951f6306 --- /dev/null +++ b/tasks/managed/direct-sign-index-image/tests/test-direct-sign-index-image.yaml @@ -0,0 +1,191 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: test-direct-sign-index-image +spec: + description: Test the happy path for direct-sign-index-image + params: + - name: ociStorage + description: The OCI repository where the Trusted Artifacts are stored. + type: string + - name: ociArtifactExpiresAfter + description: Expiration date for the trusted artifacts created in the + OCI repository. An empty string means the artifacts do not expire. + type: string + default: "1d" + - name: orasOptions + description: oras options to pass to Trusted Artifacts calls + type: string + default: "--insecure" + - name: trustedArtifactsDebug + description: Flag to enable debug logging in trusted artifacts. Set to a non-empty string to enable. + type: string + default: "" + - name: dataDir + description: The location where data will be stored + type: string + tasks: + - name: setup + taskSpec: + results: + - name: sourceDataArtifact + type: string + volumes: + - name: workdir + emptyDir: {} + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + - name: "ORAS_OPTIONS" + value: "$(params.orasOptions)" + - name: "DEBUG" + value: "$(params.trustedArtifactsDebug)" + steps: + - name: setup-values + image: quay.io/konflux-ci/release-service-utils@sha256:3cb03b14ac9d90ff27070036ce2b50712e65aa285daeb28852254a745bb25dfc + script: | + #!/usr/bin/env bash + set -eux + + mkdir -p "$(params.dataDir)/$(context.pipelineRun.uid)" + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/data.json" << EOF + { + "sign": { + "configMapName": "signing-config-map" + } + } + EOF + cat > "$(params.dataDir)/$(context.pipelineRun.uid)/fbcResults.json" << EOF + { + "components": [ + { + "target_index": "quay.io/testrepo/testimage:tag", + "rh-registry-repo": "registry.redhat.io/testrepo", + "image_digests": [ + "sha256:6f9a420f660e73a", + "sha256:6f9a420f660e73b" + ] + } + ] + } + EOF + - name: create-trusted-artifact + ref: + name: create-trusted-artifact + params: + - name: ociStorage + value: $(params.ociStorage) + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(results.sourceDataArtifact.path) + - name: run-task + taskRef: + name: direct-sign-index-image + params: + - name: requester + value: testuser + - name: pipelineRunUid + value: $(context.pipelineRun.uid) + - name: dataPath + value: $(context.pipelineRun.uid)/data.json + - name: fbcResultsPath + value: $(context.pipelineRun.uid)/fbcResults.json + - name: pyxisSecret + value: test-pyxis-image-cert + - name: pyxisServer + value: production + - name: ociStorage + value: $(params.ociStorage) + - name: orasOptions + value: $(params.orasOptions) + - name: sourceDataArtifact + value: "$(tasks.setup.results.sourceDataArtifact)=$(params.dataDir)" + - name: dataDir + value: $(params.dataDir) + - name: trustedArtifactsDebug + value: $(params.trustedArtifactsDebug) + - name: taskGitUrl + value: "http://localhost" + - name: taskGitRevision + value: "main" + runAfter: + - setup + - name: check-result + params: + - name: sourceDataArtifact + value: "$(tasks.run-task.results.sourceDataArtifact)=$(params.dataDir)" + taskSpec: + params: + - name: sourceDataArtifact + type: string + volumes: + - name: workdir + emptyDir: {} + stepTemplate: + volumeMounts: + - mountPath: /var/workdir + name: workdir + env: + - name: IMAGE_EXPIRES_AFTER + value: $(params.ociArtifactExpiresAfter) + - name: "ORAS_OPTIONS" + value: "$(params.orasOptions)" + - name: "DEBUG" + value: "$(params.trustedArtifactsDebug)" + steps: + - name: use-trusted-artifact + ref: + name: use-trusted-artifact + params: + - name: workDir + value: $(params.dataDir) + - name: sourceDataArtifact + value: $(params.sourceDataArtifact) + - name: check-result + image: quay.io/konflux-ci/release-service-utils@sha256:3cb03b14ac9d90ff27070036ce2b50712e65aa285daeb28852254a745bb25dfc + script: | + #!/usr/bin/env bash + set -euxo pipefail + + mock_file="$(params.dataDir)/mock_direct_sign_index_image.txt" + if [ ! -f "${mock_file}" ]; then + echo "direct_sign_index_image was not called" + exit 1 + fi + + args=$(cat "${mock_file}") + + if ! echo "${args}" | grep -q -- "--requester testuser"; then + echo "Expected --requester testuser in args: ${args}" + exit 1 + fi + + if ! echo "${args}" | grep -q -- "--pyxis-server production"; then + echo "Expected --pyxis-server production in args: ${args}" + exit 1 + fi + + if ! echo "${args}" | grep -q -- "--pipeline container-signing"; then + echo "Expected --pipeline container-signing in args: ${args}" + exit 1 + fi + + if ! echo "${args}" | grep -q -- "--pipeline-image"; then + echo "Expected --pipeline-image in args: ${args}" + exit 1 + fi + + if ! echo "${args}" | grep -q -- "--fbc-results"; then + echo "Expected --fbc-results in args: ${args}" + exit 1 + fi + + echo "All checks passed" + runAfter: + - run-task