09

Remote resolution

So far every taskRef named a Task already applied to the namespace. With remote resolution, a reference names a resolver instead (git, bundles, hub, cluster or http) plus a few params, and Tekton fetches the definition when the run starts. This chapter follows one fetch through the ResolutionRequest, tours each resolver, and then asks the question that matters for a supply chain: when the remote changes, which version do your runs get, and how do you know it is the one you approved?
The Pipelines controller does not fetch anything itself. It writes a ResolutionRequest, a resolver controller fulfils it, and the result comes back base64-encoded in the request's status. Only then is the Task decoded, validated and turned into a Pod.
inject

who does what

stage 1/6
watchstatuscreateread datawatchstatus.datafetchcreateTaskRunclone-runPipelines controllertekton-pipelinesPodclone-run-podResolutionRequesttype=gitgit resolvertekton-pipelines-resolversgithub.comtektoncd/catalog
1. TaskRun created

kubectl create -f taskrun.yaml. The spec has a taskRef with a resolver, not a Task name, so there is nothing to run yet.

TaskRun reason
Pending
ResolutionRequest
—
status.data
—
the whole file, base64 in the CR status
Pod
none
TaskRun
apiVersion: tekton.dev/v1
kind: TaskRun
metadata: { name: clone-run }
spec:
taskRef:
resolver: git
params:
- { name: url, value: github.com/tektoncd/catalog.git }
- { name: revision, value: main }
- { name: pathInRepo, value: task/git-clone/0.9/git-clone.yaml }
status:
conditions:
- type: Succeeded
status: "Unknown"
reason: Pending
ResolutionRequest
# no ResolutionRequest yet
base64 -d status.data
# waiting for status.data

Try: step through once and watch the work pass from the Pipelines controller to the resolver and back through the ResolutionRequest. Then inject “repo not found”: the error stops at the request, the TaskRun fails, and no Pod is ever created.

Every resolver takes the same shape of input, a list of params, and returns the same output, one YAML document. What differs is where it looks, which params it needs and how it can fail.
mode
revision
pathInRepo
TaskRun.spec · git resolver
taskRef:
resolver: git
params:
- { name: url, value: https://github.com/tektoncd/catalog.git }
- { name: revision, value: main }
- { name: pathInRepo, value: task/git-clone/0.9/git-clone.yaml }

resolution path

resolved
  1. git resolver
  2. →git clone (anonymous, shallow)
  3. →checkout main
  4. →read git-clone.yaml

resolved; refSource.digest.sha1 = a339fc519093…

defaults from ConfigMap git-resolver-config: default-revision, fetch-timeout, default-url, scm-type, server-url, api-token-secret-name, default-org

about the git resolver

  • Anonymous mode does a shallow clone of url and reads one file at revision. Simple, but every resolution is a clone.
  • SCM API mode (org + repo) reads the file through the provider API with a token from a Secret. Required for private repos without clone credentials, and subject to API rate limits.
  • revision can be a branch, a tag or a commit SHA. Only the SHA is immutable; status.provenance.refSource always records the SHA that was actually used.

Try: in bundles, ask for name “ci” and see that a Pipeline layer doesn’t satisfy a Task request. In cluster, pick kube-system: the resolver refuses it because of blocked-namespaces. In git, flip to the SCM API mode and note the token Secret it now needs.

The same YAML can run different code from week to week. A branch follows every merge, and a tag is a pointer someone can move. A commit SHA or an image digest names the content itself. Scrub through twelve weeks of upstream changes and compare what each style of reference actually ran.

upstream repo: tektoncd-style task on main

HEAD = e0bad11
w0w1w2w3w4w5w6w7w8w9w10w11w12A3f2a9c1B81d07beCc45e210Me0bad11mainv0.9

A (3f2a9c1): the version you tested · B (81d07be): new optional param · C (c45e210): param renamed: runs fail · M (e0bad11): step now exfiltrates secrets

what each weekly PipelineRun actually ran

git revision: main
AAABBCCCMMMMM
git revision: v0.9
AAAAAAAAAAAAA
git revision: <commit sha>
AAAAAAAAAAAAA
bundle :0.9 (tag)
AAAAAAAAAAAAA
bundle @sha256 (digest)
AAAAAAAAAAAAA
A: tested versiondrifted, harmlessbreaking changemalicious
refs still reproducible
4/5
every run so far used version A
runs that hit a breaking change
3
runs that ran malicious code
5
supply-chain exposure
refSource on week 12 (main)
e0bad11
recorded in status.provenance, so you can audit drift after the fact

Try: play the weeks with “upstream compromised” on. revision: main runs every change the moment it lands, including the malicious one. Then turn on “tag force-moved”: tags look pinned but are just movable pointers. Only the commit SHA and the bundle digest stay on version A.

Resolution happens with the controller's and resolver's access, and the resolved Task runs with your ServiceAccount and Secrets. Pointing a release pipeline at someone else's main branch hands them a step in it.

Trusted resources: verify before you run

trusted-resourcesverification-policy
Pinning fixes which bytes you get, not who wrote them. With trusted resources, a publisher signs the Task, and the signature goes in the tekton.dev/signature annotation. A VerificationPolicy says which public key must have signed resources from which sources. The controller checks after resolution and before creating the Pod.
Task signed with
mode
no-match policy
Task fetched by the resolver
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: build
annotations:
tekton.dev/signature: ZjI3ZDg1M2Q3ODY1MjNhMDhjNzAzMTgzN2I2ZTE1ZDZk
spec:
steps:
- name: build
image: gcr.io/kaniko-project/executor:v1.23.2
args: ["--destination=$(params.IMAGE)"]
VerificationPolicy
apiVersion: tekton.dev/v1alpha1
kind: VerificationPolicy
metadata: { name: acme-tasks, namespace: ci }
spec:
resources:
- pattern: "https://github.com/acme/tasks.git"
authorities:
- name: acme-release-key
key:
secretRef: { name: acme-pubkey, namespace: ci }
hashAlgorithm: sha256
mode: enforce

Pipelines controller verifies before running

  1. ✓policy matches source git+https://github.com/acme/tasks.git
  2. ✓signature annotation present; strip it, then hash the rest (sha256)
  3. ✓signature verifies with the policy's public key
sha256(spec) ≈ 4d385af19924730224cd80b7…
TaskRun
runs
TrustedResourcesVerified
True
verified

Try: tamper with the Task. The signature no longer matches and enforce mode fails the TaskRun before a Pod exists. Switch to warn and it runs, flagged with TrustedResourcesVerified=False. Then make the pattern not match: now the no-match feature flag decides, and its default (ignore) lets the unverified Task run.

Configuration, defaults and load

resolver-configresolver-feature-flags
Each resolver reads a ConfigMap in the tekton-pipelines-resolvers namespace for its defaults and limits, and a shared feature-flags ConfigMap decides which resolvers are on. Every PipelineRun resolves every remote ref again, so at scale the remote's rate limits and latency start to matter.
default-revision
resolver ConfigMaps
# ns tekton-pipelines-resolvers
kind: ConfigMap
metadata: { name: resolvers-feature-flags }
data:
enable-git-resolver: "true"
enable-bundles-resolver: "true"
enable-hub-resolver: "true"
enable-cluster-resolver: "true"
enable-http-resolver: "true"
---
kind: ConfigMap
metadata: { name: git-resolver-config }
data:
default-revision: main
fetch-timeout: 1m
scm-type: github
api-token-secret-name: ""
api-token-secret-key: token
default-org: acme
TaskRun (relies on defaults)
taskRef:
resolver: git
params:
- { name: repo, value: tasks } # org from default-org
- { name: pathInRepo, value: build.yaml }
# revision omitted

effective request the resolver sees

org/repoacme/tasks ← default-org
revisionmain ← default-revision
pathInRepobuild.yaml
deadline60s ← fetch-timeout

load on the remote: every run resolves every remote ref

git mode
fetches / hour
40
runs × refs
API calls vs limit
40/60
GitHub REST, per hour
fetches over fetch-timeout
0.0%
exponential latency model
PipelineRuns that fail to resolve
0.0%

Try: in “API, no token” mode push PipelineRuns/hour past ~15 with 4 refs and watch runs fail on the rate limit; adding a token Secret fixes it. Turn latency up and fetch-timeout down to see resolution timeouts. Pin to SHAs and enable the cache: fetches drop to one per distinct ref.

You rarely write a clone or image-build Task yourself. The community catalog has versioned, tested Tasks such as git-clone, buildah and kaniko, and any of the resolvers above can fetch them.
version
reference via

tektoncd/catalog

  • git-clone.yaml
    README.md
    samples/ · tests/

git-clone 0.9

Git

Clones a repo into the output workspace and reports the commit it checked out.

params
urlrevisionrefspecsubmodulesdepthsslVerifysubdirectorydeleteExisting
workspaces
outputssh-directory?basic-auth?ssl-ca-directory?
results
commiturlcommitter-date
annotations:
tekton.dev/pipelines.minVersion: "0.38.0"
tekton.dev/categories: Git
tekton.dev/platforms: linux/amd64,linux/arm64
pipeline.yaml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata: { name: ci }
spec:
tasks:
- name: fetch
taskRef:
resolver: hub
params:
- { name: catalog, value: tekton-catalog-tasks }
- { name: type, value: artifact }
- { name: kind, value: task }
- { name: name, value: git-clone }
- { name: version, value: "0.9" }
params:
- { name: url, value: $(params.repo-url) }
workspaces:
- { name: output, workspace: shared }

Try: pick a task and switch the reference method. Hub is the shortest; git with a commit SHA and bundles by digest are the ones you can pin. Each catalog version is its own directory, so upgrading is an explicit change in your Pipeline, never a surprise.

Tekton Hub (hub.tekton.dev) is deprecated. The catalog is listed on Artifact Hub, which is why the hub resolver now defaults to type: artifact. Some popular Tasks, git-clone among them, are also maintained in their own repositories under the tektoncd-catalog GitHub org, released as versioned bundles.

Publishing your own bundle

tekton-bundletkn-bundle
tkn bundle push turns YAML files into an OCI image, one layer per resource, and prints the digest. tkn bundle list reads the layer annotations back. After that it is an ordinary registry artifact: tags move, digests do not.
tag
terminal
# pick files, then push

ghcr.io/acme/ci-bundle:v1

Not pushed yet.

registry: every pushed digest

empty

Layer digest of build: sha256:537d768135adc1e615a…

Try: push to :v1, then edit build.yaml and push to :v1 again. The tag moves to a new manifest digest, but the old digest is still in the registry. A pipeline that references the bundle by @sha256 keeps getting the old version. Uncheck everything and push to see the validation error.

  • takeawayA remote ref is a request, not a lookup. The controller creates a ResolutionRequest, a resolver fills status.data, and only after decoding and validation does a Pod exist. Fetch failures show up as TaskRunResolutionFailed before anything runs.
  • takeawayThe reference decides reproducibility. Branches and tags drift; commit SHAs and bundle digests don't. status.provenance.refSource records what was actually used either way.
  • takeawayPin for sameness and sign for authorship. A VerificationPolicy in enforce mode rejects a Task that was modified or signed by the wrong key, but sources that match no policy follow the no-match flag, which defaults to ignore.

Key concepts · 19

book chapter 9
  1. Remote resolution

    Fetching a Task or Pipeline definition from outside the cluster at run time, by naming a resolver and its params in taskRef/pipelineRef.

    It lets teams share versioned Tasks without copying YAML into every namespace. The controller stays generic, and each source type is a pluggable resolver.

    taskRef.resolverpipelineRef.resolvertaskRef.params
    ↑ see it in the visualization
  2. Resolver

    A controller that watches ResolutionRequests carrying its type label, fetches the requested resource and writes it back into the request's status.

    Resolvers are decoupled from the Pipelines controller: built-in ones ship in the tekton-pipelines-resolvers namespace, and you can write your own with the resolver framework.

    tekton-pipelines-remote-resolversresolution.tekton.dev/type
    ↑ see it in the visualization
  3. ResolutionRequest

    A CRD (resolution.tekton.dev/v1beta1) the Pipelines controller creates per remote ref: spec.params in, status.data (base64) and status.refSource out.

    It is the contract between controller and resolver, and it is inspectable: kubectl get resolutionrequests shows what was asked, what came back, and why a resolution failed.

    status.datastatus.refSourcekubectl get resolutionrequests
    ↑ see it in the visualization
  4. refSource provenance

    The uri, digest and entryPoint of the fetched resource, copied into TaskRun/PipelineRun status.provenance.refSource.

    Even when you reference a moving branch, the run records the exact commit it used. That is how you audit drift afterwards, and what Tekton Chains puts into provenance.

    status.provenance.refSource.digest.sha1
    ↑ see it in the visualization
  5. Git resolver

    Fetches one file (pathInRepo) at a revision, either by anonymous clone of url or through the SCM provider API using org/repo and a token Secret.

    The most common way to keep Tasks next to the code that uses them. Clone mode is simple; API mode works with private repos but counts against provider rate limits.

    url / revision / pathInRepoorg / repo / token / scmTypegit-resolver-config
    ↑ see it in the visualization
  6. Bundles resolver

    Pulls an OCI image (bundle param) and returns the layer whose annotations match the requested kind and name.

    Registries give you immutable digests, replication and access control for free, so bundles are the easiest way to pin and distribute Tasks.

    bundle / name / kind / secretbundleresolver-config
    ↑ see it in the visualization
  7. Tekton Bundle

    An OCI image in which each layer holds one Tekton resource, identified by the annotations dev.tekton.image.kind, dev.tekton.image.name and dev.tekton.image.apiVersion.

    Packing a Pipeline and its Tasks into one image versions them together; referencing it by @sha256 digest gives you exactly the bytes you tested.

    dev.tekton.image.kinddev.tekton.image.name
    ↑ see it in the visualization
  8. tkn bundle push / list

    CLI commands that build a Tekton Bundle from YAML files and push it to a registry, and list the resources inside an existing bundle.

    They make bundles a normal artifact in your release process. Pushing to the same tag moves the tag, so consumers should use the printed digest.

    tkn bundle push ghcr.io/acme/b:v1 -f task.yamltkn bundle list ghcr.io/acme/b:v1
    ↑ see it in the visualization
  9. Hub resolver

    Fetches a catalog Task or Pipeline by catalog, kind, name and version from Artifact Hub (type: artifact) or the older Tekton Hub API (type: tekton).

    The shortest way to use community Tasks such as git-clone or buildah, at the price of trusting the hub and the publisher at fetch time.

    type / catalog / kind / name / versionhubresolver-config
    ↑ see it in the visualization
  10. Artifact Hub

    The CNCF package index (artifacthub.io) where the Tekton catalog is now published, as tekton-task and tekton-pipeline packages.

    Tekton Hub (hub.tekton.dev) is deprecated; the hub resolver defaults to type artifact and the tekton-catalog-tasks catalog.

    artifacthub.iotekton-catalog-tasks
    ↑ see it in the visualization
  11. Cluster resolver

    Fetches a Task or Pipeline from another namespace of the same cluster by kind, name and namespace, subject to allowed and blocked namespace lists.

    It replaces the deprecated ClusterTask: a platform team keeps shared Tasks in one namespace, and the resolver config controls who can read from where.

    kind / name / namespaceblocked-namespacesClusterTask (deprecated)
    ↑ see it in the visualization
  12. HTTP resolver

    Fetches a resource from any HTTPS URL, with optional basic auth from a Secret.

    Handy for internal file servers, but a URL is not a version: unless the URL itself is immutable, the content can change under you.

    urlhttp-usernamehttp-password-secret
    ↑ see it in the visualization
  13. Pinning vs drift

    Referencing an immutable identifier (commit SHA, image digest) instead of a moving one (branch, tag) so every run resolves the same bytes.

    Branches change on every merge and tags can be force-moved, so unpinned refs silently pick up breaking or malicious changes. Pinning makes runs reproducible; upgrades become explicit commits.

    revision: <sha>bundle: img@sha256:…
    ↑ see it in the visualization
  14. Trusted resources

    A Pipelines feature that verifies a signature on remotely resolved Tasks and Pipelines before running them.

    Pinning tells you which bytes you got; a signature tells you who published them. Together they close the gap between “the file I resolved” and “the file my release team approved”.

    tekton.dev/signaturetrusted-resources-verification-no-match-policy
    ↑ see it in the visualization
  15. VerificationPolicy

    A namespaced CRD mapping source patterns (regexes over the resource's source URI) to authorities: public keys from inline data, a Secret or KMS, with mode enforce or warn.

    enforce fails the run on a bad or missing signature; warn runs it but sets TrustedResourcesVerified=False. Sources that match no policy fall back to the no-match feature flag (ignore, warn or fail).

    spec.resources[].patternspec.authorities[].keyspec.mode
    ↑ see it in the visualization
  16. Resolver ConfigMaps

    Per-resolver defaults and limits in the resolvers namespace (git-resolver-config, bundleresolver-config, hubresolver-config, cluster-resolver-config, http-resolver-config).

    Defaults such as default-revision or default-org keep TaskRuns short, and fetch-timeout bounds how long a slow remote can hold up a run.

    default-revisionfetch-timeoutdefault-kind
    ↑ see it in the visualization
  17. resolvers-feature-flags

    The ConfigMap with enable-git-resolver, enable-bundles-resolver, enable-hub-resolver, enable-cluster-resolver and enable-http-resolver.

    Operators can switch off resolvers they don't want users to reach; a disabled resolver makes every request of that type fail.

    enable-git-resolverenable-http-resolver
    ↑ see it in the visualization
  18. Tekton Catalog

    The community collection of reusable Tasks and Pipelines (tektoncd/catalog), one directory per name and version, each with a README, samples and tests.

    Versions are directories, so a new version never changes an old one. The tekton.dev/pipelines.minVersion annotation says which Pipelines release a version needs.

    task/<name>/<version>/<name>.yamltekton.dev/pipelines.minVersionbuildahkaniko
    ↑ see it in the visualization
  19. git-clone Task

    The most used catalog Task: clones url at revision into the output workspace and emits commit and url results.

    Almost every pipeline starts with it, and its commit result is what downstream tasks and provenance use to say exactly which source was built.

    workspaces.outputresults.commitparams.revision
    ↑ see it in the visualization