- `workers (4)` The amount of workers for parallel webhook processing. If you plan on processing a LOT of requests or triggering long running task, increase the worker count.
- `basic_auth_user (null)` Your user if you want to do basic auth. Check the `Building a request` section for more information on basic_auth headers
- `basic_auth_password (null)` Your password if you want to do basic auth.
-- `secret (null)` A hex secret for authentication via payload signature verification. Check the `Building a request` section for more information on signature headers. Can be, for instance, be created with `hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random`
+- `secret (null)` A secret for authentication via payload signature verification. Check the `Building a request` section for more information on signature headers. Can be, for instance, be created with `pwgen 25 1`
- `basic_auth_and_secret (false)` By default it's only required to authenticate via BasicAuth OR signature authentication. If you want to be super safe, set this to true to require both.
- `webhooks` A list of webhooks. Such a webhook looks like this:
```
echo -n '{"parameters":{"param1":"-al","param2":"/tmp"}}' | http POST localhost:8000/ls \
- Signature:'sha1=e8c87b736b2c385979cddb8a4a425de77b6b4640' \
+ Signature:'sha1=d762407ca7fb309dfbeb73c080caf6394751f0a4' \
Authorization:'Basic d2ViaG9vazp0aGlzaXNhcGFzc3dvcmQ='
```
**Headers:**
- `Authorization`: If `basic_auth.username` and `basic_auth.password` is specified, this should be the standard `base64` encoded authorization header.
-- `Signature:` If you specify a secret, the content of the signature is the HMAC of the json payload with the secret as key.
- This procedure is based on Github's webhook secret system.
+- `Signature:` If you specify a secret, the content of the signature is the HMAC of the json payload with the UTF8-encoded secret as key.
+ This procedure is based on Github's webhook secret system. (Github says to use a hex key, but they interpret it as UTF8 -.-)
Python example: `hmac.new(key, payload, hashlib.sha1)`
Ruby example: `OpenSSL::HMAC.hexdigest("SHA256", key, payload)`
- `X-Hub-Signature`: This is the default of Github's webhooks and is a fallback, if `Signature` is not specified.
-use ::actix_web::http::header::{HeaderMap, HeaderValue};
+use ::std::collections::HashMap;
use ::actix_web::*;
use ::hex;
use ::hmac::{Hmac, Mac};
pub fn verify_authentication_header(
settings: &Settings,
- request: &HttpRequest,
+ headers: &HashMap<String, String>,
body: &Vec<u8>,
parsed_body: String,
) -> Result<(), HttpResponse> {
// Check for a correct signature, if we have as secret or both authentication methods are required
if has_secret || check_both {
- let signature = get_signature_header(&request.headers())?;
+ let signature = get_signature_header(headers)?;
if !signature.is_empty() {
verify_signature_header(signature, secret, body, parsed_body)?;
} else if check_both {
/// Extract the correct signature header content from all headers
/// It's possible to receive the signature from multiple Headers, since Github uses their own
/// Header name for their signature method.
-fn get_signature_header(headers: &HeaderMap) -> Result<String, HttpResponse> {
- let header: &HeaderValue;
- if headers.contains_key("signature") {
- header = headers
- .get("signature")
- .expect("Error while extracting signature header");
- } else if headers.contains_key("X-Hub-Signature") {
- header = headers
- .get("X-Hub-Signature")
- .expect("Error while extracting github signature header");
+fn get_signature_header(headers: &HashMap<String, String>) -> Result<String, HttpResponse> {
+ let mut header = headers.get("signature");
+ if header.is_none() {
+ header = headers.get("X-Hub-Signature");
+ }
+
+ // We dont' find any headers for signatures and this method is not required
+ let mut header = if let Some(header) = header {
+ header.clone()
} else {
- // We dont' find any headers for signatures and this method is not required
return Ok("".to_string());
};
- match header.to_str() {
- Ok(header) => {
- // Header must be formatted like this: sha1={{hash}}
- let mut header = header.to_string();
- if !header.starts_with("sha1=") {
- Err(HttpResponse::Unauthorized()
- .body("Error while parsing signature: Couldn't find prefix"))
- } else {
- Ok(header.split_off(5))
- }
- },
- Err(error) => {
- Err(HttpResponse::Unauthorized()
- .body(format!("Error while parsing signature: {}", error)))
- }
+ // Header must be formatted like this: sha1={{hash}}
+ if !header.starts_with("sha1=") {
+ Err(HttpResponse::Unauthorized()
+ .body("Error while parsing signature: Couldn't find prefix"))
+ } else {
+ Ok(header.split_off(5))
}
}
use ::actix::prelude::*;
use ::actix_web::*;
+use ::actix_web::http::header::HeaderMap;
use ::serde::Deserialize;
use ::serde_json;
use ::log::{info, warn, debug};
let body: Vec<u8> = body.to_vec();
let payload = get_payload(&body)?;
+ let headers = get_headers_hash_map(request.headers())?;
let parsed_payload = match str::from_utf8(&body) {
Ok(parsed) => parsed,
Err(_) => {
let webhook_name = path_info.into_inner();
// Check the credentials and signature headers of the request
- verify_authentication_header(&data.settings, &request, &body, parsed_payload.to_string())?;
+ verify_authentication_header(&data.settings, &headers, &body, parsed_payload.to_string())?;
info!("");
info!("Incoming webhook for \"{}\":", webhook_name);
}
}
}
+
+
+fn get_headers_hash_map(map: &HeaderMap) -> Result<HashMap<String, String>, HttpResponse> {
+ let mut headers = HashMap::new();
+
+ for (key, header_value) in map.iter() {
+ let key = key.as_str().to_string();
+ let value: String;
+ match header_value.to_str() {
+ Ok(header_value) => {
+ value = header_value.to_string()
+ },
+ Err(error) => {
+ let message = format!("Couldn't parse header: {}", error);
+ warn!("{}", message);
+ return Err(HttpResponse::Unauthorized().body(message));
+ }
+ };
+
+ headers.insert(key, value);
+ }
+
+ Ok(headers)
+}