Skip to content

Narrow provider package signing refs to release tags once packages publish from a tag #3162

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence. Both verification rules currently pin the Crossplane provider package identity to the default branch:

  • k8s/bases/infrastructure/cluster-policies/best-practices/verify-app-images.yaml (Kyverno, admission)
  • talos/cluster/verify-first-party-images.yaml (Talos, host-level image pull)

each matching …/publish-provider-package\.yml@refs/heads/main$. A cosign verify against the package the production cluster runs today confirms that is the SAN the signature actually carries.

Audience and problem. This closes the original vulnerability — the ref was previously unconstrained (@.+$), which accepted a package signed from any branch or PR. But refs/heads/main is still a mutable ref, so the signature attests "built by our workflow at some point on main" rather than "this is release vX.Y.Z". That is a weaker provenance claim than the toolchain can give, and it is the residual hardening gap.

Why this is blocked, not merely unstarted. The narrowing cannot lead the publisher. publish-provider-package.yml in provider-upjet-unifi is workflow_dispatch-only, so it signs as refs/heads/main; narrowing these rules first would reject the deployed package at admission and at image pull on the nodes — the more damaging of the two, since it is a node-level ImagePullBackOff rather than a rejected manifest. The publisher must move first: devantler-tech/provider-upjet-unifi#35.

Acceptance criteria.

  • provider-upjet-unifi#35 is delivered and a re-published package verifies with a refs/tags/vX.Y.Z SAN.
  • Both regexes above are narrowed to …/publish-provider-package\.yml@refs/tags/v[0-9].+$.
  • Verified behaviourally, not just statically: cosign verify against the deployed package passes under the new regex, and the provider pod is admitted and pulls on a node.
  • A negative control confirms a branch-ref signature is now rejected.

Size. Small — two one-line regex changes, but only after the upstream publisher change, and gated on a real signature check rather than a regex smoke test.

History. #2706 attempted this narrowing ahead of the publisher and was closed as unmergeable; its branch also predated the current @refs/heads/main policy, so resolving its conflict would have silently reverted main. This is a fresh two-line change from current main, not a revival of that branch.


Blocker: #3421 | last-verified 2026-08-28: still blocking — the stated
blocker provider-upjet-unifi#35 is DELIVERED (closed 2026-08-27; the publisher now triggers on
push: tags: v* and rejects a non-tag ref, and v1.0.0 published from its tag). What remains is that
the deployed package is still the branch-signed v0.1.0. Measured with cosign verify, each cell
controlling the other:

package current …@refs/heads/main$ proposed …@refs/tags/v[0-9].+$
v0.1.0 (deployed) PASS FAIL
v1.0.0 (tag-signed) FAIL PASS

The bottom-left cell is this issue's own warning, measured rather than predicted: narrowing now would
reject the deployed provider at admission and at node-level image pull. #3421 moves the pin and is
open as #3422; once it has rolled out and the provider is observed healthy, the two regexes here can
be narrowed and the negative control in the acceptance criteria is already satisfied by the
top-right cell.

Activity

  1. moved this to 📥 Backlog in 🌊 Project Boardon Aug 16, 2026
  2. coderabbitai commented on Aug 16, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #2704 - fix(aws): constrain eks-ci KMS grant creation [merged]
    #2707 - fix(unifi): prevent pruning managed network resources [merged]
    #2721 - fix(kyverno): scope Flagger primary mutation RBAC [merged]
    #3103 - feat(security): assert the node image verifier can enforce at all [merged]
    #3109 - fix(deploy): avoid redundant GHCR node reboots [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. added a commit that references this issue on Aug 28, 2026
  4. moved this from 📥 Backlog to 👀 In Review in 🌊 Project Boardon Aug 28, 2026
  5. devantler commented on Aug 28, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Unblocked, and the sequencing this issue records is refuted — the narrowing cannot follow the
    package bump, because both orders break. It is being delivered atomically in #3422.

    The blocker line said the deployed package was still branch-signed v0.1.0, so narrowing now would
    reject it — correct, and it is the bottom-left cell below. What that reading missed is the cell
    diagonally opposite: bumping the package while the rules still pin main rejects the new
    provider just as hard, at admission and at node-level pull. Codex raised exactly that as a P1 on
    #3422, and it is right.

    Re-measured with cosign verify against the registry at #3422's head, against the regex this
    issue's acceptance criteria specify:

    package current …@refs/heads/main$ this issue's …@refs/tags/v[0-9].+$
    v0.1.0 (deployed) PASS FAIL
    v1.0.0 (tag-signed) FAIL PASS

    A perfect anti-diagonal: each cell is the other's control, and there is no order in which the two
    halves can be shipped separately without an ImagePullBackOff in between. So they are one atomic
    change. #3422 now carries the package bump and both regex narrowings.

    One correction to the premise, which strengthens the case rather than weakening it. This issue
    and the in-file comments both state that publish-provider-package.yml is workflow_dispatch-only
    and publishes from main. That is no longer true: upstream it triggers on push: tags: v* and
    rejects any non-tag ref before it checks anything out. So @refs/heads/main$ does not merely
    fail to match v1.0.0 — it can no longer match any future release either. The stale comments in both
    files have been corrected in #3422.

    Acceptance criteria status.

    • provider-upjet-unifi#35 delivered and the re-published package verifies with a
      refs/tags/vX.Y.Z SAN — closed 2026-08-27; v1.0.0 measured PASS above.
    • Both regexes narrowed to …/publish-provider-package\.yml@refs/tags/v[0-9].+$.
    • A negative control confirms a branch-ref signature is rejected — the top-right cell, plus a
      deliberately bogus ref regex that also fails, so the expression is discriminating rather than
      inert.
    • Verified behaviourally end to end: the provider pod is admitted and pulls on a node. This
      is the one criterion that cannot be met before merge; it is the post-rollout observation noted
      on chore(crossplane): move provider-upjet-unifi to the tag-signed v1.0.0 #3422.

    Removing the blocked label: the named blocker (#3421) is what #3422 delivers, so this is no longer
    waiting on anything external.

  6. moved this from 👀 In Review to ✅ Done in 🌊 Project Boardon Aug 29, 2026
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

    No labels
    No labels

    Fields

    No fields configured for Security.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions