mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-08 03:01:29 -07: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_REQUEST_METHOD_description" : "The HTTP request method to be used for the webhook call.",
|
||||||
"WEBHOOK_SIZE_name" : "Max payload size",
|
"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_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_display_name" : "Apprise",
|
||||||
"Apprise_icon" : "<i class=\"fa fa-bullhorn\"></i>",
|
"Apprise_icon" : "<i class=\"fa fa-bullhorn\"></i>",
|
||||||
"REPORT_APPRISE_name" : "Enable Apprise",
|
"REPORT_APPRISE_name" : "Enable Apprise",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ REPORT_WEBHOOK = False
|
|||||||
WEBHOOK_URL = ''
|
WEBHOOK_URL = ''
|
||||||
WEBHOOK_PAYLOAD = 'json'
|
WEBHOOK_PAYLOAD = 'json'
|
||||||
WEBHOOK_REQUEST_METHOD = 'GET'
|
WEBHOOK_REQUEST_METHOD = 'GET'
|
||||||
|
WEBHOOK_SECRET = ''
|
||||||
|
|
||||||
# Apprise
|
# Apprise
|
||||||
REPORT_APPRISE = False
|
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_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_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_SIZE = ccd('WEBHOOK_SIZE', 1024 , c_d, 'Payload size', 'integer', '', 'Webhooks')
|
||||||
|
conf.WEBHOOK_SECRET = ccd('WEBHOOK_SECRET', '' , c_d, 'Secret', 'text', '', 'Webhooks')
|
||||||
|
|
||||||
# Apprise
|
# Apprise
|
||||||
conf.REPORT_APPRISE = ccd('REPORT_APPRISE', False , c_d, 'Enable Apprise', 'boolean', '', 'Apprise', ['test'])
|
conf.REPORT_APPRISE = ccd('REPORT_APPRISE', False , c_d, 'Enable Apprise', 'boolean', '', 'Apprise', ['test'])
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
import conf
|
import conf
|
||||||
from const import logPath
|
from const import logPath
|
||||||
@@ -71,6 +73,7 @@ def send (msg: noti_struc):
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# DEBUG - Write the json payload into a log file for debugging
|
# DEBUG - Write the json payload into a log file for debugging
|
||||||
write_file (logPath + '/webhook_payload.json', json.dumps(_json_payload))
|
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]
|
curlParams = ["curl","-i","-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
|
||||||
else:
|
else:
|
||||||
_WEBHOOK_URL = conf.WEBHOOK_URL
|
_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:
|
try:
|
||||||
# Execute CURL call
|
# Execute CURL call
|
||||||
|
|||||||
Reference in New Issue
Block a user