Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: [Proposal] Source Verification Policies #14964

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

jannfis
Copy link
Member

@jannfis jannfis commented Aug 8, 2023

Proposal for Source Verification Policies.

Note on DCO:

If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the Details link next to the DCO action for instructions on how to resolve this.

Checklist:

  • Either (a) I've created an enhancement proposal and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
  • The title of the PR states what changed and the related issues number (used for the release note).
  • The title of the PR conforms to the Toolchain Guide
  • I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
  • I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
  • Does this PR require documentation updates?
  • I've updated documentation as required by this PR.
  • Optional. My organization is added to USERS.md.
  • I have signed off all my commits as required by DCO
  • I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
  • My build is green (troubleshooting builds).
  • My new feature complies with the feature status guidelines.
  • I have added a brief description of why this PR is necessary and/or what this PR solves.

Please see Contribution FAQs if you have questions about your pull-request.

Signed-off-by: jannfis <jann@mistrust.net>
@codecov
Copy link

codecov bot commented Aug 8, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 49.91%. Comparing base (389cf75) to head (25933d6).
Report is 816 commits behind head on master.

❗ Current head 25933d6 differs from pull request most recent head 782a314. Consider uploading reports for the commit 782a314 to get more accurate results

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #14964   +/-   ##
=======================================
  Coverage   49.91%   49.91%           
=======================================
  Files         262      262           
  Lines       45002    45002           
=======================================
  Hits        22464    22464           
  Misses      20329    20329           
  Partials     2209     2209           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


So as it seems from that explanation of the impact of verification level, a newly Application could only ever be synced with a verification level of `head`.

### Detailed examples
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen with a multi-source application? Would we sync just one source while not syncing the source that fails the verification level check? Or will we sync the application only if all the sources succeeded the verification level checks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If any policy fails validation, the application will not be synced. One advantage of the new verification policies is that you can define a unique policy on a per-source basis. If one of the sources of a multi-source application is not covered by a policy, it will be automatically a "pass".


Also, the current approach is an "all-or-nothing" approach for any given AppProject. The reality however is, that different repositories might have different trust levels, and different people working on them and being trusted. Especially with the advent of multi-source applications, the current approach of defining the trusted signers for all Applications in a given AppProject is not viable anymore.

### Goals

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an additional goal of support GPG in ApplicationSet git generator?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, ApplicationSet is out of scope for this proposal. ApplicationSets are not bound to AppProjects afaik.

