In-toto attestation formatter

Overview

The in-toto attestation spec is defined here.

In-toto attestations can be generated for TaskRuns or PipelineRuns. Tekton Chains generates in-toto attestations with the slsa-provenance predicate format.

Standard in-toto predicate

The in-toto format can be enabled by running:

# For TaskRuns
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"artifacts.taskrun.format": "in-toto"}}'
# For PipelineRuns
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{"artifacts.pipelinerun.format": "in-toto"}}'

To provide a git URL/commit as material, add a parameter named CHAINS-GIT_COMMIT and CHAINS-GIT_URL. The value of these parameters should be fed by some VCS task (e.g like this task). A PipeLine example where another task checkout has URL/commit as task results:

    - name: build
      params:
        - name: CHAINS-GIT_COMMIT
          value: "$(tasks.checkout.results.commit)"
        - name: CHAINS-GIT_URL
          value: "$(tasks.checkout.results.url)"

Alternatively, CHAINS-GIT_COMMIT and CHAINS-GIT_URL can be results instead. Another Pipeline example where results are used:

spec:
  results:
    - description: Repository URL used for buiding the image.
      name: CHAINS-GIT_URL
      value: $(tasks.checkout.results.url)
    - description: Repository commit used for building the image.
      name: CHAINS-GIT_COMMIT
      value: $(tasks.checkout.results.commit)
  tasks:
  - name: checkout

Type Hinting

To capture artifacts created by a task, Chains will scan the TaskRun and PipelineRun result for a result name *_DIGEST. The result shall be a string on the format alg:digest, alg is common sha256. If the result is named Foo_DIGEST, Chains will try to find a parameter or result (in that order) with the name Foo. The parameter or result named Foo shall contain the name (reference) to the built artifact.

An example (a Task):

  params:
  - name: IMAGE
    description: Name (reference) of the image to build.
...
  results:
  - name: IMAGE_DIGEST
    description: Digest of the image just built.

So if the IMAGE parameter have the value gcr.io/test/foo and the result IMAGE_DIGEST is sha256:abcd then an attestation for the subject pkg:/docker/test/foo@sha256:abcd?repository_url=gcr.io is created. Note that image references are represented using Package URL format.

Limitations

This is an MVP implementation of the in-toto attestation format. More work would be required to properly capture the Entrypoint field in the provenance predicate, now the TaskRef’s name is used. Also metadata related to hermeticity/reproducibility are currently not populated.

Examples

Example TaskRun attestation:

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://in-toto.io/Provenance/v0.1",
  "subject": [
    {
      "name": "file-SNAPSHOT.jar",
      "digest": {
        "sha256": "3f1017b520fe358d7b3796879232cd36259066ccd5bab5466cbedb444064dfed"
      }
    }
  ],
  "predicate": {
    "builder": {
      "id": "https://configured.builder@v1"
    },
    "recipe": {
      "type": "https://tekton.dev/attestations/chains@v1",
      "definedInMaterial": 0,
      "entryPoint": "maven"
    },
    "metadata": {
      "buildStartedOn": "2021-05-11T11:05:50Z",
      "buildFinishedOn": "2021-05-11T11:15:42Z",
      "completeness": {
        "arguments": false,
        "environment": false,
        "materials": false
      },
      "reproducible": false
    },
    "materials": [
      {
        "uri": "git+https://github.com/org/repo",
        "digest": {
          "git_commit": "c4b75d454655c1755ab116947e88a59ac03e28a9"
        }
      },
      {
        "uri": "pkg:docker/alpine@sha256:69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f",
        "digest": {
          "sha256": "69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f"
        }
      },
      {
        "uri": "pkg:docker/alpine@sha256:69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f",
        "digest": {
          "sha256": "69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f"
        }
      },
      {
        "uri": "pkg:docker/org/build/openjdk-11@sha256:51aa63475b5e1e2e22d1dc416556a14658a7e03a0d3c88bb9dd7b6e3411ae34a?repository_url=gcr.io",
        "digest": {
          "sha256": "51aa63475b5e1e2e22d1dc416556a14658a7e03a0d3c88bb9dd7b6e3411ae34a"
        }
      }
    ]
  }
}

