diff options
| author | MatthieuCoder <matthieu@matthieu-dev.xyz> | 2022-12-31 17:07:30 +0400 |
|---|---|---|
| committer | MatthieuCoder <matthieu@matthieu-dev.xyz> | 2022-12-31 17:07:30 +0400 |
| commit | 65652932f77ce194a10cbc8dd42f3064e2c1a132 (patch) | |
| tree | 4ca18a9317c4e561e917e9dd0cf39b695b43bc34 /webhook/src | |
| parent | a16bafdf5b0ec52fa0d73458597eee7c34ea5e7b (diff) | |
updates and bazel removal
Diffstat (limited to 'webhook/src')
| -rw-r--r-- | webhook/src/config.rs | 19 | ||||
| -rw-r--r-- | webhook/src/handler/error.rs | 36 | ||||
| -rw-r--r-- | webhook/src/handler/handler.rs | 167 | ||||
| -rw-r--r-- | webhook/src/handler/make_service.rs | 34 | ||||
| -rw-r--r-- | webhook/src/handler/mod.rs | 7 | ||||
| -rw-r--r-- | webhook/src/handler/signature.rs | 41 | ||||
| -rw-r--r-- | webhook/src/handler/tests/handler.rs | 0 | ||||
| -rw-r--r-- | webhook/src/handler/tests/handler_integration.rs | 255 | ||||
| -rw-r--r-- | webhook/src/handler/tests/mod.rs | 4 | ||||
| -rw-r--r-- | webhook/src/handler/tests/signature.rs | 33 | ||||
| -rw-r--r-- | webhook/src/handler/tests/utils.rs | 15 | ||||
| -rw-r--r-- | webhook/src/main.rs | 45 |
12 files changed, 0 insertions, 656 deletions
diff --git a/webhook/src/config.rs b/webhook/src/config.rs deleted file mode 100644 index a054d33..0000000 --- a/webhook/src/config.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::Deserialize; - -#[derive(Debug, Deserialize, Clone, Default)] -pub struct ServerSettings { - pub port: u16, - pub address: String, -} - -#[derive(Debug, Deserialize, Clone, Default)] -pub struct Discord { - pub public_key: String, - pub client_id: u32, -} - -#[derive(Debug, Deserialize, Clone, Default)] -pub struct Config { - pub server: ServerSettings, - pub discord: Discord, -} diff --git a/webhook/src/handler/error.rs b/webhook/src/handler/error.rs deleted file mode 100644 index d4fee07..0000000 --- a/webhook/src/handler/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -use hyper::{header::ToStrError, Body, Response, StatusCode}; - -pub struct WebhookError { - pub code: StatusCode, - pub message: String, -} - -impl WebhookError { - pub fn new(code: StatusCode, message: &str) -> WebhookError { - WebhookError { - code, - message: message.to_string(), - } - } -} - -impl Into<Response<Body>> for WebhookError { - fn into(self) -> Response<Body> { - Response::builder() - .status(self.code) - .body(self.message.into()) - .unwrap() - } -} - -impl From<hyper::Error> for WebhookError { - fn from(_: hyper::Error) -> Self { - WebhookError::new(StatusCode::BAD_REQUEST, "invalid request") - } -} - -impl From<ToStrError> for WebhookError { - fn from(_: ToStrError) -> Self { - WebhookError::new(StatusCode::BAD_REQUEST, "invalid request") - } -} diff --git a/webhook/src/handler/handler.rs b/webhook/src/handler/handler.rs deleted file mode 100644 index b25d270..0000000 --- a/webhook/src/handler/handler.rs +++ /dev/null @@ -1,167 +0,0 @@ -use super::error::WebhookError; -use super::signature::validate_signature; -use crate::config::Config; -use common::log::{debug, error}; -use common::nats_crate::Connection; -use common::payloads::SerializeHelper; -use ed25519_dalek::PublicKey; -use hyper::{ - body::{to_bytes, Bytes}, - service::Service, - Body, Method, Request, Response, StatusCode, -}; -use serde::{Deserialize, Serialize}; -use std::{ - future::Future, - pin::Pin, - str::from_utf8, - sync::Arc, - task::{Context, Poll}, - time::Duration, -}; -use twilight_model::application::interaction::Interaction; -use twilight_model::gateway::event::Event; -use twilight_model::gateway::payload::InteractionCreate; - -/// Hyper service used to handle the discord webhooks -#[derive(Clone)] -pub struct HandlerService { - pub config: Arc<Config>, - pub nats: Arc<Connection>, - pub public_key: Arc<PublicKey>, -} - -impl HandlerService { - async fn check_request(&self, req: Request<Body>) -> Result<Bytes, WebhookError> { - if req.method() == Method::POST { - let signature = if let Some(sig) = req.headers().get("X-Signature-Ed25519") { - sig.to_owned() - } else { - return Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "missing signature header", - )); - }; - - let timestamp = if let Some(timestamp) = req.headers().get("X-Signature-Timestamp") { - timestamp.to_owned() - } else { - return Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "missing timestamp header", - )); - }; - let data = to_bytes(req.into_body()).await?; - - if validate_signature( - &self.public_key, - &[timestamp.as_bytes().to_vec(), data.to_vec()].concat(), - signature.to_str()?, - ) { - Ok(data) - } else { - Err(WebhookError::new( - StatusCode::UNAUTHORIZED, - "invalid signature", - )) - } - } else { - Err(WebhookError::new(StatusCode::NOT_FOUND, "not found")) - } - } - - async fn process_request( - &mut self, - req: Request<Body>, - ) -> Result<Response<Body>, WebhookError> { - match self.check_request(req).await { - Ok(data) => { - let utf8 = from_utf8(&data); - match utf8 { - Ok(data) => match serde_json::from_str::<Interaction>(data) { - Ok(value) => match value { - Interaction::Ping(_) => Ok(Response::builder() - .header("Content-Type", "application/json") - .body(serde_json::to_string(&Ping { t: 1 }).unwrap().into()) - .unwrap()), - _ => { - debug!("calling nats"); - // this should hopefully not fail ? - let payload = - serde_json::to_string(&common::payloads::CachePayload { - tracing: common::payloads::Tracing { - node_id: "".to_string(), - span: None, - }, - data: SerializeHelper(Event::InteractionCreate( - Box::new(InteractionCreate(value)), - )), - }) - .unwrap(); - - match self.nats.request_timeout( - "nova.cache.dispatch.interaction_create", - payload, - Duration::from_secs(2), - ) { - Ok(response) => Ok(Response::builder() - .header("Content-Type", "application/json") - .body(Body::from(response.data)) - .unwrap()), - - Err(error) => { - error!("failed to request nats: {}", error); - Err(WebhookError::new( - StatusCode::INTERNAL_SERVER_ERROR, - "failed to request nats", - )) - } - } - } - }, - - Err(error) => { - error!("invalid json body: {}", error); - Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "invalid json body", - )) - } - }, - - Err(_) => Err(WebhookError::new(StatusCode::BAD_REQUEST, "not utf-8 body")), - } - } - Err(error) => Err(error), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Ping { - #[serde(rename = "type")] - t: i32, -} - -/// Implementation of the service -impl Service<Request<Body>> for HandlerService { - type Response = Response<Body>; - type Error = hyper::Error; - type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request<Body>) -> Self::Future { - let mut clone = self.clone(); - Box::pin(async move { - let response = clone.process_request(req).await; - - match response { - Ok(r) => Ok(r), - Err(e) => Ok(e.into()), - } - }) - } -} diff --git a/webhook/src/handler/make_service.rs b/webhook/src/handler/make_service.rs deleted file mode 100644 index 9e90436..0000000 --- a/webhook/src/handler/make_service.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::handler::HandlerService; -use crate::config::Config; -use hyper::service::Service; -use common::nats_crate::Connection; -use std::{ - future::{ready, Ready}, - sync::Arc, - task::{Context, Poll}, -}; -use ed25519_dalek::PublicKey; - -pub struct MakeSvc { - pub settings: Arc<Config>, - pub nats: Arc<Connection>, - pub public_key: Arc<PublicKey> -} - -impl<T> Service<T> for MakeSvc { - type Response = HandlerService; - type Error = std::io::Error; - type Future = Ready<Result<Self::Response, Self::Error>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { - Ok(()).into() - } - - fn call(&mut self, _: T) -> Self::Future { - ready(Ok(HandlerService { - config: self.settings.clone(), - nats: self.nats.clone(), - public_key: self.public_key.clone() - })) - } -} diff --git a/webhook/src/handler/mod.rs b/webhook/src/handler/mod.rs deleted file mode 100644 index 20a977a..0000000 --- a/webhook/src/handler/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod error; -mod handler; -pub mod make_service; -mod signature; - -#[cfg(test)] -pub mod tests; diff --git a/webhook/src/handler/signature.rs b/webhook/src/handler/signature.rs deleted file mode 100644 index 748fa6a..0000000 --- a/webhook/src/handler/signature.rs +++ /dev/null @@ -1,41 +0,0 @@ -use common::prometheus::{Counter, HistogramVec, labels, opts, register_counter, register_histogram_vec}; -use ed25519_dalek::PublicKey; -use ed25519_dalek::Verifier; -use ed25519_dalek::Signature; -use std::convert::TryInto; - -lazy_static::lazy_static! { - static ref SIGNATURE_TIME_HISTOGRAM: HistogramVec = register_histogram_vec!( - "nova_webhook_signature_time", - "The time taken by the signature verification", - &["signature"] - ).unwrap(); - - static ref SIGNATURE_COUNTER: Counter = register_counter!(opts!( - "nova_webhook_signatures_verify", - "number of signatures verification issued by the service", - labels! {"handler" => "webhook_main"} - )).unwrap(); -} - -fn demo<T, const N: usize>(v: Vec<T>) -> [T; N] { - v.try_into() - .unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len())) -} - -pub fn validate_signature(public_key: &PublicKey, data: &Vec<u8>, hex_signature: &str) -> bool { - SIGNATURE_COUNTER.inc(); - let timer = SIGNATURE_TIME_HISTOGRAM.with_label_values(&["webhook_main"]).start_timer(); - - let signature_result = hex::decode(hex_signature); - - let mut result = false; - if let Ok(signature) = signature_result { - let sig = Signature::from(demo(signature)); - - result = public_key.verify(data, &sig).is_ok(); - } - - timer.observe_duration(); - result -} diff --git a/webhook/src/handler/tests/handler.rs b/webhook/src/handler/tests/handler.rs deleted file mode 100644 index e69de29..0000000 --- a/webhook/src/handler/tests/handler.rs +++ /dev/null diff --git a/webhook/src/handler/tests/handler_integration.rs b/webhook/src/handler/tests/handler_integration.rs deleted file mode 100644 index b5204fb..0000000 --- a/webhook/src/handler/tests/handler_integration.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::time::Duration; - -use ctor; -use hyper::{Body, Method, Request, StatusCode}; -use lazy_static::lazy_static; -use serde_json::json; -use ed25519_dalek::Keypair; - -use common::{ - config::test_init, - nats_crate::Connection, - testcontainers::{Image, images::generic::WaitFor}, -}; -use common::{ - config::Settings, - log::info, - testcontainers::{clients::Cli, Container, Docker, images::generic::GenericImage}, -}; - -use crate::{ - config::Config, - handler::tests::utils::{generate_keypair, sign_message}, - start, -}; - -const fn nats_image<'a>() -> &'a str { - #[cfg(all(unix, target_arch = "x86_64"))] - return "amd64/nats"; - #[cfg(all(unix, target_arch = "aarch64"))] - return "arm64v8/nats"; - #[cfg(all(target_arch = "x86_64", target_os = "windows"))] - return "winamd64/nats"; -} - -static mut NATS: Option<Container<Cli, GenericImage>> = None; -static mut SETTINGS: Option<Settings<Config>> = None; - -lazy_static! { - static ref TEST_KEYPAIR: Keypair = generate_keypair(); - static ref DOCKER: Cli = Cli::default(); -} - -#[ctor::ctor] -unsafe fn init() { - test_init(); - let image = GenericImage::new(nats_image()) - .with_wait_for(WaitFor::message_on_stderr("Server is ready")); - - let container = DOCKER.run(image); - container.start(); - container.image().wait_until_ready(&container); - container.get_host_port(4222).unwrap(); - - let port = container.get_host_port(4222).unwrap(); - NATS = Some(container); - SETTINGS = Some(common::config::Settings { - config: crate::config::Config { - server: crate::config::ServerSettings { - port: 5003, - address: "0.0.0.0".to_string(), - }, - discord: crate::config::Discord { - public_key: hex::encode(TEST_KEYPAIR.public.clone()), - client_id: 0, - }, - }, - redis: common::redis::RedisConfiguration { - url: "".to_string(), - }, - monitoring: common::monitoring::MonitoringConfiguration { - enabled: false, - address: None, - port: None, - }, - nats: common::nats::NatsConfiguration { - client_cert: None, - root_cert: None, - jetstream_api_prefix: None, - max_reconnects: None, - reconnect_buffer_size: None, - tls: None, - client_name: None, - tls_required: None, - host: format!("localhost:{}", port), - }, - }); - let settings = (&mut SETTINGS).as_ref().unwrap(); - - std::thread::spawn(move || { - let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(start(settings.clone())); - }); - std::thread::sleep(Duration::from_secs(3)); -} - -#[ctor::dtor] -unsafe fn destroy() { - let nats = (&mut NATS).as_ref().unwrap(); - nats.stop(); -} - -#[tokio::test] -async fn respond_to_pings() { - let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1, "channel_id": "123" }).to_string(); - let timestamp = "my datetime :)"; - let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, &TEST_KEYPAIR); - - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", signature) - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - - assert_eq!(result.status(), StatusCode::OK); -} - -#[tokio::test] -async fn deny_invalid_signatures() { - let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1, "channel_id": "123" }).to_string(); - let timestamp = "my datetime :)"; - - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", "inva&lid signature :)") - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - assert_eq!(result.status(), StatusCode::UNAUTHORIZED); -} - -#[tokio::test] -async fn response_500_when_no_nats_response() { - let ping = json!({ - "type": 2, - "id": "0", - "application_id": "0", - "token": "random token", - "version": 1, - "channel_id": "123", - "data": { - "id": "0", - "name": "command" - } - }).to_string(); - - let timestamp = "my datetime :)"; - let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, &TEST_KEYPAIR); - - // we must timeout - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", signature) - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - assert_eq!(result.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[tokio::test] -async fn respond_from_nats_response() { - let nats: Connection; - unsafe { - nats = SETTINGS.clone().unwrap().nats.into(); - } - let sub = nats.subscribe("nova.cache.dispatch.interaction").unwrap(); - let ping = json!({ - "type": 2, - "id": "0", - "application_id": "0", - "token": "random token", - "version": 1, - "channel_id": "123", - "data": { - "id": "0", - "name": "command" - } - }).to_string(); - - let timestamp = "my datetime :)"; - let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, &TEST_KEYPAIR); - - sub.with_handler(move |msg| { - info!("Received {}", &msg); - msg.respond("ok :)").unwrap(); - Ok(()) - }); - - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", signature) - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - assert_eq!(result.status(), StatusCode::OK); -} - -#[tokio::test] -async fn response_400_when_invalid_json_body() { - let ping = "{".to_string(); - let timestamp = "my datetime :)"; - let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, &TEST_KEYPAIR); - - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", signature) - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - assert_eq!(result.status(), StatusCode::BAD_REQUEST); -} - -#[tokio::test] -async fn response_400_when_invalid_utf8_body() { - // invalid 2 octet sequence - let ping = vec![0xc3, 0x28]; - - let timestamp = "my datetime :)"; - let signature_data = [timestamp.as_bytes().to_vec(), ping.to_vec()].concat(); - let signature = sign_message(signature_data, &TEST_KEYPAIR); - - let req = Request::builder() - .method(Method::POST) - .uri("http://localhost:5003/") - .header("X-Signature-Ed25519", signature) - .header("X-Signature-Timestamp", timestamp) - .body(Body::from(ping.clone())) - .expect("request builder"); - let client = hyper::client::Client::new(); - let result = client.request(req).await.unwrap(); - assert_eq!(result.status(), StatusCode::BAD_REQUEST); -} diff --git a/webhook/src/handler/tests/mod.rs b/webhook/src/handler/tests/mod.rs deleted file mode 100644 index 589ad52..0000000 --- a/webhook/src/handler/tests/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod handler_integration; -pub mod signature; -pub mod utils; -pub mod handler; diff --git a/webhook/src/handler/tests/signature.rs b/webhook/src/handler/tests/signature.rs deleted file mode 100644 index 490143b..0000000 --- a/webhook/src/handler/tests/signature.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::handler::signature::validate_signature; -use ed25519_dalek::PublicKey; - -#[test] -fn validate_signature_test() { - let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; - let content = "message de test incroyable".as_bytes().to_vec(); - let public_key = PublicKey::from_bytes(&hex::decode("eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f").unwrap()).unwrap(); - - assert!(validate_signature(&public_key, &content, signature)) -} - -#[test] -fn validate_signature_reverse_test() { - let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; - let public_key = PublicKey::from_bytes(&hex::decode("c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044").unwrap()).unwrap(); - - let content = "ceci est un test qui ne fonctionnera pas!" - .as_bytes() - .to_vec(); - assert!(!validate_signature(&public_key, &content, signature)) -} - -#[test] -fn invalid_hex() { - let signature = "zzz"; - let public_key = PublicKey::from_bytes(&hex::decode("c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044").unwrap()).unwrap(); - - let content = "ceci est un test qui ne fonctionnera pas!" - .as_bytes() - .to_vec(); - assert!(!validate_signature(&public_key, &content, signature)) -}
\ No newline at end of file diff --git a/webhook/src/handler/tests/utils.rs b/webhook/src/handler/tests/utils.rs deleted file mode 100644 index 5e59f09..0000000 --- a/webhook/src/handler/tests/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -use rand::rngs::OsRng; -use ed25519_dalek::{Signer, Keypair, Signature}; - -pub fn generate_keypair() -> Keypair { - let mut csprng = OsRng{}; - Keypair::generate(&mut csprng) -} - -pub fn sign_message( - message: Vec<u8>, - keypair: &Keypair, -) -> String { - let signature: Signature = keypair.sign(&message); - return hex::encode(signature.to_bytes()); -}
\ No newline at end of file diff --git a/webhook/src/main.rs b/webhook/src/main.rs deleted file mode 100644 index 00d42ee..0000000 --- a/webhook/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::{net::ToSocketAddrs, sync::Arc};
-mod config;
-mod handler;
-use crate::handler::make_service::MakeSvc;
-
-use crate::config::Config;
-use common::config::Settings;
-use common::log::{error, info};
-use ed25519_dalek::PublicKey;
-use hyper::Server;
-
-#[tokio::main]
-async fn main() {
- let settings: Settings<Config> = Settings::new("webhook").unwrap();
- start(settings).await;
-}
-
-async fn start(settings: Settings<Config>) {
- let addr = format!(
- "{}:{}",
- settings.config.server.address, settings.config.server.port
- )
- .to_socket_addrs()
- .unwrap()
- .next()
- .unwrap();
-
- info!(
- "Starting server on {}:{}",
- settings.config.server.address, settings.config.server.port
- );
-
- let config = Arc::new(settings.config);
- let public_key =
- Arc::new(PublicKey::from_bytes(&hex::decode(&config.discord.public_key).unwrap()).unwrap());
- let server = Server::bind(&addr).serve(MakeSvc {
- settings: config,
- nats: Arc::new(settings.nats.into()),
- public_key: public_key,
- });
-
- if let Err(e) = server.await {
- error!("server error: {}", e);
- }
-}
|
