mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Merge pull request #459 from nimec01/webhook-signatures
Add webhook signatures with amazing docs - thanks to @nimec01 🙏
This commit is contained in:
38
docs/WEBHOOK_SECRET.md
Normal file
38
docs/WEBHOOK_SECRET.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Webhook Secrets
|
||||
|
||||
## How does the signing work?
|
||||
|
||||
Pi.Alert will use the configured secret to create a hash signature of the request body. This SHA256-HMAC signature will appear in the `X-Webhook-Signature` header of each request to the webhook target URL. You can use the value of this header to validate the request was sent by Pi.Alert.
|
||||
|
||||
## Activating webhook signatures
|
||||
|
||||
All you need to do in order to add a signature to the request headers is to set the `WEBHOOK_SECRET` config value to a non-empty string.
|
||||
|
||||
## Validating webhook deliveries
|
||||
|
||||
There are a few things to keep in mind when validating the webhook delivery:
|
||||
|
||||
- Pi.Alert uses an HMAC hex digest to compute the hash
|
||||
- The signature in the `X-Webhook-Signature` header always starts with `sha256=`
|
||||
- The hash signature is generated using the configured `WEBHOOK_SECRET` and the request body.
|
||||
- Never use a plain `==` operator. Instead, consider using a method like [`secure_compare`](https://www.rubydoc.info/gems/rack/Rack%2FUtils:secure_compare) or [`crypto.timingSafeEqual`](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b), which performs a "constant time" string comparison to help mitigate certain timing attacks against regular equality operators, or regular loops in JIT-optimized languages.
|
||||
|
||||
## Testing the webhook payload validation
|
||||
|
||||
You can use the following secret and payload to verify that your implementation is working correctly.
|
||||
|
||||
`secret`: 'this is my secret'
|
||||
|
||||
`payload`: '{"test":"this is a test body"}'
|
||||
|
||||
If your implementation is correct, the signature you generated should match the following:
|
||||
|
||||
`signature`: bed21fcc34f98e94fd71c7edb75e51a544b4a3b38b069ebaaeb19bf4be8147e9
|
||||
|
||||
`X-Webhook-Signature`: sha256=bed21fcc34f98e94fd71c7edb75e51a544b4a3b38b069ebaaeb19bf4be8147e9
|
||||
|
||||
## More information
|
||||
|
||||
If you want to learn more about webhook security, take a look at [GitHub's webhook documentation](https://docs.github.com/en/webhooks/about-webhooks).
|
||||
|
||||
You can find examples for validating a webhook delivery [here](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries#examples).
|
||||
@@ -533,6 +533,8 @@
|
||||
"WEBHOOK_REQUEST_METHOD_description" : "The HTTP request method to be used for the webhook call.",
|
||||
"WEBHOOK_SIZE_name" : "Max payload size",
|
||||
"WEBHOOK_SIZE_description" : "The maximum size of the webhook payload as number of characters in the passed string. If above limit, it will be truncated and a <code>(text was truncated)</code> message is appended.",
|
||||
"WEBHOOK_SECRET_name": "HMAC Secret",
|
||||
"WEBHOOK_SECRET_description": "When set, use this secret to generate the SHA256-HMAC hex digest value of the request body, which will be passed as the <code>X-Webhook-Signature</code> header to the request. You can find more informations <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_SECRET.md\">here</a>.",
|
||||
"Apprise_display_name" : "Apprise",
|
||||
"Apprise_icon" : "<i class=\"fa fa-bullhorn\"></i>",
|
||||
"REPORT_APPRISE_name" : "Enable Apprise",
|
||||
@@ -661,4 +663,4 @@
|
||||
"Donations_Platforms" : "Sponsor platforms",
|
||||
"Donations_Others" : "Others"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ REPORT_WEBHOOK = False
|
||||
WEBHOOK_URL = ''
|
||||
WEBHOOK_PAYLOAD = 'json'
|
||||
WEBHOOK_REQUEST_METHOD = 'GET'
|
||||
WEBHOOK_SECRET = ''
|
||||
|
||||
# Apprise
|
||||
REPORT_APPRISE = False
|
||||
|
||||
@@ -132,6 +132,7 @@ def importConfigs (db):
|
||||
conf.WEBHOOK_PAYLOAD = ccd('WEBHOOK_PAYLOAD', 'json' , c_d, 'Payload type', 'text.select', "['json', 'html', 'text']", 'Webhooks')
|
||||
conf.WEBHOOK_REQUEST_METHOD = ccd('WEBHOOK_REQUEST_METHOD', 'GET' , c_d, 'Req type', 'text.select', "['GET', 'POST', 'PUT']", 'Webhooks')
|
||||
conf.WEBHOOK_SIZE = ccd('WEBHOOK_SIZE', 1024 , c_d, 'Payload size', 'integer', '', 'Webhooks')
|
||||
conf.WEBHOOK_SECRET = ccd('WEBHOOK_SECRET', '' , c_d, 'Secret', 'text', '', 'Webhooks')
|
||||
|
||||
# Apprise
|
||||
conf.REPORT_APPRISE = ccd('REPORT_APPRISE', False , c_d, 'Enable Apprise', 'boolean', '', 'Apprise', ['test'])
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import json
|
||||
import subprocess
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
import conf
|
||||
from const import logPath
|
||||
@@ -71,6 +73,7 @@ def send (msg: noti_struc):
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
# DEBUG - Write the json payload into a log file for debugging
|
||||
write_file (logPath + '/webhook_payload.json', json.dumps(_json_payload))
|
||||
|
||||
@@ -81,7 +84,13 @@ def send (msg: noti_struc):
|
||||
curlParams = ["curl","-i","-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
|
||||
else:
|
||||
_WEBHOOK_URL = conf.WEBHOOK_URL
|
||||
curlParams = ["curl","-i","-X", conf.WEBHOOK_REQUEST_METHOD ,"-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
|
||||
curlParams = ["curl","-i","-X", conf.WEBHOOK_REQUEST_METHOD , "-H", "Content-Type:application/json", "-d", json.dumps(_json_payload), _WEBHOOK_URL]
|
||||
|
||||
# Add HMAC signature if configured
|
||||
if(conf.WEBHOOK_SECRET != ''):
|
||||
h = hmac.new(conf.WEBHOOK_SECRET.encode("UTF-8"), json.dumps(_json_payload, separators=(',', ':')).encode(), hashlib.sha256).hexdigest()
|
||||
curlParams.insert(4,"-H")
|
||||
curlParams.insert(5,f"X-Webhook-Signature: sha256={h}")
|
||||
|
||||
try:
|
||||
# Execute CURL call
|
||||
|
||||
Reference in New Issue
Block a user