Example PipelineRun attestation:

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "subject": [
    {
      "name": "registry.example.com/minimal-container/min",
      "digest": {
        "sha256": "41a8ace7b880ae40708daa60387d2f181c41ecec667c93010294d1529d58c27e"
      }
    }
  ],
  "predicate": {
    "builder": {
      "id": "https://tekton.dev/chains/v2"
    },
    "buildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
    "invocation": {
      "configSource": {},
      "parameters": {
        "git-repo": "https://github.com/lcarva/minimal-container",
        "git-revision": "main",
        "output-image": "registry.example.com/minimal-container/min:latest"
      }
    },
    "buildConfig": {
      "tasks": [
        {
          "name": "git-clone",
          "ref": {
            "name": "git-clone",
            "kind": "Task"
          },
          "startedOn": "2022-08-29T18:42:04Z",
          "finishedOn": "2022-08-29T18:42:23Z",
          "status": "Succeeded",
          "steps": [
            {
              "entryPoint": "#!/usr/bin/env sh\nset -eu\n\nif [ \"${PARAM_VERBOSE}\" = \"true\" ] ; then\n  set -x\nfi\n\n\nif [ \"${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}\" = \"true\" ] ; then\n  cp \"${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials\" \"${PARAM_USER_HOME}/.git-credentials\"\n  cp \"${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig\" \"${PARAM_USER_HOME}/.gitconfig\"\n  chmod 400 \"${PARAM_USER_HOME}/.git-credentials\"\n  chmod 400 \"${PARAM_USER_HOME}/.gitconfig\"\nfi\n\nif [ \"${WORKSPACE_SSH_DIRECTORY_BOUND}\" = \"true\" ] ; then\n  cp -R \"${WORKSPACE_SSH_DIRECTORY_PATH}\" \"${PARAM_USER_HOME}\"/.ssh\n  chmod 700 \"${PARAM_USER_HOME}\"/.ssh\n  chmod -R 400 \"${PARAM_USER_HOME}\"/.ssh/*\nfi\n\nif [ \"${WORKSPACE_SSL_CA_DIRECTORY_BOUND}\" = \"true\" ] ; then\n   export GIT_SSL_CAPATH=\"${WORKSPACE_SSL_CA_DIRECTORY_PATH}\"\nfi\nCHECKOUT_DIR=\"${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}\"\n\ncleandir() {\n  # Delete any existing contents of the repo directory if it exists.\n  #\n  # We don't just \"rm -rf ${CHECKOUT_DIR}\" because ${CHECKOUT_DIR} might be \"/\"\n  # or the root of a mounted volume.\n  if [ -d \"${CHECKOUT_DIR}\" ] ; then\n    # Delete non-hidden files and directories\n    rm -rf \"${CHECKOUT_DIR:?}\"/*\n    # Delete files and directories starting with . but excluding ..\n    rm -rf \"${CHECKOUT_DIR}\"/.[!.]*\n    # Delete files and directories starting with .. plus any other character\n    rm -rf \"${CHECKOUT_DIR}\"/..?*\n  fi\n}\n\nif [ \"${PARAM_DELETE_EXISTING}\" = \"true\" ] ; then\n  cleandir\nfi\n\ntest -z \"${PARAM_HTTP_PROXY}\" || export HTTP_PROXY=\"${PARAM_HTTP_PROXY}\"\ntest -z \"${PARAM_HTTPS_PROXY}\" || export HTTPS_PROXY=\"${PARAM_HTTPS_PROXY}\"\ntest -z \"${PARAM_NO_PROXY}\" || export NO_PROXY=\"${PARAM_NO_PROXY}\"\n\n/ko-app/git-init \\\n  -url=\"${PARAM_URL}\" \\\n  -revision=\"${PARAM_REVISION}\" \\\n  -refspec=\"${PARAM_REFSPEC}\" \\\n  -path=\"${CHECKOUT_DIR}\" \\\n  -sslVerify=\"${PARAM_SSL_VERIFY}\" \\\n  -submodules=\"${PARAM_SUBMODULES}\" \\\n  -depth=\"${PARAM_DEPTH}\" \\\n  -sparseCheckoutDirectories=\"${PARAM_SPARSE_CHECKOUT_DIRECTORIES}\"\ncd \"${CHECKOUT_DIR}\"\nRESULT_SHA=\"$(git rev-parse HEAD)\"\nEXIT_CODE=\"$?\"\nif [ \"${EXIT_CODE}\" != 0 ] ; then\n  exit \"${EXIT_CODE}\"\nfi\nprintf \"%s\" \"${RESULT_SHA}\" > \"$(results.commit.path)\"\nprintf \"%s\" \"${PARAM_URL}\" > \"$(results.url.path)\"\n",
              "arguments": null,
              "environment": {
                "container": "clone",
                "image": "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:45dca0972541546d3625d99ee8a8fbcc768b01fc9c199d1251ebd7dfd1b8874c"
              },
              "annotations": null
            }
          ],
          "invocation": {
            "configSource": {},
            "parameters": {
              "deleteExisting": "true",
              "depth": "1",
              "gitInitImage": "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.29.0",
              "httpProxy": "",
              "httpsProxy": "",
              "noProxy": "",
              "refspec": "",
              "revision": "main",
              "sparseCheckoutDirectories": "",
              "sslVerify": "true",
              "subdirectory": "",
              "submodules": "true",
              "url": "https://github.com/lcarva/minimal-container",
              "userHome": "/tekton/home",
              "verbose": "true"
            }
          },
          "results": [
            {
              "name": "commit",
              "value": "89dedecaca1b85346600c7db9939a4fe090a42ef"
            },
            {
              "name": "url",
              "value": "https://github.com/lcarva/minimal-container"
            }
          ]
        },
        {
          "name": "source-security-scan",
          "after": [
            "git-clone"
          ],
          "ref": {
            "name": "trivy-scanner",
            "kind": "Task",
            "bundle": "gcr.io/tekton-releases/catalog/upstream/trivy-scanner:0.1"
          },
          "startedOn": "2022-08-29T18:42:24Z",
          "finishedOn": "2022-08-29T18:42:40Z",
          "status": "Succeeded",
          "steps": [
            {
              "entryPoint": "#!/usr/bin/env sh\n  cmd=\"trivy $* $(params.IMAGE_PATH)\"\n  echo \"Running trivy task with command below\"\n  echo \"$cmd\"\n  eval \"$cmd\"\n",
              "arguments": [
                "$(params.ARGS)"
              ],
              "environment": {
                "container": "trivy-scan",
                "image": "docker.io/aquasec/trivy@sha256:dea76d4b50c75125cada676a87ac23de2b7ba4374752c6f908253c3b839201d9"
              },
              "annotations": null
            }
          ],
          "invocation": {
            "configSource": {},
            "parameters": {
              "ARGS": [
                "filesystem"
              ],
              "IMAGE_PATH": ".",
              "TRIVY_IMAGE": "docker.io/aquasec/trivy@sha256:dea76d4b50c75125cada676a87ac23de2b7ba4374752c6f908253c3b839201d9"
            }
          }
        },
        {
          "name": "image-build",
          "after": [
            "source-security-scan"
          ],
          "ref": {
            "name": "buildah",
            "kind": "ClusterTask"
          },
          "startedOn": "2022-08-29T18:42:41Z",
          "finishedOn": "2022-08-29T18:43:03Z",
          "status": "Succeeded",
          "steps": [
            {
              "entryPoint": "[[ \"$(workspaces.sslcertdir.bound)\" == \"true\" ]] && CERT_DIR_FLAG=\"--cert-dir $(workspaces.sslcertdir.path)\"\nbuildah ${CERT_DIR_FLAG} --storage-driver=$(params.STORAGE_DRIVER) bud \\\n  $(params.BUILD_EXTRA_ARGS) --format=$(params.FORMAT) \\\n  --tls-verify=$(params.TLSVERIFY) --no-cache \\\n  -f $(params.DOCKERFILE) -t $(params.IMAGE) $(params.CONTEXT)\n",
              "arguments": null,
              "environment": {
                "container": "build",
                "image": "quay.io/buildah/stable@sha256:0ceadda5ead6601f347a801c935e668888a72ff858ef0c7b826aca10273f9a77"
              },
              "annotations": null
            },
            {
              "entryPoint": "[[ \"$(params.SKIP_PUSH)\" == \"true\" ]] && echo \"Push skipped\" && exit 0\n[[ \"$(workspaces.sslcertdir.bound)\" == \"true\" ]] && CERT_DIR_FLAG=\"--cert-dir $(workspaces.sslcertdir.path)\"\nbuildah ${CERT_DIR_FLAG} --storage-driver=$(params.STORAGE_DRIVER) push \\\n  $(params.PUSH_EXTRA_ARGS) --tls-verify=$(params.TLSVERIFY) \\\n  --digestfile $(workspaces.source.path)/image-digest $(params.IMAGE) \\\n  docker://$(params.IMAGE)\n",
              "arguments": null,
              "environment": {
                "container": "push",
                "image": "quay.io/buildah/stable@sha256:0ceadda5ead6601f347a801c935e668888a72ff858ef0c7b826aca10273f9a77"
              },
              "annotations": null
            },
            {
              "entryPoint": "cat \"$(workspaces.source.path)\"/image-digest | tee $(results.IMAGE_DIGEST.path)\necho \"$(params.IMAGE)\" | tee $(results.IMAGE_URL.path)\n",
              "arguments": null,
              "environment": {
                "container": "digest-to-results",
                "image": "quay.io/buildah/stable@sha256:0ceadda5ead6601f347a801c935e668888a72ff858ef0c7b826aca10273f9a77"
              },
              "annotations": null
            }
          ],
          "invocation": {
            "configSource": {},
            "parameters": {
              "BUILDER_IMAGE": "quay.io/buildah/stable:v1.18.0",
              "BUILD_EXTRA_ARGS": "",
              "CONTEXT": ".",
              "DOCKERFILE": "./Dockerfile",
              "FORMAT": "oci",
              "IMAGE": "registry.example.com/minimal-container/min:latest",
              "PUSH_EXTRA_ARGS": "",
              "SKIP_PUSH": "false",
              "STORAGE_DRIVER": "vfs",
              "TLSVERIFY": "true"
            }
          },
          "results": [
            {
              "name": "IMAGE_DIGEST",
              "value": "sha256:41a8ace7b880ae40708daa60387d2f181c41ecec667c93010294d1529d58c27e"
            },
            {
              "name": "IMAGE_URL",
              "value": "registry.example.com/minimal-container/min:latest\n"
            }
          ]
        },
        {
          "name": "image-security-scan",
          "after": [
            "image-build"
          ],
          "ref": {
            "name": "trivy-scanner",
            "kind": "Task",
            "bundle": "gcr.io/tekton-releases/catalog/upstream/trivy-scanner@sha256:e4c2916f25ce2d42ec7016c3dc3392e527442c307f43aae3ea63f4622ee5cfe4"
          },
          "startedOn": "2022-08-29T18:43:03Z",
          "finishedOn": "2022-08-29T18:43:14Z",
          "status": "Succeeded",
          "steps": [
            {
              "entryPoint": "#!/usr/bin/env sh\n  cmd=\"trivy $* $(params.IMAGE_PATH)\"\n  echo \"Running trivy task with command below\"\n  echo \"$cmd\"\n  eval \"$cmd\"\n",
              "arguments": [
                "$(params.ARGS)"
              ],
              "environment": {
                "container": "trivy-scan",
                "image": "docker.io/aquasec/trivy@sha256:dea76d4b50c75125cada676a87ac23de2b7ba4374752c6f908253c3b839201d9"
              },
              "annotations": null
            }
          ],
          "invocation": {
            "configSource": {},
            "parameters": {
              "ARGS": [
                "image"
              ],
              "IMAGE_PATH": "registry.example.com/minimal-container/min:latest\n",
              "TRIVY_IMAGE": "docker.io/aquasec/trivy@sha256:dea76d4b50c75125cada676a87ac23de2b7ba4374752c6f908253c3b839201d9"
            }
          }
        }
      ]
    },
    "metadata": {
      "buildStartedOn": "2022-08-29T18:42:04Z",
      "buildFinishedOn": "2022-08-29T18:43:14Z",
      "completeness": {
        "parameters": false,
        "environment": false,
        "materials": false
      },
      "reproducible": false
    },
    "materials": [
      {
        "uri": "git+https://github.com/lcarva/minimal-container.git",
        "digest": {
          "sha1": "89dedecaca1b85346600c7db9939a4fe090a42ef"
        }
      }
    ]
  }
}