From 81be74b4ab69e05e163b3f2361ecefdd13fc59b9 Mon Sep 17 00:00:00 2001 From: Jerboa-app Date: Fri, 5 Jan 2024 11:41:24 +0000 Subject: [PATCH] support https with certificate path options http as a cargo build feature --- Cargo.toml | 8 +++ README.md | 31 ++++++++++-- src/lib.rs | 3 ++ src/main_http.rs | 32 ++++++++++++ src/pulse-ssl.rs | 120 --------------------------------------------- src/server.rs | 61 +++++++++++++++++++++-- src/server_http.rs | 100 +++++++++++++++++++++++++++++++++++++ 7 files changed, 226 insertions(+), 129 deletions(-) create mode 100644 src/main_http.rs delete mode 100644 src/pulse-ssl.rs create mode 100644 src/server_http.rs diff --git a/Cargo.toml b/Cargo.toml index c01a707..138afab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,18 @@ edition="2021" name = "pulse" path = "src/main.rs" +[[bin]] +name = "pulse_http" +path = "src/main_http.rs" +required-features=["http"] + [[bin]] name = "post_discord" path = "src/post_discord.rs" +[features] +http = [] + [dependencies] tokio = { version = "1", features = ["full"] } axum = "0.6.20" diff --git a/README.md b/README.md index 5c3bde2..a55319e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,34 @@ -### Pulse -#### A work in progress information bot linking Discord and Github, with more to come +## Pulse +### A work in progress information bot linking Discord and Github, with more to come - for now uses a Discord webhook for only posting messages - recieves POST requests (e.g. from github webhooks) to be processed into custom messages, which are then POST'd to Discord - so we can format the Github POST content to our hearts content -# To come +### Roadmap -- [ ] support for https POST reciepts (does work, just need actual certs, and to bundle them in) -- [ ] verify POST's are from github using the webhook secret +- [x] support for https POST receipts +- [x] support for http POST receipts (as a cargo build option) +- [x] verify POST's are from github using the webhook secret - [ ] Release formatting - [ ] Pre-release formatting + +### Setup + +### Example Google Cloud instance (free tier) + +### https certificate setup + +#### Self signed (useful for localhost testing) + +- You can use the bash script ```certs/gen.sh``` to generate a key/cert pair with openssl + +#### Production; from authority + +- get a domain (e.g. from squarespace) +- create a custom DNS record, e.g. + - ``` + your.domain.somwhere A 1 hour google.cloud.instance.ip + ``` +- Use [Let's Encrypts](https://letsencrypt.org/) recommendation of [certbot](https://certbot.eff.org/) it really is very easy + - You will need to enable http in the cloud instance firewall for provisioning as well as https \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 5a815cc..35d8483 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,9 @@ pub mod discord; pub mod web; pub mod server; +#[cfg(feature = "http")] +pub mod server_http; + const DEBUG: bool = true; pub fn debug(msg: String, context: Option) diff --git a/src/main_http.rs b/src/main_http.rs new file mode 100644 index 0000000..cb45290 --- /dev/null +++ b/src/main_http.rs @@ -0,0 +1,32 @@ +#[cfg(feature = "http")] +use pulse::server_http::serve; + +#[cfg(feature = "http")] +#[tokio::main] +async fn main() { + + let args: Vec = std::env::args().collect(); + + let token = if args.iter().any(|x| x == "-t") + { + let i = args.iter().position(|x| x == "-t").unwrap(); + + if i+1 < args.len() + { + args[i+1].clone() + } + else + { + println!("Authentication token not provided, please provide -t token"); + std::process::exit(1); + } + } + else + { + println!("Authentication token not provided, please provide -t token"); + std::process::exit(1); + }; + + serve(token).await; + +} \ No newline at end of file diff --git a/src/pulse-ssl.rs b/src/pulse-ssl.rs deleted file mode 100644 index c30e278..0000000 --- a/src/pulse-ssl.rs +++ /dev/null @@ -1,120 +0,0 @@ -mod throttle; - -use axum::ServiceExt; -use reqwest::get; -use throttle::throttle::{IpThrottler, handle_throttle}; - -use axum::http::{StatusCode, HeaderMap}; -use axum::response::IntoResponse; - -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; - -use axum:: -{ - routing::post, - Router, - http::Request, - middleware::{self, Next}, - response::{Response, Json}, - body::{Body, Bytes}, -}; - -use axum_server::tls_rustls::RustlsConfig; - -use openssl::sha::sha256; - -use serde_json::json; - -#[tokio::main] -async fn main() { - - let args: Vec = std::env::args().collect(); - - let port = if args.iter().any(|x| x == "-p") - { - let i = args.iter().position(|x| x == "-p").unwrap(); - if i+1 < args.len() - { - args[i+1].parse::().unwrap() - } - else - { - 3030 - } - } - else - { - 3030 - }; - - let ip = if args.iter().any(|x| x == "-t") - { - Ipv4Addr::new(127,0,0,1) - } - else - { - Ipv4Addr::new(0,0,0,0) - }; - - let requests: IpThrottler = IpThrottler::new - ( - 10.0, - 5000 - ); - - // wrap the hashmap into a Arc object, which may be cloned later (with all clones referencing the origin data) - // further wrap in a mutex so each thread can lock it - // when locked the mutex will release when going out of scope - let state = Arc::new(Mutex::new(requests)); - - // configure https - - let config = RustlsConfig::from_pem_file( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("certs") - .join("cert.pem"), - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("certs") - .join("key.pem"), - ) - .await - .unwrap(); - - let app = Router::new() - .route("/", post(|| async move { })) - .layer(middleware::from_fn(reflect)); - - let addr = SocketAddr::new(IpAddr::V4(ip), port); - - axum_server::bind_rustls(addr, config) - .serve(app.into_make_service()) - .await - .unwrap(); - - async fn reflect - ( - headers: HeaderMap, - request: Request, - next: Next - ) -> Result - where B: axum::body::HttpBody - { - let (_parts, body) = request.into_parts(); - - let bytes = match body.collect().await { - Ok(collected) => collected.to_bytes(), - Err(_) => { - return Err(StatusCode::BAD_REQUEST) - } - }; - - let msg = std::str::from_utf8(&bytes).unwrap().to_string(); - - println!("You sent:\n{}",msg); - Ok(format!("You sent:\n{}",msg).into_response()) - - } - -} \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 605c2b0..40b6e46 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,6 +5,7 @@ use crate::web::response::github_verify::github_verify; use std::clone; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use axum::extract::State; @@ -14,6 +15,7 @@ use axum:: Router, middleware }; +use axum_server::tls_rustls::RustlsConfig; pub struct Server { @@ -62,10 +64,27 @@ impl Server self.addr } - pub async fn serve(self: Server) + pub async fn serve(self: Server, cert_path: String, key_path: String) { - axum::Server::bind(&self.addr) - .serve(self.router.into_make_service_with_connect_info::()) + + // configure https + + let config = match RustlsConfig::from_pem_file( + PathBuf::from(cert_path.clone()), + PathBuf::from(key_path.clone()) + ) + .await + { + Ok(c) => c, + Err(e) => + { + println!("error while reading certificates in {} and key {}\n{}", cert_path, key_path, e); + std::process::exit(1); + } + }; + + axum_server::bind_rustls(self.addr, config) + .serve(self.router.into_make_service()) .await .unwrap(); } @@ -93,8 +112,42 @@ pub async fn serve(token: String) { 3030 }; + let cert_path = if args.iter().any(|x| x == "-c") + { + let i = args.iter().position(|x| x == "-c").unwrap(); + if i+1 < args.len() + { + args[i+1].clone() + } + else + { + "./cert.pem".to_string() + } + } + else + { + "./cert.pem".to_string() + }; + + let key_path = if args.iter().any(|x| x == "-k") + { + let i = args.iter().position(|x| x == "-k").unwrap(); + if i+1 < args.len() + { + args[i+1].clone() + } + else + { + "./key.pem".to_string() + } + } + else + { + "./key.pem".to_string() + }; + let server = Server::new(127,0,0,1,port,token); - server.serve().await + server.serve(cert_path, key_path).await } \ No newline at end of file diff --git a/src/server_http.rs b/src/server_http.rs new file mode 100644 index 0000000..baa153d --- /dev/null +++ b/src/server_http.rs @@ -0,0 +1,100 @@ + +use crate::web::throttle::{IpThrottler, handle_throttle}; +use crate::web::response::util::{reflect, stdout_log}; +use crate::web::response::github_verify::github_verify; + +use std::clone; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::{Arc, Mutex}; + +use axum::extract::State; +use axum:: +{ + routing::post, + Router, + middleware +}; + +pub struct ServerHttp +{ + addr: SocketAddr, + router: Router +} + +impl ServerHttp +{ + pub fn new + ( + a: u8, + b: u8, + c: u8, + d: u8, + port: u16, + token: String + ) + -> ServerHttp + { + + let requests: IpThrottler = IpThrottler::new + ( + 10.0, + 5000 + ); + + let throttle_state = Arc::new(Mutex::new(requests)); + + let authenticated_state = token; + + + ServerHttp + { + addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(a,b,c,d)), port), + router: Router::new() + .route("/", post(|| async move { })) + .layer(middleware::from_fn_with_state(authenticated_state.clone(), github_verify)) + .layer(middleware::from_fn_with_state(throttle_state.clone(), handle_throttle)) + + } + } + + pub fn get_addr(self: ServerHttp) -> SocketAddr + { + self.addr + } + + pub async fn serve(self: ServerHttp) + { + axum::Server::bind(&self.addr) + .serve(self.router.into_make_service_with_connect_info::()) + .await + .unwrap(); + } + +} + +pub async fn serve(token: String) { + + let args: Vec = std::env::args().collect(); + + let port = if args.iter().any(|x| x == "-p") + { + let i = args.iter().position(|x| x == "-p").unwrap(); + if i+1 < args.len() + { + args[i+1].parse::().unwrap() + } + else + { + 3030 + } + } + else + { + 3030 + }; + + let server = ServerHttp::new(127,0,0,1,port,token); + + server.serve().await + +} \ No newline at end of file