Make Argo CD Refuse Unsigned Commits: Source Integrity in 3.5, Step by Step
Think of your cluster as a nightclub, and Argo CD as the person at the door. Their job is to let in whatever Git says should be inside. The problem? Until recently, nobody asked whether Git itself was telling the truth.
If someone sneaks a malicious change into your config repo, a perfectly healthy GitOps pipeline will deploy it with a smile. That's the part of GitOps nobody puts on the architecture slide.
Argo CD 3.5, which went GA on August 4, 2026 (3.5.4 is the latest patch as of this week), added a proper answer: Source Integrity. It lets a project refuse to sync unless Git commits are signed by keys you trust. Think of it as an ID check at the door.
And with Argo CD's quarterly cadence, 3.6 is already on the horizon. The project's release schedule points to the first Tuesday of November (November 3), though dates can slip. So this is a good moment to get comfortable with the 3.5 security features before the next upgrade lands.
What We Are Cooking Today
- Understand what Source Integrity does and what it doesn't
- Configure trusted GPG keys and an AppProject policy
- Test that an unsigned commit gets blocked
- Graduate from
headtostrictmode safely, using seal commits
What Else Shipped in 3.5
Source Integrity gets the headline here, but 3.5 had a few siblings worth a quick mention. All of this is from the project's release coverage:
- mTLS for the repo-server. Internal components now authenticate to each other with client certificates.
- ApplicationSet management in the UI, including a preview of which applications a template will generate.
- Impersonation and Source Hydrator moved to beta.
- Helm 4 support alongside Helm 3, and ApplicationSets in any namespace.
If you read our earlier 3.5 write-up, this recipe is the hands-on follow-up: less "what's new," more "how do I actually turn this on."
How Source Integrity Works (The Short Version)
Each AppProject can carry a sourceIntegrity section. It contains policies that match Git repository URLs (with glob patterns) and say how thoroughly to verify GnuPG signatures. When a source fails its checks, the application won't sync, and you'll see a comparison error instead of a deployment.
There are three verification modes:
| Mode | What it verifies | Best for |
|---|---|---|
none | Nothing. Unsigned and invalidly signed commits are accepted. | Explicitly exempting a repo from a broader pattern |
head | Only the commit (or annotated tag) your target revision points to | Getting started; the legacy behavior |
strict | The target revision and all of its ancestors | Repos with a clean, fully signed history |
An honest limitation: verification applies to Git repositories only. It isn't supported for Helm or OCI sources. And Argo CD's trust model is simple: once you import a key, it trusts it. There's no web of trust.
Ingredients
- Argo CD 3.5 or newer
- A config repo where your team already signs commits (or is willing to start)
- The public keys, and key IDs, of the people and bots allowed to commit
- The
argocdCLI, logged in with permission to manage GPG keys - A non-production AppProject to practice on
Kitchen Prep
Three things to know before touching anything:
- Legacy
signatureKeyscan't be combined withsourceIntegrity. If a project uses the old setting, migrate it: remove.spec.signatureKeysand define policies under.spec.sourceIntegrity.git.policies. The old behavior maps tomode: headon all repos. - Source Integrity is configured through AppProject manifests or the CLI, not the UI.
- Once enforced,
argocd app sync --localstops working. If anyone on your team relies on local syncs for debugging, tell them first.
Recipe Steps
Step 1: Collect the public keys you trust
Export each trusted signer's public key. Never the private one.
gpg --armor --export <KEY_ID> > alice.pub.asc
gpg --list-keys --keyid-format long
Expected: an ASCII-armored public key file, and the long key ID you'll reference in policies. Pitfall: exporting with --export-secret-keys. Check twice. Please.
Step 2: Import the keys into Argo CD
argocd gpg add --from alice.pub.asc
argocd gpg list
Argo CD stores keys in the argocd-gpg-keys-cm ConfigMap, so you can also manage them declaratively in Git, which is the better long-term habit. Keys can take a little while to propagate through the cluster after import.
Step 3: Add a policy to an AppProject (start with head)
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: payments
namespace: argocd
spec:
description: Payments team workloads
sourceRepos:
- https://github.com/my-org/payments-config.git
destinations:
- namespace: payments-*
server: https://kubernetes.default.svc
sourceIntegrity:
git:
policies:
- repos:
- url: "https://github.com/my-org/payments-config.git"
gpg:
mode: head
keys:
- "<KEY_ID_OF_ALICE>"
- "<KEY_ID_OF_CI_BOT>"
Why head first: it only checks the commit you're deploying, so old unsigned history won't block you on day one. When not to use it: if you need the guarantee that nothing unsigned exists anywhere in the history, graduate to strict (Step 6).
Only one policy applies per repository, and sources that no policy matches aren't verified at all. So a typo in a URL pattern means silent non-enforcement. Verify, don't assume.
Step 4: Prove it blocks something
A control you haven't tested is a hope. On a test branch, push one deliberately unsigned commit:
git checkout -b sigtest
git commit --allow-empty --no-gpg-sign -m "test: unsigned commit"
git push origin sigtest
Point a test Application's targetRevision at sigtest, then check its status:
argocd app get <test-app>
argocd app sync <test-app>
Expected: the sync is refused with a comparison error because the revision isn't signed by a trusted key. Then push a properly signed commit and confirm the app syncs. Delete the test branch afterward.
Step 5: Handle the GitHub web merge gotcha
If your team merges pull requests through the GitHub UI, those merge or squash commits are signed by GitHub's own web-flow key rather than the author's. The Argo CD docs actually use that web-flow key as their example for declarative key setup. So if you want UI merges to pass verification, that key has to be in your trusted list. This is my reading of how the pieces fit together, so test it in your own setup rather than taking my word for it.
Whether you should trust that key is a policy decision. Trusting it means trusting that anything merged through GitHub's UI by an authorized person is acceptable. Many teams are fine with that. Some aren't.
Step 6: Move to strict mode with a seal commit
Switching to strict on a repo with unsigned history will fail immediately, since ancestors must pass too. Rewriting history is risky. Argo CD offers a better route: a seal commit, a signed commit saying "everything before this was reviewed and trusted."
git commit --allow-empty --signoff --gpg-sign \
--trailer="Argocd-gpg-seal: Reviewed history through $(date +%F); signing enforced from here" \
-m "chore: seal history for Argo CD strict verification"
git push origin main
Argo CD then verifies only as far back as the most recent seal on each ancestral branch. Then update the policy:
gpg:
mode: strict
Seal commits also work for key changes: "I'm retiring Bob's key; trust his old commits but not new ones" is a legitimate seal message. Write real justifications in the trailer. Your future auditor is also your future self.
Chef Tips
- Enforce signing at the Git provider too. Argo CD is your last line of defense, not your first. Branch protection that requires signed commits stops problems before they reach the cluster. That's a recommendation, not an Argo CD feature.
- Give CI bots their own keys. A shared human key in a pipeline is a breach waiting for a retro. Dedicated bot keys also make revocation clean.
- Roll out project by project. Start with a low-risk project, then expand. Strict verification across everything on day one is how people end up disabling it on day two.
- Keep keys in Git. The
argocd-gpg-keys-cmConfigMap can be managed declaratively, which gives you a reviewable history of who was trusted when.
Kitchen Disasters
| Symptom | Likely cause | Fix |
|---|---|---|
| Signed commit still rejected | Key not yet propagated, or not in the policy's keys list | Check argocd gpg list, confirm the key ID in the policy, and restart argocd-repo-server pods if it stays out of sync |
| Policy seems ignored | Repo URL doesn't match any repos glob | Fix the pattern; unmatched sources aren't verified at all |
strict mode fails on old history | Unsigned or untrusted-key ancestors | Add a signed seal commit rather than rewriting history |
Cannot use sync --local | Source integrity enforced | Expected behavior; use a signed commit on a test branch instead |
| ApplicationSet apps not verified | Git generator with a templated project field isn't supported for signature verification | Use a fixed project for those ApplicationSets |
| Helm or OCI sources unverified | GnuPG verification supports Git only | Secure those with other controls, such as pinned chart digests and registry policies |
Serving Suggestions
- Pair Source Integrity with solid cluster security practices. Signed commits prove who wrote a change, not that the change is safe, so keep admission policies and image scanning in the pipeline.
- Stage the next Argo CD minor release in a non-production environment before it goes GA, and read its upgrade notes. The 3.5 release candidates were a good time to find surprises, and 3.6 is no different.
- Review who can manage GPG keys. The RBAC resource is
gpgkeys, and keepingcreateanddeletetight matters as much as the policy itself.
Cleanup
Delete the sigtest branch and the test Application. If you tried the policy on a project and decided to pause, set the policy's mode to none or remove the sourceIntegrity block rather than deleting keys. Argo CD can also disable the GnuPG feature entirely by setting ARGOCD_GPG_ENABLED=false on its components, but that's a big hammer, so use it deliberately.
Frequently Asked Questions
Does Source Integrity protect against a compromised Argo CD?
No. It protects against unsigned or untrusted changes reaching Argo CD through Git. A compromised Argo CD or a stolen trusted signing key is a different problem.
Can I use SSH signatures or Sigstore instead of GPG?
The current documentation lists Git GnuPG verification as the supported method. The framework is designed so other methods can be added later, but check the docs for your version.
Will this slow down syncs?
strict mode verifies more commits, so it does more work than head. I haven't benchmarked it, and I won't invent a number. Measure it on your own repo history in staging.
Is 3.5 safe to upgrade to?
That's your call and your staging environment's. Argo CD supports only the three most recent minor versions with patches, so staying current is part of the security posture.
Final Thoughts
GitOps made Git the front door to production. Source Integrity finally lets you ask the door to check ID. Start with head, prove it blocks an unsigned commit, handle your merge-workflow quirks, and graduate to strict with a seal commit when your history is ready. It's a small amount of YAML for a very different answer to "what if someone pushes something sneaky?"
No comments:
Post a Comment