-
Notifications
You must be signed in to change notification settings - Fork 252
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
Webhook Signatures and Validation #4224
Comments
This makes sense. The primary problem is "proving" that a request came from a trusted application. Webhook URLs can currently contain credentials, which can address some of the concerns, including per-config auth. What's left is knowing where it came from, and a public/private key approach would work well with that, as any receiving application can then verify. As for implementation, maybe break this into three parts:
|
I tend to agree it is unnecessary to be able to individually rotate the single key-ring...
Agreed. Not sure it's a prerequisite of this functionality, it'd be important either way |
I did some digging around existing examples, this is the most active/comprehensive RFC I could find for existing work solving this problem in the wild: Looks like there's a tool out there to experiment with a list of libraries: TL;DR; uses two headers:
It's also possible to specify multiple signatures, which would allow for key rotation or even config changes/deprecations as necessary in the future. An example of what a request with two signatures might look likePOST /path?param=value HTTP/1.1
Host: www.example.com
Date: Tue, 20 Apr 2021 02:07:55 GMT
Content-Type: application/json
Content-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:
Content-Length: 18
Signature-Input: sig1=("@method" "@target-uri" "content-digest");created=1618884473;keyid="test-key-old";alg="ecdsa-p256-sha256"
Signature: sig1=:dMT/A/76ehrdBTD/2Xx8QuKV6FoyzEP/I9hdzKN8LQJLNgzU4W767HK05rx1i8meNQQgQPgQp8wq2ive3tV5Ag==:
Signature-Input: sig2=("@method" "@target-uri" "content-digest");created=1618884475;keyid="test-key-new";alg="ecdsa-p256-sha256"
Signature: sig2=:C73J41GVKc+TYXbSobvZf0CmNcptRiWN+NY1Or0A36ISg6ymdRN6ZgR2QfrtopFNzqAyv+CeWrMsNbcV2Ojsgg==:
{"hello": "world"} In this example:
As for what we'd use, ECDSA with SHA-256 and P-256 is becoming widely accepted, so I think that would be a good balance. Also, if GoAlert calls into any environments/applications that need to be FIPS compliant, there are options for that combo. Lastly, for how/where to put this, we could extend the existing keyring to support overriding its curve and adding a new method if necessary to sign; otherwise we could consider making a new one that supports more flexibility around keys. The existing code is a bit dated, so it may be good to update it one way or another (in-place or replace). |
If I understand that RFC correctly, the actual signature would not be generated strictly from the message body itself but from a "signature base" derived from the contents of the message. The I am not entirely comfortable with ECDSA/SHA256/P256, but I think the majority of possible attacks are timing related. I am outside of my depth here though... cryptography is not my strong suit. It is a supported algorithm in RFC9421. If we used If we are to use RFC9421, which seems wise, we could very easily sign every outgoing GoAlert request with middleware if desired (and it likely is). My biggest concerned with RFC9421 is how many implementation libraries exist for each language, but rolling our own scheme seems more fraught with error, even if it is simpler than the RFC. |
Speaking on potential JWK endpoints, we could use I think we'd want to provide the current public key in the UI as a PEM-encoded blob, which is likely the most compatible format for future loading (or at least is the easiest to translate to different formats). This could be accomplished via the GQL API, presenting the current public key as a portion of the schema. |
What problem would you like to solve? Please describe:
It should be possible to validate that a given webhook payload came from a specific GoAlert instance and that it wasn't tampered with over the wire.
Describe the solution you'd like:
By signing a webhook payload and sending that signature as an HTTP header, we can validate that (so long as the transport protocol is secure, e.g. HTTPS) a webhook payload came from a specific GoAlert instance. This would also ensure tampering doesn't happen.
Describe alternatives you've considered:
Including a bearer token, basic auth, or other authN/Z protocol as part of the webhook delivery mechanism would give stronger guarantees than "this payload came from this GoAlert instance" (e.g. "this payload came from this GoAlert delivery method") but would involve potentially storing credentials for each webhook delivery method.
Similarly, having signing keys for each webhook delivery method (per user, etc) would cause the same "credential bloat" but would avoid the risk of end-user credential rotation (like basic AuthN/Z or another form of end-user provided credential solution). I think it is a safe assumption to make that an end-user should "trust" all webhooks originating from a given GoAlert instance. Whether or not the application receiving the webhook should act upon a received webhook once it has been validated is likely not a concern of GoAlert which sends the webhook.
Additional context:
Sketching out a solution, it is likely that the simplest approach is to use a single
Keyring
per instance that can be manually rotated via the API and sign the webhook as it gets delivered. The public key could be accessed via the UI or API (proof that it "came from this specific GoAlert instance") and used by the receiving party to validate. I'm going to try writing this up.The text was updated successfully, but these errors were encountered: