Compare commits

..

15 commits

Author SHA1 Message Date
faba2949d2 cargo: Bump hmac and sha2 dependencies.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Bump the `hmac` dependency to `0.12`, therefor remove deprecated
`NewMac`. Further, bump the `sha2` dependency to `0.10`.
2023-06-11 22:37:25 +02:00
57d4f10b41 cargo: Bump clap to 4.3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Bump `clap` to latest version.
2023-06-11 18:01:37 +02:00
35b31b2a15 cargo: Cargo update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Update the build dependencies by `cargo update`.
2023-06-11 17:28:01 +02:00
71153b28ec cargo: Order dependencies alphabetically
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
To improve readability of dependencies order them alphabetically.
2023-06-11 17:28:01 +02:00
f6ec8af944 clippy: Fix clippy::get_first
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Fix the `clippy::get_first` lint.

Note clippy fails due to a false positive.
2023-06-11 17:27:18 +02:00
55c5134840 ci: Update CI config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-10-15 01:56:07 +02:00
f38c70373c metrics: Increase hooks_successful when done
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-23 15:20:39 +01:00
f25ee6b943 ci: run everything in pipeline in parallel
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-02-11 19:52:09 +01:00
976c25ba1a ci: Also execute the tests during ci
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-02-10 01:38:37 +01:00
81be79d46d ci: Add a basic ci config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Do things like build docs, check fmt, clippy and build in non-release
and release mode as well as the debian package. Include a badge which
reflects the status of the ci for the main branch to the readme.
2022-02-10 00:56:49 +01:00
f195162ce5 Update build dependencies
Use clap `v3.0`.
2022-01-07 11:50:52 +01:00
0312a600ed Add from annotation to inherited errors 2022-01-07 11:49:16 +01:00
f7aea10c6b Update build dependencies 2021-12-14 17:00:55 +01:00
620fa520ce Raise version to 0.1.6 2021-12-09 13:43:46 +01:00
856cdc9457 Update build dependencies
This needed also some adaption in order to use `clap 3.0.0-rc.0`.
2021-12-09 13:40:17 +01:00
9 changed files with 714 additions and 724 deletions

37
.woodpecker.yml Normal file
View file

@ -0,0 +1,37 @@
pipeline:
fmt:
group: default
image: rust_full
commands:
- cargo fmt --all -- --check
clippy:
group: default
image: rust_full
commands:
- cargo clippy --all-features
test:
group: default
image: rust_full
commands:
- cargo test
build:
group: default
image: rust_full
commands:
- cargo build
- cargo build --release
build-deb:
group: default
image: rust_full
commands:
- cargo deb
doc:
group: default
image: rust_full
commands:
- cargo doc

1267
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "webhookey"
version = "0.1.5"
version = "0.1.6"
authors = ["finga <webhookey@onders.org>"]
edition = "2021"
license = "GPL-3.0-or-later"
@ -11,24 +11,23 @@ description = "Trigger scripts via http(s) requests"
tls = ["rocket/tls"]
[dependencies]
anyhow = "1.0"
clap = { version = "4.3", features = ["derive"] }
dirs = "4.0"
env_logger = "0.9"
hex = "0.4"
hmac = "0.12"
ipnet = { version = "2.3", features = ["serde"] }
log = "0.4"
regex = "1.5"
rocket = "0.5.0-rc.1"
run_script = "0.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.8"
serde_regex = "1.1"
regex = "1.5"
dirs = "4.0"
anyhow = "1.0"
log = "0.4"
env_logger = "0.9"
hmac = "0.11"
sha2 = "0.9"
hex = "0.4"
ipnet = { version = "2.3", features = ["serde"] }
serde_yaml = "0.8"
sha2 = "0.10"
thiserror = "1.0"
run_script = "0.9"
clap = "3.0.0-beta.5"
base64 = "0.13"
[package.metadata.deb]
extended-description = "Webhookey receives requests in form of a so called Webhook as for example sent by Gitea. Those requests are matched against configured filters, if a filter matches, values from the header and the body can be passed to scripts as parameters which are then executed subsequently."

View file

