diff --git a/Makefile b/Makefile index 085898b..71dba15 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,10 @@ E2E_BUILDAH_IMAGE_TAG ?= task-buildah:latest # fully qualified container image passed to buildah task IMAGE param E2E_BUILDAH_PARAMS_IMAGE ?= $(IMAGE_BASE)/${E2E_BUILDAH_IMAGE_TAG} +E2E_BUILDAH_USER_NAMESPACE="auto" # Options: "", "private", "auto", "host", or a specific path +E2E_BUILDAH_UID_MAP="0:100000:65536" # Mapping for container UIDs "CONTAINER_UID:HOST_UID:SIZE". +E2E_BUILDAH_GID_MAP="0:100000:65536" # Mapping for container GIDs "CONTAINER_UID:HOST_UID:SIZE". + # container image name and tag to be created by s2i during e2e E2E_S2I_IMAGE_TAG ?= task-s2i:latest # (fully qualified) container image passed to s2i task IMAGE param @@ -175,9 +179,16 @@ test-e2e-buildah: bats test-e2e-buildah-openshift: prepare-e2e-buildah test-e2e-buildah-openshift: REGISTRY_URL = image-registry.openshift-image-registry.svc.cluster.local:5000 test-e2e-buildah-openshift: REGISTRY_NAMESPACE = $(shell oc project -q) -test-e2e-buildah-openshift: E2E_TESTS = $(E2E_TEST_DIR)/*buildah*.bats +test-e2e-buildah-openshift: E2E_TESTS = $(E2E_TEST_DIR)/*buildah.bats test-e2e-buildah-openshift: bats +.PHONY: test-e2e-buildah-userns-openshift +test-e2e-buildah-userns-openshift: prepare-e2e-buildah +test-e2e-buildah-userns-openshift: REGISTRY_URL = image-registry.openshift-image-registry.svc.cluster.local:5000 +test-e2e-buildah-userns-openshift: REGISTRY_NAMESPACE = $(shell oc project -q) +test-e2e-buildah-userns-openshift: E2E_TESTS = $(E2E_TEST_DIR)/*buildah-userns.bats +test-e2e-buildah-userns-openshift: bats + # runs the end-to-end tests for s2i-python .PHONY: test-e2e-s2i-python test-e2e-s2i-python: E2E_S2I_LANGUAGE = python diff --git a/scripts/buildah-bud-userns.sh b/scripts/buildah-bud-userns.sh new file mode 100644 index 0000000..a63169d --- /dev/null +++ b/scripts/buildah-bud-userns.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +shopt -s inherit_errexit +set -eu -o pipefail + +source "$(dirname ${BASH_SOURCE[0]})/common.sh" +source "$(dirname ${BASH_SOURCE[0]})/buildah-common.sh" + +function _buildah() { + buildah \ + --storage-driver="${PARAMS_STORAGE_DRIVER}" \ + --tls-verify="${PARAMS_TLS_VERIFY}" \ + ${*} +} + +# Prepare Buildah command +BUILD_CMD="HOME=/workspace/source _buildah bud" + +# Extra arguments for the build +if [[ -n "${PARAMS_BUILD_EXTRA_ARGS}" ]]; then + phase "Extra 'buildah bud' arguments informed: '${PARAMS_BUILD_EXTRA_ARGS}'" + BUILD_CMD+=" ${PARAMS_BUILD_EXTRA_ARGS}" +fi + +# User Namespace Configuration +if [[ -n "${PARAMS_USER_NAMESPACE}" ]]; then + BUILD_CMD+=" --userns=${PARAMS_USER_NAMESPACE}" +fi + +if [[ -n "${PARAMS_UID_MAP}" ]]; then + BUILD_CMD+=" --userns-uid-map=${PARAMS_UID_MAP}" +fi + +if [[ -n "${PARAMS_GID_MAP}" ]]; then + BUILD_CMD+=" --userns-gid-map=${PARAMS_GID_MAP}" +fi + +ENTITLEMENT_VOLUME="" +if [[ "${WORKSPACES_RHEL_ENTITLEMENT_BOUND}" == "true" ]]; then + ENTITLEMENT_VOLUME="--volume ${WORKSPACES_RHEL_ENTITLEMENT_PATH}:/etc/pki/entitlement" +fi + +# Adding entitlement volume and build arguments +BUILD_CMD+=" ${ENTITLEMENT_VOLUME} ${BUILD_ARGS[@]}" + +# Check for /etc/subgid and /etc/subuid +if [[ -f /etc/subgid ]]; then + phase "Contents of /etc/subgid:" + ls -l /etc/subgid + cat /etc/subgid +else + phase "/etc/subgid does not exist." +fi + +if [[ -f /etc/subuid ]]; then + phase "Contents of /etc/subuid:" + ls -l /etc/subuid + cat /etc/subuid +else + phase "/etc/subuid does not exist." +fi + +# Building the image +phase "Building '${PARAMS_IMAGE}' based on '${DOCKERFILE_FULL}'" +BUILD_CMD+=" --file='${DOCKERFILE_FULL}' --tag='${PARAMS_IMAGE}' '${PARAMS_CONTEXT}'" + +# Execute the command +eval ${BUILD_CMD} + +if [[ "${PARAMS_SKIP_PUSH}" == "true" ]]; then + phase "Skipping pushing '${PARAMS_IMAGE}' to the container registry!" + exit 0 +fi + +# +# Push +# + +phase "Pushing '${PARAMS_IMAGE}' to the container registry" + +[[ -n "${PARAMS_PUSH_EXTRA_ARGS}" ]] && + phase "Extra 'buildah bud' arguments informed: '${PARAMS_PUSH_EXTRA_ARGS}'" + +# temporary file to store the image digest, information only obtained after pushing the image to the +# container registry +declare -r digest_file="/tmp/buildah-digest.txt" + +_buildah push ${PARAMS_PUSH_EXTRA_ARGS} \ + --digestfile="${digest_file}" \ + "${PARAMS_IMAGE}" \ + "docker://${PARAMS_IMAGE}" + +# +# Results +# + + +phase "Inspecting digest report ('${digest_file}')" + +[[ ! -r "${digest_file}" ]] && + fail "Unable to find digest-file at '${digest_file}'" + +declare -r digest_sum="$(cat ${digest_file})" + +[[ -z "${digest_sum}" ]] && + fail "Digest file '${digest_file}' is empty!" + +phase "Successfuly built container image '${PARAMS_IMAGE}' ('${digest_sum}')" +echo -n "${PARAMS_IMAGE}" | tee ${RESULTS_IMAGE_URL_PATH} +echo -n "${digest_sum}" | tee ${RESULTS_IMAGE_DIGEST_PATH} diff --git a/templates/task-buildah-userns.yaml b/templates/task-buildah-userns.yaml new file mode 100644 index 0000000..1e99122 --- /dev/null +++ b/templates/task-buildah-userns.yaml @@ -0,0 +1,128 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: buildah-userns + labels: + app.kubernetes.io/version: {{ .Chart.Version }} +{{- if .Values.annotations }} + annotations: + {{- .Values.annotations | toYaml | nindent 4 }} + io.kubernetes.cri-o.userns-mode: "auto" + io.openshift.builder: "true" +{{- end }} +spec: + description: | + Buildah task builds source into a container image and + then pushes it to a container registry using user namespaces. + + workspaces: + - name: source + optional: false + description: | + Container build context, like for instnace a application source code + followed by a `Dockerfile`. + - name: dockerconfig + description: >- + An optional workspace that allows providing a .docker/config.json file + for Buildah to access the container registry. + The file should be placed at the root of the Workspace with name config.json + or .dockerconfigjson. + optional: true + - name: rhel-entitlement + description: >- + An optional workspace that allows providing the entitlement keys + for Buildah to access subscription. The mounted workspace contains + entitlement.pem and entitlement-key.pem. + optional: true + mountPath: /tmp/entitlement + params: + - name: IMAGE + type: string + description: | + Fully qualified container image name to be built by buildah. + - name: DOCKERFILE + type: string + default: ./Dockerfile + description: | + Path to the `Dockerfile` (or `Containerfile`) relative to the `source` workspace. + - name: BUILD_ARGS + type: array + default: + - "" + description: | + Dockerfile build arguments, array of key=value + - name: USER_NAMESPACE + type: string + description: | + User namespace configuration. Valid values: "", "private", "auto", "host", or path to an existing user namespace. + default: "auto" + - name: UID_MAP + type: string + description: | + UID mapping for user namespace, format: "CONTAINER_UID:HOST_UID:SIZE". + default: "" + - name: GID_MAP + type: string + description: | + GID mapping for user namespace, format: "CONTAINER_GID:HOST_GID:SIZE". + default: "" + +{{- include "params_buildah_common" . | nindent 4 }} +{{- include "params_common" . | nindent 4 }} + + results: +{{- include "results_buildah" . | nindent 4 }} + + stepTemplate: + env: +{{- $variables := list + "params.IMAGE" + "params.CONTEXT" + "params.DOCKERFILE" + "params.FORMAT" + "params.STORAGE_DRIVER" + "params.BUILD_EXTRA_ARGS" + "params.PUSH_EXTRA_ARGS" + "params.SKIP_PUSH" + "params.TLS_VERIFY" + "params.VERBOSE" + "params.USER_NAMESPACE" + "params.UID_MAP" + "params.GID_MAP" + "workspaces.source.bound" + "workspaces.source.path" + "workspaces.dockerconfig.bound" + "workspaces.dockerconfig.path" + "workspaces.rhel-entitlement.bound" + "workspaces.rhel-entitlement.path" + "results.IMAGE_URL.path" + "results.IMAGE_DIGEST.path" +}} +{{- include "environment" ( list $variables ) | nindent 6 }} + + steps: + - name: build + image: {{ .Values.images.buildah }} + workingDir: $(workspaces.source.path) + securityContext: + capabilities: + add: ["SETFCAP"] + runAsUser: 1000 + args: + - $(params.BUILD_ARGS[*]) + script: | +{{- include "load_scripts" ( list . ( list "buildah-" ) ( list "/scripts/buildah-bud-userns.sh" ) ) | nindent 8 }} + volumeMounts: + - name: scripts-dir + mountPath: /scripts + - name: varlibcontainers + mountPath: /workspace/source/build/.local/share/containers + + volumes: + - name: scripts-dir + emptyDir: {} + - name: varlibcontainers + emptyDir: {} + + diff --git a/test/e2e/e2e-buildah-userns.bats b/test/e2e/e2e-buildah-userns.bats new file mode 100644 index 0000000..e2e6b7e --- /dev/null +++ b/test/e2e/e2e-buildah-userns.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats + +source ./test/helper/helper.sh + +declare -rx E2E_BUILDAH_PVC_NAME="${E2E_BUILDAH_PVC_NAME:-}" +declare -rx E2E_BUILDAH_PARAMS_IMAGE="${E2E_BUILDAH_PARAMS_IMAGE:-}" +declare -rx E2E_BUILDAH_USER_NAMESPACE="${E2E_BUILDAH_USER_NAMESPACE:-auto}" +declare -rx E2E_BUILDAH_UID_MAP="${E2E_BUILDAH_UID_MAP:-}" +declare -rx E2E_BUILDAH_GID_MAP="${E2E_BUILDAH_GID_MAP:-}" + +# Testing the Buildah task with user namespace support +@test "[e2e] buildah task is able to build and push a container image with user namespaces" { + # asserting all required configuration is informed + [ -n "${E2E_BUILDAH_PARAMS_IMAGE}" ] + [ -n "${E2E_BUILDAH_USER_NAMESPACE}" ] + [ -n "${E2E_PARAMS_TLS_VERIFY}" ] + + # cleaning up all the existing resources before starting a new taskrun + run kubectl delete taskrun --all + assert_success + + # + # E2E Pipeline + # + + run kubectl delete pipelinerun --all + assert_success + + run tkn pipeline start task-buildah-userns \ + --param="IMAGE=${E2E_BUILDAH_PARAMS_IMAGE}" \ + --param="USER_NAMESPACE=${E2E_BUILDAH_USER_NAMESPACE}" \ + --param="UID_MAP=${E2E_BUILDAH_UID_MAP}" \ + --param="GID_MAP=${E2E_BUILDAH_GID_MAP}" \ + --param="TLS_VERIFY=${E2E_PARAMS_TLS_VERIFY}" \ + --param="VERBOSE=true" \ + --workspace name=source,volumeClaimTemplateFile=./test/e2e/resources/workspace-template.yaml \ + --filename=test/e2e/resources/pipeline-buildah-userns.yaml \ + --showlog >&3 + assert_success + + # waiting a few seconds before asserting results + sleep 30 + + # asserting the pipelinerun status, making sure all steps have been successful + assert_tekton_resource "pipelinerun" --partial '(Failed: 0, Cancelled: 0), Skipped: 0' + + # asserting the latest taskrun instance to inspect the resources against a regular expression + assert_tekton_resource "taskrun" --regexp $'\S+\n?IMAGE_DIGEST=\S+\nIMAGE_URL=\S+' +} diff --git a/test/e2e/resources/pipeline-buildah-userns.yaml b/test/e2e/resources/pipeline-buildah-userns.yaml new file mode 100644 index 0000000..6a9f411 --- /dev/null +++ b/test/e2e/resources/pipeline-buildah-userns.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + labels: + name: task-buildah-userns + name: task-buildah-userns +spec: + params: + - name: IMAGE + type: string + - name: TLS_VERIFY + type: string + - name: VERBOSE + type: string + - name: USER_NAMESPACE + type: string + default: "auto" + - name: UID_MAP + type: string + - name: GID_MAP + type: string + + workspaces: + - name: source + + tasks: + - name: containerfile-stub + taskRef: + name: containerfile-stub + workspaces: + - name: source + workspace: source + + - name: buildah-userns + taskRef: + name: buildah-userns + runAfter: + - containerfile-stub + workspaces: + - name: source + workspace: source + params: + - name: IMAGE + value: $(params.IMAGE) + - name: TLS_VERIFY + value: $(params.TLS_VERIFY) + - name: VERBOSE + value: $(params.VERBOSE) + - name: USER_NAMESPACE + value: $(params.USER_NAMESPACE) + - name: UID_MAP + value: $(params.UID_MAP) + - name: GID_MAP + value: $(params.GID_MAP)