Open wlynch opened 1 year ago
One potential resolution for this is to provide an optional parameter to the git resolver to define a floating reference. At resolution time, the git resolver can lazily walk the tree from the floating reference until a commit is reached (or fail if the commit is not reached). This should minimize the cost of tree walking while protecting against the described attack vector. The time to verify the compute should be minimized as long as the pinned references are reasonably close to the floating reference's HEAD.
Based on the imposter commit detection tool: https://github.com/chainguard-dev/clank
EDIT: Or by using something like git merge-base --is-ancestor $sha $branch
cc @abayer
@vdemeester @abayer , is there any update on this?
Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale
with a justification.
Stale issues rot after an additional 30d of inactivity and eventually close.
If this issue is safe to close now please do so with /close
with a justification.
If this issue should be exempted, mark the issue as frozen with /lifecycle frozen
with a justification.
/lifecycle stale
Send feedback to tektoncd/plumbing.
/lifecycle frozen
Is this really an issue? If you have free choice of any commit in the repo, then you could easily pick an old commit before a vulnerability was fixed, or when there was some sort of misconfiguration.
It is an issue because of the ability to believe that a commit is trusted as having passed through certain requirements to be merged into a trusted repository. Instead, there are no guarantees about unvalidated git commit references as content can be added without any checks in place.
Expected Behavior
Remote resolution only resolves commits belonging to the repository specified.
Actual Behavior
Anonymous Git remote resolution will fail with:
However authenticated remote resolution will blindly fetch the commit, even though it's from a fork and not the originally specified repo. This can be used to trick the resolver / maintainers to use a non-reviewed version of a Task by making it seem like it's coming from the parent repo.
The root cause of this really a behavior of how GitHub handles forked commits, but we're inconsistent with how we handle this. See https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd for more details.
Steps to Reproduce the Problem
Anonymous Fetching (correctly fails)
Full spec
```yaml apiVersion: tekton.dev/v1 kind: TaskRun metadata: annotations: chains.tekton.dev/signed: "true" chains.tekton.dev/transparency: https://rekor.sigstore.dev/api/v1/log/entries?logIndex=14950865 pipeline.tekton.dev/release: d1b63d4 creationTimestamp: "2023-03-08T15:48:21Z" finalizers: - chains.tekton.dev generateName: git-remote- generation: 1 labels: app.kubernetes.io/managed-by: tekton-pipelines name: git-remote-bt2sw namespace: default resourceVersion: "285651" uid: 154ee892-86c3-4e09-a59d-076c6b4c88a5 spec: params: - name: url value: https://github.com/wlynch/imposter-commits-demo serviceAccountName: default taskRef: kind: Task params: - name: url value: https://github.com/tektoncd-catalog/git-clone - name: revision value: 2298f211d1d160ec45c497cb2d719d0794383ba4 - name: pathInRepo value: task/git-clone/git-clone.yaml resolver: git timeout: 1h0m0s workspaces: - emptyDir: {} name: output status: completionTime: "2023-03-08T15:48:22Z" conditions: - lastTransitionTime: "2023-03-08T15:48:22Z" message: 'failed to get task: error requesting remote resource: error getting "Git" "default/git-d9f145400a6dd620a430f19b39e885ad": revision error: reference not found' reason: TaskRunResolutionFailed status: "False" type: Succeeded podName: "" startTime: "2023-03-08T15:48:21Z" ```Authenticated Fetching (incorrectly succeeds)
Full spec
```yaml apiVersion: tekton.dev/v1 kind: TaskRun metadata: annotations: chains.tekton.dev/signed: "true" chains.tekton.dev/transparency: https://rekor.sigstore.dev/api/v1/log/entries?logIndex=14950489 pipeline.tekton.dev/release: d1b63d4 tekton.dev/categories: Git tekton.dev/displayName: git clone tekton.dev/pipelines.minVersion: 0.29.0 tekton.dev/platforms: linux/amd64,linux/s390x,linux/ppc64le,linux/arm64 tekton.dev/tags: git creationTimestamp: "2023-03-08T15:44:22Z" finalizers: - chains.tekton.dev generateName: git-remote- generation: 1 labels: app.kubernetes.io/managed-by: tekton-pipelines app.kubernetes.io/version: "0.8" tekton.dev/task: git-clone name: git-remote-l886g namespace: default resourceVersion: "284978" uid: 32fe08cd-2c70-4343-b23d-d4dc7a8aab8d spec: params: - name: url value: https://github.com/wlynch/imposter-commits-demo serviceAccountName: default taskRef: kind: Task params: - name: org value: tektoncd-catalog - name: repo value: git-clone - name: revision value: 2298f211d1d160ec45c497cb2d719d0794383ba4 - name: pathInRepo value: task/git-clone/git-clone.yaml resolver: git timeout: 1h0m0s workspaces: - emptyDir: {} name: output status: completionTime: "2023-03-08T15:44:33Z" conditions: - lastTransitionTime: "2023-03-08T15:44:33Z" message: All Steps have completed executing reason: Succeeded status: "True" type: Succeeded podName: git-remote-l886g-pod results: - name: commit type: string value: 684d75d96546c7eb7a6c9ae0f92b01457459ed2d - name: url type: string value: https://github.com/wlynch/imposter-commits-demo startTime: "2023-03-08T15:44:22Z" steps: - container: step-clone imageID: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:c0b0ed1cd81090ce8eecf60b936e9345089d9dfdb6ebdd2fd7b4a0341ef4f2b9 name: clone terminated: containerID: containerd://074c57c88629ce0fe8e99028e1a0acbf2e28e982198b06ff410aea9f32ab14e8 exitCode: 0 finishedAt: "2023-03-08T15:44:32Z" message: '[{"key":"commit","value":"684d75d96546c7eb7a6c9ae0f92b01457459ed2d","type":1},{"key":"url","value":"https://github.com/wlynch/imposter-commits-demo","type":1}]' reason: Completed startedAt: "2023-03-08T15:44:31Z" taskSpec: description: |- These Tasks are Git tasks to work with repositories used by other tasks in your Pipeline. The git-clone Task will clone a repo from the provided url into the output Workspace. By default the repo will be cloned into the root of your Workspace. You can clone into a subdirectory by setting this Task's subdirectory param. This Task also supports sparse checkouts. To perform a sparse checkout, pass a list of comma separated directory patterns to this Task's sparseCheckoutDirectories param. params: - description: Repository URL to clone from. name: url type: string - default: "" description: Revision to checkout. (branch, tag, sha, ref, etc...) name: revision type: string - default: "" description: Refspec to fetch before checking out revision. name: refspec type: string - default: "true" description: Initialize and fetch git submodules. name: submodules type: string - default: "1" description: Perform a shallow clone, fetching only the most recent N commits. name: depth type: string - default: "true" description: Set the `http.sslVerify` global git config. Setting this to `false` is not advised unless you are sure that you trust your git remote. name: sslVerify type: string - default: ca-bundle.crt description: file name of mounted crt using ssl-ca-directory workspace. default value is ca-bundle.crt. name: crtFileName type: string - default: "" description: Subdirectory inside the `output` Workspace to clone the repo into. name: subdirectory type: string - default: "" description: Define the directory patterns to match or exclude when performing a sparse checkout. name: sparseCheckoutDirectories type: string - default: "true" description: Clean out the contents of the destination directory if it already exists before cloning. name: deleteExisting type: string - default: "" description: HTTP proxy server for non-SSL requests. name: httpProxy type: string - default: "" description: HTTPS proxy server for SSL requests. name: httpsProxy type: string - default: "" description: Opt out of proxying HTTP/HTTPS requests. name: noProxy type: string - default: "true" description: Log the commands that are executed during `git-clone`'s operation. name: verbose type: string - default: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.29.0 description: The image providing the git-init binary that this Task runs. name: gitInitImage type: string - default: /tekton/home description: | Absolute path to the user's home directory. name: userHome type: string results: - description: The precise commit SHA that was fetched by this Task. name: commit type: string - description: The precise URL that was fetched by this Task. name: url type: string steps: - computeResources: {} env: - name: HOME value: /tekton/home - name: PARAM_URL value: https://github.com/wlynch/imposter-commits-demo - name: PARAM_REVISION - name: PARAM_REFSPEC - name: PARAM_SUBMODULES value: "true" - name: PARAM_DEPTH value: "1" - name: PARAM_SSL_VERIFY value: "true" - name: PARAM_CRT_FILENAME value: ca-bundle.crt - name: PARAM_SUBDIRECTORY - name: PARAM_DELETE_EXISTING value: "true" - name: PARAM_HTTP_PROXY - name: PARAM_HTTPS_PROXY - name: PARAM_NO_PROXY - name: PARAM_VERBOSE value: "true" - name: PARAM_SPARSE_CHECKOUT_DIRECTORIES - name: PARAM_USER_HOME value: /tekton/home - name: WORKSPACE_OUTPUT_PATH value: /workspace/output - name: WORKSPACE_SSH_DIRECTORY_BOUND value: "false" - name: WORKSPACE_SSH_DIRECTORY_PATH - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND value: "false" - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH - name: WORKSPACE_SSL_CA_DIRECTORY_BOUND value: "false" - name: WORKSPACE_SSL_CA_DIRECTORY_PATH image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.29.0 name: clone script: | #!/usr/bin/env sh set -eu if [ "${PARAM_VERBOSE}" = "true" ] ; then set -x fi if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials" cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig" chmod 400 "${PARAM_USER_HOME}/.git-credentials" chmod 400 "${PARAM_USER_HOME}/.gitconfig" fi if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" = "true" ] ; then cp -R "${WORKSPACE_SSH_DIRECTORY_PATH}" "${PARAM_USER_HOME}"/.ssh chmod 700 "${PARAM_USER_HOME}"/.ssh chmod -R 400 "${PARAM_USER_HOME}"/.ssh/* fi if [ "${WORKSPACE_SSL_CA_DIRECTORY_BOUND}" = "true" ] ; then export GIT_SSL_CAPATH="${WORKSPACE_SSL_CA_DIRECTORY_PATH}" if [ "${PARAM_CRT_FILENAME}" != "" ] ; then export GIT_SSL_CAINFO="${WORKSPACE_SSL_CA_DIRECTORY_PATH}/${PARAM_CRT_FILENAME}" fi fi CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" cleandir() { # Delete any existing contents of the repo directory if it exists. # # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/" # or the root of a mounted volume. if [ -d "${CHECKOUT_DIR}" ] ; then # Delete non-hidden files and directories rm -rf "${CHECKOUT_DIR:?}"/* # Delete files and directories starting with . but excluding .. rm -rf "${CHECKOUT_DIR}"/.[!.]* # Delete files and directories starting with .. plus any other character rm -rf "${CHECKOUT_DIR}"/..?* fi } if [ "${PARAM_DELETE_EXISTING}" = "true" ] ; then cleandir fi test -z "${PARAM_HTTP_PROXY}" || export HTTP_PROXY="${PARAM_HTTP_PROXY}" test -z "${PARAM_HTTPS_PROXY}" || export HTTPS_PROXY="${PARAM_HTTPS_PROXY}" test -z "${PARAM_NO_PROXY}" || export NO_PROXY="${PARAM_NO_PROXY}" /ko-app/git-init \ -url="${PARAM_URL}" \ -revision="${PARAM_REVISION}" \ -refspec="${PARAM_REFSPEC}" \ -path="${CHECKOUT_DIR}" \ -sslVerify="${PARAM_SSL_VERIFY}" \ -submodules="${PARAM_SUBMODULES}" \ -depth="${PARAM_DEPTH}" \ -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}" cd "${CHECKOUT_DIR}" RESULT_SHA="$(git rev-parse HEAD)" EXIT_CODE="$?" if [ "${EXIT_CODE}" != 0 ] ; then exit "${EXIT_CODE}" fi printf "%s" "${RESULT_SHA}" > "/tekton/results/commit" printf "%s" "${PARAM_URL}" > "/tekton/results/url" securityContext: runAsNonRoot: true runAsUser: 65532 workspaces: - description: The git repo will be cloned onto the volume backing this Workspace. name: output - description: | A .ssh directory with private key, known_hosts, config, etc. Copied to the user's home before git commands are executed. Used to authenticate with the git remote when performing the clone. Binding a Secret to this Workspace is strongly recommended over other volume types. name: ssh-directory optional: true - description: | A Workspace containing a .gitconfig and .git-credentials file. These will be copied to the user's home before any git commands are run. Any other files in this Workspace are ignored. It is strongly recommended to use ssh-directory over basic-auth whenever possible and to bind a Secret to this Workspace over other volume types. name: basic-auth optional: true - description: | A workspace containing CA certificates, this will be used by Git to verify the peer with when fetching or pushing over HTTPS. name: ssl-ca-directory optional: true ```https://github.com/tektoncd-catalog/git-clone/commit/2298f211d1d160ec45c497cb2d719d0794383ba4 is the imposter commit. It really comes from https://github.com/wlynch/catalog-git-clone/commit/2298f211d1d160ec45c497cb2d719d0794383ba4
Additional Info
Possible fixes