Skip to content

Verify the unifi GitRepository's commit signatures in Flux #3124

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

The unifi GitRepository in k8s/providers/hetzner/apps/unifi/git-repository.yaml tracks a moving
branch and carries no spec.verify block:

  url: https://github.com/devantler-tech/unifi
  ref:
    branch: main

Flux therefore accepts whatever the branch tip is, relying entirely on GitHub having enforced the
source repository's own rules. It does not itself check that the commit it fetched is signed.

Why this is defence-in-depth, not an open hole

Measured 2026-08-14: devantler-tech/unifi and devantler-tech/platform carry the same trust
controls — required signed commits, required pull request, linear history, blocked force pushes,
required status checks, required conversation resolution. Every one of the last 10 commits on
unifi@main reports verified=true.

So the source is not a weaker trust boundary than this repository. Adding verification closes the
residual case where the enforcement layer itself is bypassed or misconfigured, which is worth having
but is not urgent.

Expected behaviour

Flux verifies the signature on the commit it fetches, and refuses to reconcile an unverifiable one.

Smallest useful first step

Add spec.verify (mode HEAD) to the GitRepository with a secretRef holding the trusted public
keys, then confirm reconciliation still succeeds against the current tip.

Known risk to design around

Recent commits on the source are bot-authored and signed with GitHub's key, so the trusted key set
must cover that. A signing method the key set does not cover would block reconciliation rather
than warn — turning a hardening change into an outage. Validate against the real tip before merging,
and stage it so a verification failure is observed before it can affect the live Kustomization.

Acceptance criteria

  • The GitRepository carries spec.verify and reconciles the current tip successfully.
  • A commit that fails verification is demonstrably refused (negative control).
  • The live unifi Kustomization stays Ready=True throughout.

Rough size: small–medium. Supersedes the suspend-based approach in #2705, which was closed because
suspending halts a healthy live reconciliation without raising the attacker's bar.

Activity

  1. coderabbitai commented on Aug 14, 2026

    @coderabbitai
    Contributor
    ⚠️ Possible Duplicate Issue(s)
    🔗 Related PRs

    #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!

  2. moved this to 📥 Backlog in 🌊 Project Boardon Aug 14, 2026
  3. self-assigned this
    on Aug 24, 2026
  4. added a commit that references this issue on Aug 24, 2026
  5. devantler commented on Aug 24, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Delivered the verification boundary in #3357, merged as signed main commit 0c2676f after merge-group CI 32785799205 and its production deploy passed.

    Read-only production evidence after merge:

    • GitRepository/unifi generation 3 has verify.mode=HEAD and the reviewed Secret reference; Ready=True and SourceVerified=True at 2026-08-24T22:46:49Z for commit 7b17f7e33ef507c24c395b884a433c62b92ace98 with key B5690EEEBB952194.
    • Kustomization/unifi is Ready=True and Healthy=True at 2026-08-24T22:50:55Z, with the same revision applied.

    The issue remains open for the second acceptance criterion. An unsigned local Git control was rejected and the repository validator has negative regressions for missing/wrong verification and widened key material, but those do not demonstrate source-controller itself refusing an unverifiable commit. No production resource was mutated to manufacture that failure.

  6. devantler commented on Aug 25, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Revalidated on pickup — the premise changed the day after this was filed

    This issue's evidence records the unifi GitRepository tracking a moving branch:

      ref:
        branch: main

    That is no longer true, and it stopped being true on 2026-08-15 — one day after this was
    filed. platform@a9a177fb ("fix(unifi): prevent pruning managed network resources", #2707)
    replaced the branch with a full commit pin, and added a comment giving the same reasoning this
    issue opens with:

      # Pin the full commit so a source-repository compromise cannot change
      # resources already authorized for patch/update without a separately
      # reviewed Platform change.
      ref:
        commit: 7b17f7e33ef507c24c395b884a433c62b92ace98

    Verified live this run against admin@prod:

    GitRepository unifi   ref={"commit":"7b17f7e3…"}  Ready=True  rev=sha1:7b17f7e3…
    Kustomization unifi   Ready=True  Applied revision: sha1:7b17f7e3…
    

    and that pinned commit itself reports verified=true, reason=valid.

    What that does to the proposed fix

    The threat named here — "Flux accepts whatever the branch tip is" — is closed, and closed by a
    stronger control than the one proposed. A 40-hex commit pin fixes the content cryptographically:
    Flux fetches that object or fails. Signature verification answers who signed the tip; a SHA pin
    removes the tip from the question entirely. Moving the pin is a reviewed Platform PR, which is the
    same review gate spec.verify would have been protecting.

    So implementing spec.verify now would add a control whose residual value is small, while carrying
    the exact outage risk this issue's own "Known risk to design around" section flags: a trusted key set
    that fails to cover a signing method blocks reconciliation rather than warning. For the record,
    that key set would be simple — all 20 most recent commits on unifi@main are dependabot[bot],
    PGP-signed by GitHub's web-flow key, uniformly verified=true — but simple is not the same as
    worth the risk against a threat that is already closed.

    Implementing it mechanically because the issue says so is precisely what revalidating on pickup is
    meant to prevent.

    The residual, which is real

    The mitigation is a convention held in one YAML file with nothing asserting it. Nothing in CI
    fails if a future change reverts ref.commit to ref.branch — for auto-updates, or as
    collateral in an unrelated refactor — and the moment that happens this issue's original threat is
    back, silently, with no signature verification behind it either.

    scripts/validate-flux-verify/ does not cover this: it targets the operator-generated root
    OCIRepository, a different resource and a different control.

    Decision

    Reframing rather than closing: the security intent (source integrity for unifi) still stands, but
    the delivery changes from add signature verification to guard the commit pin that replaced it.
    Superseded acceptance criteria are struck through below rather than deleted, so a reader meeting an
    older reference can see why.

    • The GitRepository carries spec.verify and reconciles the current tip successfully.
      Dropped — superseded by the ref.commit pin (fix(unifi): prevent pruning managed network resources #2707), a stronger control.
    • A commit that fails verification is demonstrably refused (negative control). Dropped
      with the above.
    • A guard fails CI when any GitRepository under k8s/ uses a mutable ref instead of a
      40-hex ref.commit, and its message names the reason and the fix.
    • The guard is proven in both directions — RED on a mutable ref, GREEN on the current tree —
      so it cannot pass vacuously.
    • The live unifi Kustomization stays Ready=True throughout (static guard only; no manifest
      behaviour change).
  7. added 3 commits that reference this issue on Aug 25, 2026
  8. moved this from 📥 Backlog to 👀 In Review in 🌊 Project Boardon Aug 25, 2026
  9. moved this from 👀 In Review to ✅ Done in 🌊 Project Boardon Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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