github: wip webhook support

This commit is contained in:
Antoine Martin 2021-09-12 02:16:54 +02:00
parent 0ce80677aa
commit c24ae3e7a9
7 changed files with 607 additions and 6 deletions

420
Cargo.lock generated
View file

@ -34,6 +34,27 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002"
[[package]]
name = "async-stream"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.51"
@ -45,6 +66,26 @@ dependencies = [
"syn",
]
[[package]]
name = "atomic"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281"
dependencies = [
"autocfg",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@ -77,6 +118,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "binascii"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -174,6 +221,17 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
[[package]]
name = "cookie"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.1"
@ -199,6 +257,16 @@ dependencies = [
"libc",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
@ -231,6 +299,39 @@ dependencies = [
"const-oid",
]
[[package]]
name = "devise"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595"
dependencies = [
"devise_codegen",
"devise_core",
]
[[package]]
name = "devise_codegen"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2"
dependencies = [
"devise_core",
"quote",
]
[[package]]
name = "devise_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0"
dependencies = [
"bitflags",
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -296,6 +397,20 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "figment"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df"
dependencies = [
"atomic",
"pear",
"serde",
"toml",
"uncased",
"version_check",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -440,6 +555,19 @@ dependencies = [
"slab",
]
[[package]]
name = "generator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"winapi",
]
[[package]]
name = "generic-array"
version = "0.14.4"
@ -474,6 +602,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gloo-timers"
version = "0.2.1"
@ -533,6 +667,22 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"digest",
]
[[package]]
name = "http"
version = "0.2.4"
@ -635,6 +785,12 @@ dependencies = [
"unindent",
]
[[package]]
name = "inlinable_string"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77"
[[package]]
name = "instant"
version = "0.1.10"
@ -723,6 +879,19 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "loom"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2111607c723d7857e0d8299d5ce7a0bf4b844d3e44f8de136b13da513eaf8fc4"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"serde",
"serde_json",
]
[[package]]
name = "lru"
version = "0.6.6"
@ -848,6 +1017,26 @@ dependencies = [
"winapi",
]
[[package]]
name = "multer"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680"
dependencies = [
"bytes",
"encoding_rs",
"futures-util",
"http",
"httparse",
"log",
"mime",
"spin",
"tokio",
"tokio-util",
"twoway",
"version_check",
]
[[package]]
name = "native-tls"
version = "0.2.8"
@ -986,6 +1175,29 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "pear"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -1102,15 +1314,33 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]]
name = "prololo-reborn"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"hex",
"hmac",
"matrix-sdk",
"rocket",
"serde",
"serde_json",
"serde_yaml",
"sha2",
"tokio",
"tracing",
"tracing-subscriber",
@ -1216,6 +1446,26 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ref-cast"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "regex"
version = "1.5.4"
@ -1283,6 +1533,88 @@ dependencies = [
"winreg",
]
[[package]]
name = "rocket"
version = "0.5.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2"
dependencies = [
"async-stream",
"async-trait",
"atomic",
"atty",
"binascii",
"bytes",
"either",
"figment",
"futures",
"indexmap",
"log",
"memchr",
"multer",
"num_cpus",
"parking_lot",
"pin-project-lite",
"rand 0.8.4",
"ref-cast",
"rocket_codegen",
"rocket_http",
"serde",
"state",
"tempfile",
"time",
"tokio",
"tokio-stream",
"tokio-util",
"ubyte",
"version_check",
"yansi",
]
[[package]]
name = "rocket_codegen"
version = "0.5.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb"
dependencies = [
"devise",
"glob",
"indexmap",
"proc-macro2",
"quote",
"rocket_http",
"syn",
"unicode-xid",
]
[[package]]
name = "rocket_http"
version = "0.5.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd"
dependencies = [
"cookie",
"either",
"http",
"hyper",
"indexmap",
"log",
"memchr",
"mime",
"parking_lot",
"pear",
"percent-encoding",
"pin-project-lite",
"ref-cast",
"serde",
"smallvec",
"stable-pattern",
"state",
"time",
"tokio",
"uncased",
]
[[package]]
name = "ruma"
version = "0.4.0"
@ -1516,6 +1848,12 @@ dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
@ -1532,6 +1870,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1702,6 +2046,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5"
[[package]]
name = "spki"
version = "0.4.0"
@ -1711,6 +2061,15 @@ dependencies = [
"der",
]
[[package]]
name = "stable-pattern"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045"
dependencies = [
"memchr",
]
[[package]]
name = "standback"
version = "0.2.17"
@ -1720,6 +2079,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "state"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5"
dependencies = [
"loom",
]
[[package]]
name = "stdweb"
version = "0.4.20"
@ -1941,6 +2309,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.6.8"
@ -2061,12 +2440,47 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "twoway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47"
dependencies = [
"memchr",
"unchecked-index",
]
[[package]]
name = "typenum"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "ubyte"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe"
dependencies = [
"serde",
]
[[package]]
name = "uncased"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
dependencies = [
"serde",
"version_check",
]
[[package]]
name = "unchecked-index"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
name = "unicode-bidi"
version = "0.3.6"
@ -2287,6 +2701,12 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.4.1"

View file

@ -8,8 +8,12 @@ resolver = "2"
[dependencies]
anyhow = "1.0"
serde = "1.0"
hex = "0.4"
hmac = "0.11"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
serde_yaml = "0.8"
sha2 = "0.9"
tokio = { version = "1.0", features = [ "full" ] }
tracing-subscriber = "0.2"
tracing = "0.1"
@ -25,3 +29,8 @@ features = [ "native-tls" ]
version = "3.0.0-beta.4"
default-features = false
features = ["cargo", "derive", "std"]
[dependencies.rocket]
version = "0.5.0-rc.1"
# don't need private-cookies
default-features = false