@ -1,4 +1,6 @@
# Webhookey
![status-badge](https://ci.onders.org/api/badges/finga/webhookey/status.svg?branch=main)
Webhookey is a web server listening for
[webhooks](https://en.wikipedia.org/wiki/Webhook) as for example sent
by Gitea's webhooks. Further, Webhookey allows you to specify rules

View file

@ -1,74 +0,0 @@
use crate::WebhookeyError;
use base64;
use rocket::{
http::Status,
outcome::Outcome,
request::{self, FromRequest},
Request,
};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
fn decode_to_creds<T: Into<String>>(base64_encoded: T) -> Option<(String, String)> {
let decoded_creds = match base64::decode(base64_encoded.into()) {
Ok(cred_bytes) => String::from_utf8(cred_bytes).unwrap(),
Err(_) => return None,
};
if let Some((username, password)) = decoded_creds.split_once(":") {
Some((username.to_string(), password.to_string()))
} else {
None
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct BasicAuth {
username: String,
password: String,
}
impl BasicAuth {
pub fn new<T: Into<String>>(auth_header: T) -> Option<Self> {
let key = auth_header.into();
if key.len() < 7 || &key[..6] != "Basic " {
return None;
}
let (username, password) = decode_to_creds(&key[6..])?;
Some(Self { username, password })
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for BasicAuth {
type Error = WebhookeyError;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("Authorization").collect();
match keys.len() {
0 => Outcome::Forward(()),
1 => match BasicAuth::new(keys[0]) {
Some(auth_header) => Outcome::Success(auth_header),
None => Outcome::Failure((
Status::Unauthorized,
WebhookeyError::Unauthorized(
request
.client_ip()
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
),
)),
},
_ => Outcome::Failure((
Status::Unauthorized,
WebhookeyError::Unauthorized(
request
.client_ip()
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
),
)),
}
}
}

View file

@ -1,4 +1,4 @@
use clap::{crate_authors, crate_version, AppSettings, Parser};
use clap::Parser;
#[derive(Debug, Parser)]
pub enum Command {
@ -7,16 +7,10 @@ pub enum Command {
}
#[derive(Debug, Parser)]
#[clap(
version = crate_version!(),
author = crate_authors!(", "),
global_setting = AppSettings::InferSubcommands,
global_setting = AppSettings::PropagateVersion,
)]
pub struct Opts {
/// Provide a path to the configuration file
#[clap(short, long, value_name = "FILE")]
#[arg(short, long, value_name = "FILE")]
pub config: Option<String>,
#[clap(subcommand)]
#[command(subcommand)]
pub command: Option<Command>,
}

View file

@ -1,4 +1,4 @@
use crate::{auth::BasicAuth, filters::IpFilter, hooks::Hook};
use crate::{filters::IpFilter, hooks::Hook};
use anyhow::{bail, Result};
use log::info;
use serde::{Deserialize, Serialize};
@ -15,7 +15,6 @@ pub struct MetricsConfig {
#[serde(deny_unknown_fields)]
pub struct Config {
pub metrics: Option<MetricsConfig>,
pub auth: Option<BasicAuth>,
pub hooks: BTreeMap<String, Hook>,
}

View file

@ -1,10 +1,9 @@
use crate::{
auth::BasicAuth,
filters::{FilterType, IpFilter},
Config, Metrics, WebhookeyError,
};
use anyhow::{anyhow, bail, Result};
use hmac::{Hmac, Mac, NewMac};
use hmac::{Hmac, Mac};
use log::{debug, error, info, trace, warn};
use rocket::{
data::{FromData, ToByteUnit},
@ -45,7 +44,8 @@ fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> {
.map_err(|e| anyhow!("Could not create hasher with secret: {}", e))?;
mac.update(data);
let raw_signature = hex::decode(signature.as_bytes())?;
mac.verify(&raw_signature).map_err(|e| anyhow!("{}", e))
mac.verify_slice(&raw_signature)
.map_err(|e| anyhow!("{}", e))
}
#[derive(Debug, Deserialize, Serialize)]
@ -53,7 +53,6 @@ fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> {
pub struct Hook {
command: String,
signature: String,
auth: Option<BasicAuth>,
ip_filter: Option<IpFilter>,
secrets: Vec<String>,
filter: FilterType,
@ -89,7 +88,7 @@ impl Hook {
if let Some('}') = command_template.next() {
let expr = token.trim().split(' ').collect::<Vec<&str>>();
let replaced = match expr.get(0) {
let replaced = match expr.first() {
Some(&"header") => get_header_field(
headers,
expr.get(1).ok_or_else(|| {
@ -317,6 +316,8 @@ pub async fn receive_hook<'a>(
.fetch_add(1, Ordering::Relaxed);
}
}
metrics.hooks_successful.fetch_add(1, Ordering::Relaxed);
});
Status::Ok

View file

@ -1,4 +1,3 @@
mod auth;
mod cli;
mod config;
mod filters;
@ -24,9 +23,9 @@ pub enum WebhookeyError {
#[error("Could not evaluate filter request")]
InvalidFilter,
#[error("IO Error")]
Io(std::io::Error),
Io(#[from] std::io::Error),
#[error("Serde Error")]
Serde(serde_json::Error),
Serde(#[from] serde_json::Error),
}
pub fn get_string(data: &serde_json::Value) -> Result<String, WebhookeyError> {