diff --git a/task/build-paketo-ubi-builder/0.1/README.md b/task/build-paketo-ubi-builder/0.1/README.md new file mode 100644 index 0000000000..329a3e9f74 --- /dev/null +++ b/task/build-paketo-ubi-builder/0.1/README.md @@ -0,0 +1,30 @@ +# build-paketo-ubi-builder task + +The `build-paketo-ubi-builder` task builds an ubi image for a paketo builder 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 | quay.io/redhat-user-workloads/konflux-build-pipeli-tenant/paketo-container | false | +| 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-ubi-builder/0.1/build-paketo-ubi-builder.yaml b/task/build-paketo-ubi-builder/0.1/build-paketo-ubi-builder.yaml new file mode 100644 index 0000000000..56a0ce972e --- /dev/null +++ b/task/build-paketo-ubi-builder/0.1/build-paketo-ubi-builder.yaml @@ -0,0 +1,527 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: build-paketo-ubi-builder + 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-ubi-builder task builds a ubi 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: BUILDER_IMAGE + description: The image packaging the paketo tools and to be used to build + type: string + default: "quay.io/redhat-user-workloads/konflux-build-pipeli-tenant/paketo-container:ea8ddb8818bb4a55546927e7674b0362dabd6342" + - 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: $(params.BUILDER_IMAGE) + - 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 "##########################################################################################" + echo "### Step 1 :: Configure SSH and rsync folders from tekton to the VM" + echo "##########################################################################################" + 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 + + export SSH_HOST=$(cat /ssh/host) + export BUILD_DIR=$(cat /ssh/user-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" + export 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 $(workspaces.source.path)/ "$SSH_HOST:$BUILD_DIR/volumes/workdir/" + 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 "##########################################################################################" + echo "### Step 2 :: Create the bash script to be executed within the VM" + echo "##########################################################################################" + 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 + + REMOTESSHEOF + chmod +x scripts/script-build.sh + + cat >scripts/script-post-build.sh <<'REMOTESSHEOF' + #!/bin/sh + + echo "###########################################################" + + echo "### Push the image produced and generate its digest: $IMAGE" + podman push \ + --digestfile $BUILD_DIR/volumes/shared/IMAGE_DIGEST \ + "$IMAGE" + + echo "###########################################################" + echo "### Export the image as OCI" + podman push "${IMAGE}" "oci:$BUILD_DIR/volumes/shared/konflux-final-image:$IMAGE" + echo "###########################################################" + + echo "###########################################################" + echo "### Export: IMAGE_URL" + echo "###########################################################" + echo -n "$IMAGE" > $BUILD_DIR/volumes/shared/IMAGE_URL + REMOTESSHEOF + chmod +x scripts/script-post-build.sh + + echo "##########################################################################################" + echo "### Step 3 :: Execute the bash script on the VM" + echo "##########################################################################################" + 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 + + # 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/" "$(workspaces.source.path)/" + 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 "##########################################################################################" + echo "### Running syft on the source directory" + echo "##########################################################################################" + syft dir:"/var/workdir/$SOURCE_CODE_DIR/$CONTEXT" --output cyclonedx-json="/var/workdir/sbom-source.json" + + echo "##########################################################################################" + echo "### Running syft on the image filesystem" + echo "##########################################################################################" + # syft dir:"$(cat /shared/container_path)" --output cyclonedx-json="/var/workdir/sbom-image.json" + 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 "##########################################################################################" + echo "## Merging contents of sbom-source.json and sbom-image.json into sbom-cyclonedx.json" + echo "##########################################################################################" + python3 /scripts/merge_syft_sboms.py + + if [ -f "sbom-cachi2.json" ]; then + echo "##########################################################################################" + echo "## Merging contents of sbom-cachi2.json into sbom-cyclonedx.json" + echo "##########################################################################################" + python3 /scripts/merge_cachi2_sboms.py sbom-cachi2.json sbom-cyclonedx.json >sbom-temp.json + mv sbom-temp.json sbom-cyclonedx.json + fi + + echo "##########################################################################################" + echo "Creating sbom-purl.json" + echo "##########################################################################################" + python3 /scripts/create_purl_sbom.py + + # TODO: How can we get for the paketo stuffs: base_images_from_dockerfile + # echo "##########################################################################################" + # echo "Adding base images data to sbom-cyclonedx.json" + # echo "##########################################################################################" + # 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 + + retry() { + status=-1 + max_run=5 + sleep_sec=10 + + for run in $(seq 1 $max_run); do + status=0 + [ "$run" -gt 1 ] && sleep $sleep_sec + "$@" && break || status=$? + done + return $status + } + + 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 "##########################################################################################" + echo "Pull the image from the OCI storage." + echo "##########################################################################################" + buildah --storage-driver $STORAGE_DRIVER pull $IMAGE + + echo "##########################################################################################" + echo "## Copy within the container of the image the sbom files" + echo "##########################################################################################" + #base_image_name=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.name"}}' $IMAGE | cut -f1 -d'@') + #base_image_digest=$(buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.digest"}}' $IMAGE) + + 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/ + + # The image build don't include "ImageAnnotations" as "null" + #buildah config -a org.opencontainers.image.base.name=${base_image_name} -a org.opencontainers.image.base.digest=${base_image_digest} $container + + BUILDAH_ARGS=() + if [ "${SQUASH}" == "true" ]; then + BUILDAH_ARGS+=("--squash") + fi + + buildah --storage-driver $STORAGE_DRIVER commit "${BUILDAH_ARGS[@]}" $container $IMAGE + set +x + echo "######################################################$TLSVERIFY####################################" + echo "Pushing to ${IMAGE%:*}:${TASKRUN_NAME}" + echo "##########################################################################################" + if ! retry buildah --storage-driver $STORAGE_DRIVER push \ + --tls-verify="$TLSVERIFY" \ + "$IMAGE" \ + "docker://${IMAGE%:*}:$(context.taskRun.name)"; then + echo "Failed to push sbom image to ${IMAGE%:*}:$(context.taskRun.name) after ${max_run} tries" + exit 1 + fi + + echo "##########################################################################################" + echo "Pushing to ${IMAGE}" + echo "##########################################################################################" + if ! retry buildah --storage-driver $STORAGE_DRIVER push \ + --tls-verify="$TLSVERIFY" \ + --digestfile "/var/workdir/image-digest" "$IMAGE" \ + "docker://$IMAGE"; then + echo "Failed to push sbom image to $IMAGE after ${max_run} tries" + exit 1 + fi + + echo "##########################################################################################" + echo "## Save the different results" + echo "##########################################################################################" + cat "/shared"/image-digest | tee $(results.IMAGE_DIGEST.path) + 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)" + + cat "/shared"/BASE_IMAGES_DIGESTS | tee $(results.BASE_IMAGES_DIGESTS.path) + 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 \ No newline at end of file diff --git a/task/build-paketo-ubi-builder/OWNERS b/task/build-paketo-ubi-builder/OWNERS new file mode 100644 index 0000000000..d3f0ff4a7e --- /dev/null +++ b/task/build-paketo-ubi-builder/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs: https://go.k8s.io/owners +approvers: + - build-team +reviewers: + - build-team