View file

@ -22,7 +22,7 @@ pub struct Prololo {
impl Prololo {
/// Creates a new [`Prololo`] bot and builds a [`matrix_sdk::Client`] using the provided
/// [`Config`].
/// [`ProloloConfig`].
///
/// The [`Client`] is only initialized, not ready to be used yet.
pub fn new(config: ProloloConfig) -> anyhow::Result<Self> {

View file

@ -3,6 +3,7 @@ use std::io::BufReader;
use std::path::PathBuf;
use clap::Clap;
use rocket::routes;
mod bot;
use bot::Prololo;
@ -10,15 +11,18 @@ use bot::Prololo;
mod config;
use config::ProloloConfig;
mod webhooks;
use webhooks::github_webhook;
#[derive(Clap)]
#[clap(version = "0.1")]
struct Opts {
/// File where session information will be saved
/// Configuration file for prololo
#[clap(short, long, parse(from_os_str))]
config: PathBuf,
}
#[tokio::main]
#[rocket::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
@ -28,7 +32,8 @@ async fn main() -> anyhow::Result<()> {
let prololo = Prololo::new(config)?;
prololo.init().await?;
prololo.run().await;
tokio::spawn(async move { prololo.run().await });
Ok(())
let rocket = rocket::build().mount("/", routes![github_webhook]);
rocket.launch().await.map_err(|err| anyhow::anyhow!(err))
}

70
src/webhooks/github.rs Normal file
View file

@ -0,0 +1,70 @@
use anyhow::anyhow;
use rocket::{
http::Status,
request::{FromRequest, Outcome},
Request,
};
use serde::Deserialize;
mod signing;
use signing::SignedGitHubPayload;
use tracing::info;
const X_GITHUB_EVENT: &str = "X-GitHub-Event";
struct GitHubSecret(String);
#[rocket::post("/api/webhooks/github", data = "<payload>")]
pub fn github_webhook(event: GitHubEventType, payload: SignedGitHubPayload) -> &'static str {
info!(
"received event {:?} with signed payload:\n{}",
event, payload.0
);
"OK"
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GitHubEventType {
Create,
Issues,
IssueComment,
Push,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for GitHubEventType {
type Error = anyhow::Error;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let event_types = request.headers().get(X_GITHUB_EVENT).collect::<Vec<_>>();
if event_types.len() != 1 {
return Outcome::Failure((
Status::BadRequest,
anyhow!("request header needs exactly one event type"),
));
}
let event_type = event_types[0];
match serde_json::from_str::<GitHubEventType>(event_type) {
Ok(ev_type) => Outcome::Success(ev_type),
Err(e) => Outcome::Failure((Status::BadRequest, anyhow!(e))),
}
}
}
enum GitHubEvent {
Create { ref_type: RefType },
Issues,
IssueComment,
Push,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum RefType {
Branch,
Tag,
}

View file

@ -0,0 +1,95 @@
use std::io;
use std::ops::{Deref, DerefMut};
use anyhow::anyhow;
use rocket::{
data::{ByteUnit, FromData, Outcome},
http::{ContentType, Status},
Data, Request, State,
};
use crate::webhooks::github::GitHubSecret;
const X_GITHUB_SIGNATURE: &str = "X-Hub-Signature-256";
fn validate_signature(secret: &str, signature: &str, data: &str) -> bool {
use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect("this should never fail");
mac.update(data.as_bytes());
match hex::decode(signature) {
Ok(bytes) => mac.verify(&bytes).is_ok(),
Err(_) => false,
}
}
pub struct SignedGitHubPayload(pub String);
// FIXME: probably not needed
impl Deref for SignedGitHubPayload {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
impl DerefMut for SignedGitHubPayload {
fn deref_mut(&mut self) -> &mut String {
&mut self.0
}
}
const LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
// Tracking issue for chaining Data guards to avoid reimplementing all this:
// https://github.com/SergioBenitez/Rocket/issues/775
#[rocket::async_trait]
impl<'r> FromData<'r> for SignedGitHubPayload {
type Error = anyhow::Error;
async fn from_data(request: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
let json_ct = ContentType::new("application", "json");
if request.content_type() != Some(&json_ct) {
return Outcome::Failure((Status::BadRequest, anyhow!("wrong content type")));
}
let signatures = request
.headers()
.get(X_GITHUB_SIGNATURE)
.collect::<Vec<_>>();
if signatures.len() != 1 {
return Outcome::Failure((
Status::BadRequest,
anyhow!("request header needs exactly one signature"),
));
}
let size_limit = request.limits().get("json").unwrap_or(LIMIT);
let content = match data.open(size_limit).into_string().await {
Ok(s) if s.is_complete() => s.into_inner(),
Ok(_) => {
let eof = io::ErrorKind::UnexpectedEof;
return Outcome::Failure((
Status::PayloadTooLarge,
io::Error::new(eof, "data limit exceeded").into(),
));
}
Err(e) => return Outcome::Failure((Status::BadRequest, e.into())),
};
let signature = signatures[0];
let secret = request.guard::<&State<GitHubSecret>>().await.unwrap();
if !validate_signature(&secret.0, signature, &content) {
return Outcome::Failure((Status::BadRequest, anyhow!("couldn't verify signature")));
}
Outcome::Success(SignedGitHubPayload(content))
}
}

2
src/webhooks/mod.rs Normal file
View file

@ -0,0 +1,2 @@
mod github;
pub use github::github_webhook;