p2-inc / keycloak-events

Useful Keycloak event listener implementations and utilities.
https://phasetwo.io
Other
168 stars 35 forks source link

Secret is not contained in webhook payload #50

Closed kecskesalbert closed 7 months ago

kecskesalbert commented 7 months ago

Hello p2-inc,

Your keycloak-events plugin has been quite useful. We have started using it and are satisfied with it. The only niggle so far is that sent webhook objects don't contain the secret that had been specified when registering for the event. I am assuming that the secret is meant to validate the sender.

Steps to reproduce:

1) From my code, I register the receiver application: POST /realms//webhooks { "enabled": true, "url": "", "eventTypes": [ "access.*" ], "secret": "" }

2) When an event occurs, app receives the webhook.

Payload: { "time": 1707232563322, "realmId": "d68b322b-c065-4969-b852-83f2ee7d4cb1", "uid": "1a4f38f0-ea82-4cb5-a9c0-63066904f55e", "authDetails": { "realmId": "...", "clientId": "account-console", "userId": "47c223f9-813f-4d65-81ea-56f49cc2aba1", "ipAddress": "...", "username": "...", "sessionId": "d827ee85-5ced-4bc3-b64a-82720eade321" }, "type": "access.LOGIN", "details": { "auth_method": "openid-connect", "auth_type": "code", "response_type": "code", "redirect_uri": "...", "consent": "no_consent_required", "code_id": "d827ee85-5ced-4bc3-b64a-82720eade321", "username": "...", "response_mode": "fragment" } }

Headers: { "x-keycloak-signature": [ "b10d183d323f4f4e40bbc9fdf611810ddbd3afbc10c0f4358243bae4ec9c6ef3"' ], "accept-encoding": [ "gzip,deflate" ], "user-agent": [ "Apache-HttpClient/4.5.14 (Java/17.0.10)" ], "connection": [ "Keep-Alive" ], "content-type": [ "application/json" ], "host": [ "..." ], "content-length": [ "677" ] }

As you can see, neither the payload, nor the headers contain the secret value, so the receiver can't be 100% sure that the webhook received is coming from the Keycloak instance it had registered to. We have 5 Keycloak instances and 5 event receiver instances potentially with the same realm_id. Instances might be destroyed and redeployed continuously. If we register event receiver instance A to Keycloak instance A, later destroy event receiver instance A, deploy event receiver instance B with configuration to receive webhooks from Keycloak instance B (listening on the same URL and port), then we'll have Keycloak instance A still sending event hooks to new event receiver instance B, although unintendedly. With the secret being included in the payload, we could validate the secret and discard unintended transactions.

Keycloak: docker.io/bitnami/keycloak:23.0.4-debian-11-r2 keycloak-events plugin: https://github.com/p2-inc/keycloak-events.git master latest version

xgp commented 7 months ago

Correct. The point is it's a shared secret. When you create the webhook, you send the secret you want to use, and store it on your side. You use the secret and the webhook payload to calculate an HMAC according to RFC2104. I'd suggest reading the RFC if you have questions about how this works.

With the secret being included in the payload, we could validate the secret and discard unintended transactions.

"With the secret being included in the payload" this would be completely useless as a security measure.

kecskesalbert commented 7 months ago

Thanks for your response. Once I've got the HMAC calculated from the JSON payload, which field do I compare it against? Is it the x-keycloak-signature header?

xgp commented 7 months ago

Once I've got the HMAC calculated from the JSON payload, which field do I compare it against? Is it the x-keycloak-signature header?

Yes

kecskesalbert commented 7 months ago

And which HMAC hash function should be used? There are at least a dozen: https://en.wikipedia.org/wiki/HMAC Is it SHA-256 perhaps?

xgp commented 7 months ago

See the README

The HMAC algortihm used for signing. Defaults to HmacSHA256. Can be set to HmacSHA1 for backwards compatibility

josip-radic-dijuno commented 2 days ago

Hello, I’m trying to validate the signature provided in the POST request of a webhook event. I’m generating an HMAC using the same secret and the webhook event’s data (payload).

However, the signature I compute doesn’t match the one sent in the POST request. I’m not sure what exactly is being signed or the exact format of the string. I looked through the source code and it seems you’re signing a JSON-encoded event object, but I’m unclear on what the event looks like in detail. Even a small difference in the string I use to create the signature results in a completely different output. This makes it hard to figure out the exact string format that Keycloak is signing.

Could anyone provide clarity on what the event string looks like and how it is formatted before signing? Any help would be greatly appreciated.

PS: I am using Python :

hmac_obj = hmac.new(secret.encode('utf-8'), data.encode('utf-8'), hashlib.sha256)
computed_signature = hmac_obj.hexdigest()

Update I found the issue. Make sure you’re decoding the request body to UTF-8 to keep the payload exactly the same.

payload_raw = request.body.decode('utf-8')