Skip to content

watcher: git-auth secret AlreadyExists path reuses and adopts a secret it does not own #2956

Description

@chmouel

Spotted while chasing #2955, which turned out to be a logging defect. This one is real, though it stops at the namespace boundary.

When CreateSecret returns AlreadyExists, we reuse whatever secret already carries that name:

https://github.com/tektoncd/pipelines-as-code/blob/main/pkg/reconciler/reconciler.go#L306-L318

ee9da52 added that branch for a genuine case: slow etcd makes the API server retry a create for the same PipelineRun. But it matches on the name alone. It never checks that the existing secret belongs to this PipelineRun or this Repository.

We then call UpdateSecretWithOwnerRef on it, which replaces the owner references wholesale:

https://github.com/tektoncd/pipelines-as-code/blob/main/pkg/kubeinteraction/secrets.go#L52

The first consequence is a run bound to a secret it does not own. Secret names are pac-gitauth- plus 6 random letters, lowercased by GenerateBasicAuthSecretName, so the space is 26^6. On a collision inside a namespace, a PipelineRun for repo A proceeds with the token minted for repo B and the only signal is a warning that carries the wrong context (#2955).

The second is worse: PaC can be talked into adopting an arbitrary secret. The reconciler acts on annotations that sit on the PipelineRun, so anyone who can create a PipelineRun in a namespace can write:

metadata:
  annotations:
    pipelinesascode.tekton.dev/repository: <an existing Repository CR in that namespace>
    pipelinesascode.tekton.dev/git-auth-secret: <any existing secret in that namespace>
    pipelinesascode.tekton.dev/secret-created: "false"

CreateSecret fails with AlreadyExists, the reuse branch tolerates it, and UpdateSecretWithOwnerRef stamps that unrelated secret with an owner reference to the attacker's PipelineRun. Delete the PipelineRun and the secret gets garbage collected with it. CleanupPipelines deletes by the same annotation value:

https://github.com/tektoncd/pipelines-as-code/blob/main/pkg/kubeinteraction/cleanups.go#L52-L59

It needs create-PipelineRun rights in the target namespace, so no cross-tenant escape. Still a destructive primitive we hand out for free, and it applies to the PaC install namespace too wherever users can create PipelineRuns there.

Suggested fix

Reuse the existing secret only when it provably belongs to this PipelineRun or Repository: owner UID matching the PipelineRun, or matching pipelinesascode.tekton.dev/url-org, url-repository and sha annotations. That covers the etcd-retry case the branch was written for and rejects the rest, which should then fail closed with a Warning Event on the Repository CR instead of running with someone else's credential.

UpdateSecretWithOwnerRef should also stop flattening OwnerReferences, and refuse a secret already owned by another object.

While in there: GenerateBasicAuthSecretName lowercases the generated string, dropping the space from 52^6 to 26^6 for nothing.

Goes back at least to v0.48.1 (reconciler.go:226-238 and kubeinteraction/secrets.go:51 there).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsecurity

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions