diff --git a/CODEOWNERS b/CODEOWNERS index 1103b7eef..0cb0d5300 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -110,6 +110,9 @@ /task/oci-copy @ralphbean /task/oci-copy-oci-ta @ralphbean +# renovate groupName=buildpack +/task/build-paketo-builder-oci-ta @cmoulliard + # These are auto-generated and often require changes when tasks change. # Allow anyone with write access to approve the changes. /pipelines/*/README.md diff --git a/renovate.json b/renovate.json index 9844f3889..438074996 100644 --- a/renovate.json +++ b/renovate.json @@ -178,6 +178,12 @@ "task/provision-env-with-ephemeral-namespace/**" ], "enabled": false + }, + { + "groupName": "buildpack", + "matchFileNames": [ + "task/build-paketo-builder-oci-ta/**" + ] } ], "postUpdateOptions": [ diff --git a/task/build-paketo-builder-oci-ta/0.1/README.md b/task/build-paketo-builder-oci-ta/0.1/README.md new file mode 100644 index 000000000..3efbbe1a3 --- /dev/null +++ b/task/build-paketo-builder-oci-ta/0.1/README.md @@ -0,0 +1,30 @@ +# build-paketo-builder-oci-ta task + +The `build-paketo-builder-oci-ta` task builds a builder image (e.g. https://github.com/paketo-community/builder-ubi-base) for paketo using as input the [builder.toml](https://buildpacks.io/docs/reference/config/builder-config/) file. The image is build using the pack tool packaged part of the [paketo-container](https://github.com/konflux-ci/paketo-container/) image. +The task also produces the SBOM which is signed and added to the image. + +## Parameters +| name | description | default value | required | +|----------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------|----------| +| BUILD_ARGS | Array of --build-arg values ("arg=value" strings) | [] | false | +| BUILDER_NAME | Name of the paketo builder image containing the tools as: pack, jam, create-package | | true | +| CACHI2_ARTIFACT | The Trusted Artifact URI pointing to the artifact with the prefetched dependencies. | "" | false | +| CONTEXT | Path to the directory to use as context. | . | false | +| HERMETIC | Determines if build will be executed without network access. | false | false | +| IMAGE | Reference of the image buildah will produce. | | true | +| PLATFORM | The platform to build on | | true | +| SOURCE_ARTIFACT | The Trusted Artifact URI pointing to the artifact with the application source code. | | true | +| SOURCE_CODE_DIR | The subpath of the application source code. | "." | true | +| STORAGE_DRIVER | Storage driver to configure for buildah | vfs | false | +| TLSVERIFY | Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) | true | false | +| caTrustConfigMapKey | The name of the key in the ConfigMap that contains the CA bundle data. | ca-bundle.crt | false | +| caTrustConfigMapName | The name of the ConfigMap to read CA bundle data from. | trusted-ca | false | + +## Results +|name|description| +|---|---| +|BASE_IMAGES_DIGESTS|Digests of the base images used for build| +|IMAGE_DIGEST|Digest of the image just built| +|IMAGE_REF|Image reference of the built image| +|IMAGE_URL|Image repository and tag where the built image was pushed| +|SBOM_BLOB_URL|Reference of SBOM blob digest to enable digest-based verification from provenance| diff --git a/task/build-paketo-builder-oci-ta/0.1/build-paketo-builder-oci-ta.yaml b/task/build-paketo-builder-oci-ta/0.1/build-paketo-builder-oci-ta.yaml new file mode 100644 index 000000000..43412abbe --- /dev/null +++ b/task/build-paketo-builder-oci-ta/0.1/build-paketo-builder-oci-ta.yaml @@ -0,0 +1,490 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: build-paketo-builder-oci-ta + annotations: + tekton.dev/pipelines.minVersion: 0.12.1 + tekton.dev/tags: image-build, buildpack, paketo, konflux + labels: + app.kubernetes.io/version: "0.1" + build.appstudio.redhat.com/build_type: pack +spec: + description: |- + build-paketo-builder-oci-ta task builds an image of a paketo builder project using as input the builder.toml file. + The task also produces the SBOM which is signed and added to the image. + params: + - name: BUILD_ARGS + description: the arguments to be passed to the pack command to build the image + type: array + default: [] + - name: CACHI2_ARTIFACT + description: The Trusted Artifact URI pointing to the artifact with the prefetched + dependencies. + default: "" + type: string + - name: CONTEXT + description: Path to the directory to use as context. + default: . + type: string + - name: HERMETIC + description: Determines if build will be executed without network access. + default: "false" + type: string + - name: IMAGE + description: Reference of the image that pack will produce. + type: string + - name: PLATFORM + description: The VM OS type to be used to run the podman container doing the build + default: "linux-mlarge/amd64" + type: string + - name: SOURCE_ARTIFACT + description: The Trusted Artifact URI pointing to the artifact with + the application source code. + type: string + - name: SOURCE_CODE_DIR + description: The directory containing the code source + default: . + type: string + - name: STORAGE_DRIVER + description: Storage driver to configure for buildah + default: vfs + type: string + - name: TLSVERIFY + description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) + type: string + default: "true" + - name: caTrustConfigMapKey + description: The name of the key in the ConfigMap that contains the CA bundle + data. + type: string + default: ca-bundle.crt + - name: caTrustConfigMapName + description: The name of the ConfigMap to read CA bundle data from. + type: string + default: trusted-ca + results: + - name: "IMAGE_URL" + description: "Image repository and tag where the built image was pushed" + type: string + - name: "IMAGE_DIGEST" + description: "Digest of the image just built" + type: string + - name: "IMAGE_REF" + description: "Image reference of the built image" + type: string + - name: "BASE_IMAGES_DIGESTS" + description: "Digests of the base images used for build" + type: string + - name: "SBOM_BLOB_URL" + description: "SBOM Image URL" + type: string + volumes: + - name: ssh + secret: + secretName: "multi-platform-ssh-$(context.taskRun.name)" + - name: shared + emptyDir: {} + - name: varlibcontainers + emptyDir: {} + - configMap: + items: + - key: $(params.caTrustConfigMapKey) + path: ca-bundle.crt + name: $(params.caTrustConfigMapName) + optional: true + name: trusted-ca + - name: workdir + emptyDir: {} + stepTemplate: + env: + - name: BUILDER_IMAGE + value: "quay.io/redhat-user-workloads/konflux-build-pipeli-tenant/paketo-container:ea8ddb8818bb4a55546927e7674b0362dabd6342" + - name: CONTEXT + value: $(params.CONTEXT) + - name: HERMETIC + value: $(params.HERMETIC) + - name: IMAGE + value: $(params.IMAGE) + - name: PLATFORM + value: $(params.PLATFORM) + - name: SOURCE_CODE_DIR + value: $(params.SOURCE_CODE_DIR) + - name: STORAGE_DRIVER + value: $(params.STORAGE_DRIVER) + - name: TLSVERIFY + value: $(params.TLSVERIFY) + + volumeMounts: + - mountPath: /shared + name: shared + - mountPath: /var/workdir + name: workdir + steps: + - name: use-trusted-artifact + image: quay.io/redhat-appstudio/build-trusted-artifacts:latest@sha256:81c4864dae6bb11595f657be887e205262e70086a05ed16ada827fd6391926ac + args: + - use + - $(params.SOURCE_ARTIFACT)=/var/workdir/source + - $(params.CACHI2_ARTIFACT)=/var/workdir/cachi2 + - args: + - "$(params.BUILD_ARGS[*])" + image: "quay.io/konflux-ci/buildah-task:latest" + name: "run-script" + script: |- + #!/usr/bin/env bash + set -eu + set -o pipefail + + echo "Step 1 :: Configure SSH and rsync folders from tekton to the VM" + mkdir -p ~/.ssh + if [ -e "/ssh/error" ]; then + #no server could be provisioned + cat /ssh/error + exit 1 + elif [ -e "/ssh/otp" ]; then + curl --cacert /ssh/otp-ca -XPOST -d @/ssh/otp "$(cat /ssh/otp-server)" >~/.ssh/id_rsa + echo "" >> ~/.ssh/id_rsa + else + cp /ssh/id_rsa ~/.ssh + fi + chmod 0400 ~/.ssh/id_rsa + + SSH_HOST=$(cat /ssh/host) + BUILD_DIR=$(cat /ssh/user-dir) + export SSH_HOST + export BUILD_DIR + export SSH_ARGS=(-o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=10) + + echo "Export different variables which are used within the script like args, etc" + BUILD_ARGS=() + while [[ $# -gt 0 ]]; do BUILD_ARGS+=("$1"); shift; done + export BUILD_ARGS + echo "Build args: $BUILD_ARGS" + + ssh "${SSH_ARGS[@]}" "$SSH_HOST" mkdir -p "$BUILD_DIR/workspaces" "$BUILD_DIR/scripts" "$BUILD_DIR/volumes" + + export PORT_FORWARD="" + export PODMAN_PORT_FORWARD="" + + echo "Rsync folders from pod to VM ..." + rsync -ra "/var/workdir/" "$SSH_HOST:$BUILD_DIR/volumes/workdir/" + rsync -ra "/shared/" "$SSH_HOST:$BUILD_DIR/volumes/shared/" + rsync -ra "/mnt/trusted-ca/" "$SSH_HOST:$BUILD_DIR/volumes/trusted-ca/" + rsync -ra "/tekton/results/" "$SSH_HOST:$BUILD_DIR/results/" + + echo "Step 2 :: Create the bash script to be executed within the VM" + mkdir -p scripts + + cat >scripts/script-setup.sh <<'REMOTESSHEOF' + #!/bin/sh + + echo "Start podman.socket and show podman info" + systemctl --user start podman.socket + sleep 10s + + echo "Podman version" + podman version + + echo "Podman info" + podman info + REMOTESSHEOF + chmod +x scripts/script-setup.sh + + cat >scripts/script-build.sh <<'REMOTESSHEOF' + #!/bin/sh + + cd /var/workdir + + echo "Build the builder image using pack" + for build_arg in "${BUILD_ARGS[@]}"; do + PACK_ARGS+=" $build_arg" + done + + echo "Pack extra args: $PACK_ARGS" + + echo "Execute: pack builder create ..." + export DOCKER_HOST=unix:///workdir/podman.sock + pack config experimental true + + UNSHARE_ARGS=() + if [ "${HERMETIC}" == "true" ]; then + UNSHARE_ARGS+=("--net") + fi + + echo "pack builder create ${IMAGE} --config builder.toml ${PACK_ARGS}" + unshare -Uf "${UNSHARE_ARGS[@]}" --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -w source -- \ + pack builder create ${IMAGE} --config builder.toml ${PACK_ARGS} + + BASE_IMAGE=$(tomljson source/builder.toml | jq '.stack."build-image"') + podman inspect ${BASE_IMAGE} | jq -r '.[].Digest' > /shared/BASE_IMAGES_DIGESTS + echo "$BASE_IMAGE" >/shared/base_images_from_dockerfile + + REMOTESSHEOF + chmod +x scripts/script-build.sh + + cat >scripts/script-post-build.sh <<'REMOTESSHEOF' + #!/bin/sh + + echo "Push the image produced and generate its digest: $IMAGE" + podman push \ + --digestfile $BUILD_DIR/volumes/shared/IMAGE_DIGEST \ + "$IMAGE" + + echo "Export the image as OCI" + podman push "${IMAGE}" "oci:$BUILD_DIR/volumes/shared/konflux-final-image:$IMAGE" + + echo "Export: IMAGE_URL" + echo -n "$IMAGE" > $BUILD_DIR/volumes/shared/IMAGE_URL + REMOTESSHEOF + chmod +x scripts/script-post-build.sh + + + echo "Step 3 :: Execute the bash script on the VM" + + rsync -ra scripts "$SSH_HOST:$BUILD_DIR" + rsync -ra "$HOME/.docker/" "$SSH_HOST:$BUILD_DIR/.docker/" + + echo "Setup VM environment: podman, etc within the VM ..." + ssh "${SSH_ARGS[@]}" "$SSH_HOST" scripts/script-setup.sh + + # Remark: Adding security-opt to by pass: dial unix /workdir/podman.sock: connect: permission denied + ssh "${SSH_ARGS[@]}" "$SSH_HOST" "$PORT_FORWARD" podman run "$PODMAN_PORT_FORWARD" \ + -e BUILDER_IMAGE="$BUILDER_IMAGE" \ + -e HERMETIC="$HERMETIC" \ + -e PLATFORM="$PLATFORM" \ + -e STORAGE_DRIVER="$STORAGE_DRIVER" \ + -e IMAGE="$IMAGE" \ + -e BUILD_ARGS="${BUILD_ARGS[*]}" \ + -e BUILD_DIR="$BUILD_DIR" \ + -v "$BUILD_DIR/volumes/workdir:/var/workdir:Z" \ + -v "$BUILD_DIR/volumes/shared:/shared:Z" \ + -v "$BUILD_DIR/.docker:/root/.docker:Z" \ + -v "$BUILD_DIR/volumes/trusted-ca:/mnt/trusted-ca:Z" \ + -v "$BUILD_DIR/scripts:/scripts:Z" \ + -v "/run/user/1001/podman/podman.sock:/workdir/podman.sock:Z" \ + --user=0 \ + --security-opt label=disable \ + --rm "$BUILDER_IMAGE" /scripts/script-build.sh "$@" + + echo "Execute post build steps within the VM ..." + ssh "${SSH_ARGS[@]}" "$SSH_HOST" \ + BUILD_DIR="$BUILD_DIR" \ + IMAGE="$IMAGE" \ + scripts/script-post-build.sh + + echo "Rsync folders from VM to pod" + rsync -ra "$SSH_HOST:$BUILD_DIR/volumes/workdir/" /var/workdir/ + rsync -ra "$SSH_HOST:$BUILD_DIR/volumes/shared/" "/shared/" + rsync -ra "$SSH_HOST:$BUILD_DIR/results/" "/tekton/results/" + securityContext: + capabilities: + add: + - SETFCAP + volumeMounts: + - name: ssh + mountPath: "/ssh" + readOnly: true + - name: shared + mountPath: "/shared" + readOnly: false + - name: trusted-ca + mountPath: /mnt/trusted-ca + readOnly: true + workingDir: /var/workdir + - computeResources: + limits: + cpu: "2" + memory: 4Gi + requests: + cpu: 500m + memory: 1Gi + image: registry.access.redhat.com/rh-syft-tech-preview/syft-rhel9:1.4.1@sha256:34d7065427085a31dc4949bd283c001b91794d427e1e4cdf1b21ea4faf9fee3f + name: sbom-syft-generate + script: | + #!/bin/bash + set -e + if [ "${IMAGE_APPEND_PLATFORM}" == "true" ]; then + IMAGE="${IMAGE}-${PLATFORM//[^a-zA-Z0-9]/-}" + export IMAGE + fi + + echo "Running syft on the source directory" + syft dir:"/var/workdir/$SOURCE_CODE_DIR/$CONTEXT" --output cyclonedx-json="/var/workdir/sbom-source.json" + + echo "Running syft on the image filesystem" + syft scan oci-dir:/shared/konflux-final-image -o cyclonedx-json > /var/workdir/sbom-image.json + volumeMounts: + - mountPath: /shared + name: shared + workingDir: /var/workdir/source + - computeResources: + limits: + cpu: 200m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + image: quay.io/redhat-appstudio/sbom-utility-scripts-image@sha256:11851ba63f63dfdcf722e47993f41a1f5f31a7a0dc8aa85b810ce2466daf23af + name: prepare-sboms + script: | + #!/bin/bash + set -e + if [ "${IMAGE_APPEND_PLATFORM}" == "true" ]; then + IMAGE="${IMAGE}-${PLATFORM//[^a-zA-Z0-9]/-}" + export IMAGE + fi + + + echo "Merging contents of sbom-source.json and sbom-image.json into sbom-cyclonedx.json" + python3 /scripts/merge_syft_sboms.py + + if [ -f "sbom-cachi2.json" ]; then + echo "Merging contents of sbom-cachi2.json into sbom-cyclonedx.json" + python3 /scripts/merge_cachi2_sboms.py sbom-cachi2.json sbom-cyclonedx.json >sbom-temp.json + mv sbom-temp.json sbom-cyclonedx.json + fi + + echo "Creating sbom-purl.json" + python3 /scripts/create_purl_sbom.py + + echo "Adding base images data to sbom-cyclonedx.json" + python3 /scripts/base_images_sbom_script.py \ + --sbom=sbom-cyclonedx.json \ + --base-images-from-dockerfile=/shared/base_images_from_dockerfile \ + --base-images-digests=/shared/BASE_IMAGES_DIGESTS + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /shared + name: shared + workingDir: /var/workdir + - computeResources: + limits: + cpu: "4" + memory: 4Gi + requests: + cpu: "1" + memory: 1Gi + image: quay.io/konflux-ci/buildah-task:latest@sha256:b2d6c32d1e05e91920cd4475b2761d58bb7ee11ad5dff3ecb59831c7572b4d0c + name: inject-sbom-and-push + script: | + #!/bin/bash + set -e + if [ "${IMAGE_APPEND_PLATFORM}" == "true" ]; then + IMAGE="${IMAGE}-${PLATFORM//[^a-zA-Z0-9]/-}" + export IMAGE + fi + + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + cp -vf $ca_bundle /etc/pki/ca-trust/source/anchors + update-ca-trust + fi + + + echo "Pull the image from the OCI storage." + + buildah --storage-driver "$STORAGE_DRIVER" pull "$IMAGE" + + + echo "Copy within the container of the image the sbom files" + + container=$(buildah --storage-driver "$STORAGE_DRIVER" from --pull-never "$IMAGE") + buildah --storage-driver "$STORAGE_DRIVER" copy "$container" sbom-cyclonedx.json sbom-purl.json /root/buildinfo/content_manifests/ + + BUILDAH_ARGS=() + if [ "${SQUASH}" == "true" ]; then + BUILDAH_ARGS+=("--squash") + fi + + buildah --storage-driver "$STORAGE_DRIVER" commit "${BUILDAH_ARGS[@]}" "$container" "$IMAGE" + + + echo "Pushing to ${IMAGE%:*}:${TASKRUN_NAME}" + + retries=5 + if ! buildah push \ + --retry "$retries" \ + --storage-driver "$STORAGE_DRIVER" \ + --tls-verify="$TLSVERIFY" \ + "$IMAGE" \ + "docker://${IMAGE%:*}:$(context.taskRun.name)"; then + echo "Failed to push sbom image to ${IMAGE%:*}:$(context.taskRun.name) after ${retries} tries" + exit 1 + fi + + + echo "Pushing to ${IMAGE}" + + if ! buildah push \ + --retry "$retries" \ + --storage-driver "$STORAGE_DRIVER" \ + --tls-verify="$TLSVERIFY" \ + --digestfile "/var/workdir/image-digest" "$IMAGE" \ + "docker://$IMAGE"; then + echo "Failed to push sbom image to $IMAGE after ${retries} tries" + exit 1 + fi + + + echo "Save the different results" + + tee "$(results.IMAGE_DIGEST.path)" < "/var/workdir/image-digest" + echo -n "$IMAGE" | tee "$(results.IMAGE_URL.path)" + { + echo -n "${IMAGE}@" + cat "/var/workdir/image-digest" + } >"$(results.IMAGE_REF.path)" + + # Remove tag from IMAGE while allowing registry to contain a port number. + sbom_repo="${IMAGE%:*}" + sbom_digest="$(sha256sum sbom-cyclonedx.json | cut -d' ' -f1)" + # The SBOM_BLOB_URL is created by `cosign attach sbom`. + echo -n "${sbom_repo}@sha256:${sbom_digest}" | tee "$(results.SBOM_BLOB_URL.path)" + + tee "$(results.BASE_IMAGES_DIGESTS.path)" < /shared/BASE_IMAGES_DIGESTS + securityContext: + capabilities: + add: + - SETFCAP + runAsUser: 0 + volumeMounts: + - mountPath: /var/lib/containers + name: varlibcontainers + - mountPath: /shared + name: shared + - mountPath: /mnt/trusted-ca + name: trusted-ca + readOnly: true + workingDir: /var/workdir + - computeResources: + limits: + cpu: 200m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + image: quay.io/konflux-ci/appstudio-utils:48c311af02858e2422d6229600e9959e496ddef1@sha256:91ddd999271f65d8ec8487b10f3dd378f81aa894e11b9af4d10639fd52bba7e8 + name: upload-sbom + script: | + #!/bin/bash + set -e + if [ "${IMAGE_APPEND_PLATFORM}" == "true" ]; then + IMAGE="${IMAGE}-${PLATFORM//[^a-zA-Z0-9]/-}" + export IMAGE + fi + ca_bundle=/mnt/trusted-ca/ca-bundle.crt + if [ -f "$ca_bundle" ]; then + echo "INFO: Using mounted CA bundle: $ca_bundle" + cp -vf $ca_bundle /etc/pki/ca-trust/source/anchors + update-ca-trust + fi + + cosign attach sbom --sbom sbom-cyclonedx.json --type cyclonedx "$(cat "$(results.IMAGE_REF.path)")" + volumeMounts: + - mountPath: /mnt/trusted-ca + name: trusted-ca + readOnly: true + workingDir: /var/workdir