* `progressive`: This verification level will verify all commits between the application's target revision and the revision it was last synced to, but not beyond that. The revision that the application is currently synced to does not necessarily need to be signed, so that a migration path from level `head` to the more secure method `progressive` is possible. However, if the application hasn't synced before (i.e. there is no previous revision in the application's sync history), the `progressive` level will inhibit the same behaviour as the `strict` verification level (see below).
* `strict`: This verification level will always verify all commits in the history of the repository that led to the application's target revision. The `strict` level effectively enforces that all commits in your repository need to be signed from the beginning of your repository's history, without allowing _any_ unsigned commit in the history. This is the most secure policy.

For the `strict` and `progressive` verification levels, if the target revision is an annotated tag, then that tag's signature will also be verified in addition to the specific commits verified as defined by the respective level.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently either a signed tag or a lightweight tag are accepted. In these modes, should only signed tags be allowed? A malicious user, or git server, could move lightweight tags to an older commit to perform a downgrade attack (e.g. deploy and older version of an app with known vulnerabilities)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's a good catch. I haven't thought much about this scenario. However, there will be another attack vector here, which is the Application CR. The same downgrade attack would be possible by just modifying the source in the Application to resolve a previous tag. But yeah, I agree, that this would add up to a defense-in-depth principle.

What do you think about making this another option for a policy? For example, enforceSignedTag: <bool> or allowSignedTagsOnly: <bool>?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think an additional option to force tags to be signed would solve the issue.

I was worried about something going bad in git - I think if a malicious user has access to the Application CR then it's probably game over anyway?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, basically, someone with write access to the Application CR can modify the targetRevision to sync against.

The scope of the verification policies is to only allow sync against previously vetted (e.g. signed) revisions. To my knowledge, neither PGP nor Git knows about the concept of signature revocation, except when revoking the private key used to make the signature. I think preventing downgrade attacks becomes rather futile on this level.

But let's keep thinking about it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern was not really signature revocation, it was more what mischief a malicious git server could achieve. When the Application is tracking a branch it should only accept revisions that are descendants of the currently synced revision and if it's tracking a tag it should be a signed tag, both to prevent the git server moving back to a previous (but signed) vulnerable commit. If a user has the ability to change the Application CR, then they can legitimately point targetRevision where they like, as long as it's signed


* `none`: As the name implies, verification will be disabled at this verification level.
* `head`: This verification level will verify the commit that is pointed to by the HEAD of the target revision. In case target revision is an annotated tag, only the signature of the tag will be verified.
* `progressive`: This verification level will verify all commits between the application's target revision and the revision it was last synced to, but not beyond that. The revision that the application is currently synced to does not necessarily need to be signed, so that a migration path from level `head` to the more secure method `progressive` is possible. However, if the application hasn't synced before (i.e. there is no previous revision in the application's sync history), the `progressive` level will inhibit the same behaviour as the `strict` verification level (see below).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be an explicit design goal to prevent downgrade attacks? e.g. a malicious user or git server could present HEAD as an older (signed) commit, pointing to an older, vulnerable version of the app? When syncing, argocd should only move to commits that are a descendent of the currently synced commit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm wondering if a malicious user in Git could modify the ancestry tree without affecting valid signatures on commits.

The strict mode already checks the ancestry (i.e. only moves forward, not backwards). However, an attacker that has access to both, the Application CR and control over the Git server could potentially work around that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think because the git signature covers both the contents of the commit and the digest of the parent commit, it's not possible to move commits around and retain a valid signature.

I think it's game over if an attacker can modify the Application CR or ArgoCD config in general. But I think that's fine - it would be nice to only have to trust ArgoCD and the cluster it runs on, but not the git server.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's game over if an attacker can modify the Application CR

Can you please elaborate a little on this? The Application CR (as opposed to the AppProject CR) is eventually a user-modifiable resource, and a modification to it must not affect the security of the system.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I wasn't very clear. If a user has a signing key, write access to any branch of a repo on the AppProject allowlist, and write access to the Application CR, they can get ArgoCD to deploy arbitrary resources (within the bounds of what the "best" AppProject allows as they can choose which one to be bound by). If people don't want that happen, I think the only solution is separation of duties so nobody has all 3 roles (or careful push rules on the git repos)


#### Verification levels

Each source verification policy currently knows about four levels of verification:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be an additional mode that restricts ArgoCD to deploying git refs that are digests - i.e. some external system is doing verification, so it's unnecessary for ArgoCD to do any validation, apart from preventing mistakes by restricting to digests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this, and about my answer in #14964 (comment) (reading and answering top to bottom here 😅 ), maybe what I mentioned could be amended as allowedTargets: <type>, where <type> is one of st (signed tag) or digest (commit digest).

## Open Questions [optional]

* Do we really need `progressive` verification level (see below)?
* Should repository pattern matching be regexp based instead of shell-glob?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most stuff in AppProject is glob. And I think source templates are also defined with globs. So I'd favor that strategy until we have a strong reason to move to regex.

A source verification policy has the following properties:

* A repository pattern (shell-glob type) that is matched against the URL of the source (mandatory)
* A repository type that is matched against the application's repository type (mandatory)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this field need to be added to the list above?

Comment on lines +90 to +94
* A repository pattern (shell-glob type) that is matched against the URL of the source (mandatory)
* A repository type that is matched against the application's repository type (mandatory)
* A verification level that describes with which level the verification should be performed (mandatory)
* A verification methods that describes how to the source should be verified (mandatory)
* A list of identities of trusted signers (optional, if omitted, any known signer is trusted)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this list is a duplicate of the one above - could probably merge them.

* A verification methods that describes how to the source should be verified (mandatory)
* A list of identities of trusted signers (optional, if omitted, any known signer is trusted)

Additionally, there are some optional properties you can set for a policy that depend on the verification level, method and respository type.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list above already contains an optional field... are there more optional fields which are not listed here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, we only have this one optional property. This could change in the future when there are more supported verification methods, or when Argo CD supports additional repository types.

docs/proposals/source-verificiation-policies.md Outdated Show resolved Hide resolved
docs/proposals/source-verificiation-policies.md Outdated Show resolved Hide resolved

2. The `progressive` level has a "bootstrap" feature to allow creating an `Application` in `progressive` level. Obviously, when an `Application` is created, it does not have any history information unless it is synced initially. The bootstrap feature defines a time window, the `bootstrapPeriod`, in which an `Application` without history can temporarily sync using the `head` level (i.e. only with the HEAD of `targetRevision` being signed). The bootstrap feature assumes that the `.metadata.creationTimestamp` of the `Application` resource is immutable, or rather be reset by the Kubernetes API server upon modification. During the timeframe in the `bootstrapPeriod`, an adversary could delete the `Application`'s history and sync to any signed commit in the repository.

3. The `progressive` level doesn't allow roll-backs, as it verifies the commits between the previous synced state and the target revision. If the application is on `F`, and wants to roll back to `C`, it cannot find any revision history for this path (because `C` existed prior to `F`). This must be properly documented. For `head` and `full`, this is not a concern as long as the level's requirements are met in the repository.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like there are several downsides to progressive. Should we document the migration path from progressive to strict? i.e. should we be asking people to create a new orphan branch, copy over contents, and sign all commits on a the new branch so that they can move to strict?

- # taken from .spec.signatureKeys
```

If a user wants to migrate from the current implementation to the new source verification policies, they will first have to remove `.spec.signatureKeys` and can then go ahead and define desired policies in `.spec.sourceVerificationPolicies`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the behavior if both fields are set at the same time?

jannfis and others added 2 commits May 10, 2024 11:03
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Jann Fischer <jann@mistrust.net>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Jann Fischer <jann@mistrust.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants