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

fix: Adds proper CSRF protection to ArgoCD API #16856

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

Conversation

alexmt
Copy link
Collaborator

@alexmt alexmt commented Jan 12, 2024

Closes #2496
Based on #2672 - mostly the same code with some changes mostly in UI ( token handling is done in requests.ts file).

This PR adds proper CSRF protection to the ArgoCD REST API by implementing the Gorilla CSRF framework (https://github.com/gorilla/csrf).

Behaviour for CSRF token validation is as follows:

  • All HTTP verbs are protected except requests using GET, HEAD, TRACE and OPTIONS verbs
  • Each other request is evaluated by validating the X-Csrf-Token header value sent with the request, except for the conditions mentioned below
  • If the client sends the authentication cookie (i.e. argocd.token) with the request, CSRF checks are always performed
  • If the client sends an Authorization: Bearer HTTP header and is not including the authorization cookie in the request (i.e. is a stateless REST API client), the CSRF validation is skipped (the Authorization header basically verified the request already)
  • If the client does not have the authentication cookie, and no Authorization header, the only POST method allowed is on /api/v1/session to create a session for the client and retrieve the credentials for further communication
  • A new switch --disable-csrf is provided for argocd-server command, when set CSRF checks will be disabled completey.

This introduces a new key in argocd-secret for storing the key used to sign CSRF token, named server.csrfkey, which is similar to server.secretkey - that is, will be generated if the key does not exist in the secret on server start, otherwise will persist.

The retrieve a token, the client can issue a GET (or HEAD or OPTIONS if implemented by API) request on an arbitrary REST API endpoint. Preferably, this is /api/v1/session/userinfo. The server will send back a valid token in the X-CSRF-Token header in the response, as long as the user is correctly authenticated. Endpoints that are generally unprotected (currently /api/version and /api/v1/settings) will not send the appropriate header.

@alexmt
Copy link
Collaborator Author

alexmt commented Jan 12, 2024

Reviewers, FYI: some questions already answered in #2672

@alexmt alexmt force-pushed the csrf-protection branch 8 times, most recently from 634136f to a45d18b Compare January 12, 2024 22:07
Copy link

codecov bot commented Jan 12, 2024

Codecov Report

Attention: 33 lines in your changes are missing coverage. Please review.

Comparison is base (b12630c) 49.31% compared to head (a4c96db) 49.30%.
Report is 1 commits behind head on master.

Files Patch % Lines
util/settings/settings.go 19.04% 15 Missing and 2 partials ⚠️
server/server.go 60.97% 11 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #16856      +/-   ##
==========================================
- Coverage   49.31%   49.30%   -0.02%     
==========================================
  Files         272      272              
  Lines       47975    48044      +69     
==========================================
+ Hits        23660    23686      +26     
- Misses      21973    22009      +36     
- Partials     2342     2349       +7     

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

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
server/server.go Outdated Show resolved Hide resolved
@aphtrinh
Copy link

(I wrote https://blog.calif.io/p/argo-cd-csrf)

This fixes the issue, but there may be problems.

Gorilla, or CSRF tokens in general make sense for web forms (application/x-www-form-urlencoded) but not JSON. First, the browser now has to make a redundant request to fetch the X-Csrf-Token first, before calling the API. Second, we have to use some csrf.UnsafeSkipCheck to bypass the token check when calling the API. This exception case may lead to bypasses in itself.

My suggestion would be to properly parse the Content-type header before the request body and strictly require application/json. This would trigger the default cross-site protections implemented by browsers: A preflight CORS request is sent, then Argo CD does not return Access-Control-Allow-Origin header, then the request fails). This is a key point that made the exploit possible because Argo CD allows text/plain content-type for JSON body (see the list of CORS-safelisted request headers).

I assume Argo CD does not really have a lot of cross-site use cases, judging from the fact there's no CORS header (Access-Control-Allow-Origin) implemented by the HTTP server. So maybe adding gorilla-csrf is an overhead we can avoid.

@alexmt
Copy link
Collaborator Author

alexmt commented Jan 13, 2024

Thank you for review @jessesuen ! Addressed both suggestions!

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Copy link
Member

@ishitasequeira ishitasequeira left a comment

Choose a reason for hiding this comment

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

LGTM!!

Copy link
Member

@gdsoumya gdsoumya left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@todaywasawesome todaywasawesome left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@jannfis jannfis left a comment

Choose a reason for hiding this comment

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

LGTM

@crenshaw-dev
Copy link
Collaborator

I'd recommend holding off on this until we're certain that it's necessary in addition to content-type checking. Sounds like there are potential performance tradeoffs as well as additional maintenance to be sure the CSRF protection is functioning as expected.

@jannfis
Copy link
Member

jannfis commented Feb 8, 2024

Just for consideration: We were holding off the original PR because we thought SameSite cookies would solve the issue at hand, but apparently it didn't. Worst case now would be when we think the Content-Type restriction solves the issue, but it doesn't.

Yes, it will have a slight performance impact. Yes maintenance might be a little bit more complex. But it will definitely implement current best practices.

Copy link
Collaborator

@crenshaw-dev crenshaw-dev left a comment

Choose a reason for hiding this comment

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

That's fair. In that case, I think we should merge this as a new feature, and we should add update 1) API docs and 2) upgrade docs to outline the change in API behavior.

@@ -240,6 +242,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address")
command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address")
command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication")
command.Flags().BoolVar(&disableCsrf, "disable-csrf", false, "Disable API CSRF protection")
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should probably make this configurable via argocd-cmd-params-cm.

Copy link
Member

Choose a reason for hiding this comment

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

Good catch.

@jannfis
Copy link
Member

jannfis commented Feb 8, 2024

Agree, it should be a feature, not a a fix.

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.

ArgoCD does not provide CSRF protection
8 participants