Compare commits
3 commits
a8bc61f40c
...
e2d9789f9a
Author | SHA1 | Date | |
---|---|---|---|
e2d9789f9a | |||
a6b2127b0c | |||
0c68399210 |
11 changed files with 192 additions and 96 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
@ -26,6 +26,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_colours"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe"
|
||||
dependencies = [
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
|
@ -118,9 +127,11 @@ dependencies = [
|
|||
name = "boxy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi_colours",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bcrypt",
|
||||
"colour",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
|
@ -143,6 +154,12 @@ version = "3.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
@ -180,6 +197,15 @@ dependencies = [
|
|||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colour"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b536eebcabe54980476d120a182f7da2268fe02d22575cca99cee5fdda178280"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
@ -830,6 +856,15 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
|
@ -1286,6 +1321,22 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
|
@ -1295,6 +1346,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -21,4 +21,6 @@ serde = "1.0.219"
|
|||
base64 = "0.22.1"
|
||||
string-builder = "0.2.0"
|
||||
json = "0.12.4"
|
||||
ansi_colours = "1.2.3"
|
||||
colour = "2.1.0"
|
||||
|
||||
|
|
11
config.yaml
11
config.yaml
|
@ -1,7 +1,4 @@
|
|||
db:
|
||||
host: '127.0.0.1'
|
||||
user: 'postgres'
|
||||
password: 'trust'
|
||||
database: 'postgresql://postgres:trust@127.0.0.1'
|
||||
|
||||
proxy:
|
||||
listen: 127.0.0.1
|
||||
|
@ -11,10 +8,6 @@ api:
|
|||
listen: 127.0.0.1
|
||||
port: 8006
|
||||
|
||||
hosts: # ignore this it doesn't function
|
||||
- hostname: localhost:8005
|
||||
address: localhost:8000
|
||||
|
||||
clients:
|
||||
- name: 'eu-central-1' # Example Client right here (the client in this case would be for example the stereo.cat backend)
|
||||
secret: '$2b$12$5wH/0p702PPqVp7fCpVS4.1GA2/wAbk89w2nMjwuS8439OhjCUGbK' # password123
|
||||
hashed_secret: '$2b$12$5wH/0p702PPqVp7fCpVS4.1GA2/wAbk89w2nMjwuS8439OhjCUGbK' # password123
|
||||
|
|
|
@ -27,44 +27,34 @@ pub struct Api {
|
|||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Client {
|
||||
pub name: String,
|
||||
pub secret: String,
|
||||
pub hashed_secret: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Host {
|
||||
pub hostname: String,
|
||||
pub address: String,
|
||||
impl Client {
|
||||
pub async fn verify(us: String, config: Config) -> bool {
|
||||
// us stands for user:secret btw
|
||||
let us_split: Vec<&str> = us.split(':').collect();
|
||||
|
||||
let name = us_split.first().unwrap();
|
||||
let secret = us_split.last().unwrap();
|
||||
|
||||
let client: Client = config
|
||||
.clients
|
||||
.into_iter()
|
||||
.filter(|x| x.name.eq(name))
|
||||
.nth(0)
|
||||
.unwrap();
|
||||
|
||||
bcrypt::verify(secret, client.hashed_secret.as_str()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Config {
|
||||
pub db: Db,
|
||||
pub database: String,
|
||||
pub proxy: Proxy,
|
||||
pub api: Api,
|
||||
pub clients: Vec<Client>,
|
||||
pub hosts: Vec<Host>,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub async fn to_string(&self) -> String {
|
||||
let mut builder = String::new();
|
||||
|
||||
builder += format!(
|
||||
"host={} port={} user={} dbname={}",
|
||||
self.host,
|
||||
self.port.unwrap_or(5432),
|
||||
self.user,
|
||||
self.database.clone().unwrap_or(self.user.clone()),
|
||||
)
|
||||
.as_str();
|
||||
|
||||
match &self.password {
|
||||
Some(x) => builder += format!(" password={}", x).as_str(),
|
||||
None => {}
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{error::Error, net::{IpAddr, SocketAddr}};
|
||||
use std::{
|
||||
error::Error,
|
||||
net::IpAddr,
|
||||
};
|
||||
|
||||
use tokio_postgres::{Client, Socket, tls::MakeTlsConnect};
|
||||
use tokio_postgres::Client;
|
||||
|
||||
const ENDPOINT_TABLE: &str = "endpoints";
|
||||
const HOSTS_RELATION_TABLE: &str = "hosts";
|
||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -1,26 +1,28 @@
|
|||
mod config;
|
||||
mod db;
|
||||
mod server;
|
||||
mod services;
|
||||
mod types;
|
||||
|
||||
use std::{env, sync::Arc};
|
||||
use std::{env, process::exit, sync::Arc};
|
||||
|
||||
use bcrypt::{DEFAULT_COST, bcrypt};
|
||||
use bcrypt::DEFAULT_COST;
|
||||
use config::Config;
|
||||
use db::BoxyDatabase;
|
||||
use log::{error, info};
|
||||
use nanoid::nanoid;
|
||||
use ring::rand::SystemRandom;
|
||||
use log::{debug, error, info};
|
||||
use server::Server;
|
||||
use services::{api::ApiService, controller::ControllerService};
|
||||
use tokio::{fs::File, io::AsyncReadExt, sync::Mutex};
|
||||
use tokio_postgres::{NoTls, tls::NoTlsError};
|
||||
use types::Server;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::NoTls;
|
||||
|
||||
const VERSION: &str = "v0.1a";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::formatted_builder()
|
||||
.filter(None, log::LevelFilter::Info)
|
||||
.init();
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
unsafe { env::set_var("RUST_LOG", "info") };
|
||||
}
|
||||
|
||||
pretty_env_logger::init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
|
@ -37,26 +39,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
return Ok(());
|
||||
}
|
||||
"version" => {
|
||||
info!("Version: {}", VERSION);
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config::get().await.unwrap();
|
||||
|
||||
let db_string = config.db.to_string().await;
|
||||
debug!("Database URI: {}", config.database);
|
||||
|
||||
info!("Database string: {}", db_string);
|
||||
|
||||
let (client, conn) = tokio_postgres::connect(db_string.as_str(), NoTls)
|
||||
let (client, conn) = tokio_postgres::connect(config.database.as_str(), NoTls)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
error!("Error while connecting to database: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
info!("Connected to database.");
|
||||
|
||||
let database = Box::new(BoxyDatabase::new(client).await.unwrap());
|
||||
|
||||
let database_shared = Arc::new(Mutex::new(Box::leak(database)));
|
||||
|
@ -64,7 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let api_svc = ApiService {
|
||||
database: database_shared.clone(),
|
||||
config: config.clone(),
|
||||
_address: None
|
||||
_address: None,
|
||||
};
|
||||
|
||||
let svc = ControllerService {
|
||||
|
@ -80,10 +87,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
info!("Starting API server...");
|
||||
api_server.handle().await;
|
||||
});
|
||||
|
||||
// We don't put this on a separate thread because we'd be wasting the main thread.
|
||||
info!("Starting proxy server...");
|
||||
proxy_server.handle().await;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::error::Error;
|
||||
use std::{any::type_name_of_val, error::Error};
|
||||
|
||||
use http_body_util::{Either, Full};
|
||||
use hyper::{
|
||||
|
@ -8,7 +8,7 @@ use hyper::{
|
|||
service::{HttpService, Service},
|
||||
};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use log::error;
|
||||
use log::{error, info};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
pub type GeneralResponse = Response<GeneralBody>;
|
||||
|
@ -25,7 +25,7 @@ pub struct Server<S> {
|
|||
}
|
||||
|
||||
pub trait TcpIntercept {
|
||||
fn handle(&mut self, stream: &TcpStream);
|
||||
fn stream(&mut self, stream: &TcpStream);
|
||||
}
|
||||
|
||||
impl<S> Server<S>
|
||||
|
@ -39,13 +39,20 @@ where
|
|||
<S as HttpService<Incoming>>::Future: Send,
|
||||
{
|
||||
pub async fn handle(&self) {
|
||||
info!(
|
||||
"Server started at http://{} for service: {}",
|
||||
self.listener.local_addr().unwrap(),
|
||||
type_name_of_val(&self.service)
|
||||
);
|
||||
|
||||
loop {
|
||||
let (stream, _) = self.listener.accept().await.unwrap();
|
||||
|
||||
let mut svc_clone = self.service.clone();
|
||||
svc_clone.handle(&stream);
|
||||
svc_clone.stream(&stream);
|
||||
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = http1::Builder::new()
|
||||
.writev(false)
|
|
@ -1,3 +1,3 @@
|
|||
pub mod proxy;
|
||||
pub mod controller;
|
||||
pub mod api;
|
||||
pub mod controller;
|
||||
pub mod proxy;
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
use std::{hash, net::{IpAddr, SocketAddr}, pin::Pin, sync::Arc};
|
||||
use std::{net::IpAddr, pin::Pin, sync::Arc};
|
||||
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use bcrypt::DEFAULT_COST;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming}, service::Service, Method, Request, Response, StatusCode, Uri
|
||||
Method, Request, Response, StatusCode,
|
||||
body::{Bytes, Incoming},
|
||||
service::Service,
|
||||
};
|
||||
use log::{info, trace};
|
||||
use log::{debug, warn};
|
||||
use tokio::{net::TcpStream, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
config::{Client, Config}, db::{BoxyDatabase, Endpoint}, types::{GeneralBody, GeneralResponse, TcpIntercept}
|
||||
config::{Client, Config},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
server::{GeneralBody, GeneralResponse, TcpIntercept},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -20,7 +23,7 @@ pub struct ApiService {
|
|||
pub _address: Option<IpAddr>,
|
||||
}
|
||||
|
||||
async fn default_response() -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
async fn default_response() -> GeneralResponse {
|
||||
Response::builder()
|
||||
.status(404)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(
|
||||
|
@ -29,7 +32,7 @@ async fn default_response() -> Response<http_body_util::Either<Incoming, Full<By
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
async fn custom_resp(e: StatusCode, m: String) -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
async fn custom_resp(e: StatusCode, m: &'static str) -> GeneralResponse {
|
||||
Response::builder()
|
||||
.status(e)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
|
||||
|
@ -37,7 +40,7 @@ async fn custom_resp(e: StatusCode, m: String) -> Response<http_body_util::Eithe
|
|||
}
|
||||
|
||||
impl TcpIntercept for ApiService {
|
||||
fn handle(&mut self, stream: &TcpStream) {
|
||||
fn stream(&mut self, stream: &TcpStream) {
|
||||
self._address = Some(stream.peer_addr().unwrap().ip());
|
||||
}
|
||||
}
|
||||
|
@ -51,37 +54,73 @@ impl Service<Request<Incoming>> for ApiService {
|
|||
let database = self.database.clone();
|
||||
let config = self.config.clone();
|
||||
let address = self._address.clone().unwrap();
|
||||
|
||||
Box::pin(async move {
|
||||
match *req.method() {
|
||||
Method::POST => match req.uri().path() {
|
||||
"/register" => {
|
||||
let encoded_header = req.headers().get(hyper::header::AUTHORIZATION).unwrap().to_str().unwrap();
|
||||
|
||||
let auth_string = String::from_utf8(BASE64_STANDARD.decode(&encoded_header[6..]).unwrap()).unwrap();
|
||||
|
||||
let auth_string_split: Vec<&str> = auth_string.split(':').collect();
|
||||
debug!("new api register request from {}", address);
|
||||
|
||||
let name = auth_string_split.first().unwrap();
|
||||
let secret = auth_string_split.get(1).unwrap();
|
||||
let encoded_header = req
|
||||
.headers()
|
||||
.get(hyper::header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
let matched_clients: Vec<&Client> = config.clients.iter().filter(|x| x.name.eq(name)).collect();
|
||||
debug!("authorization header: {}", encoded_header);
|
||||
|
||||
let client = matched_clients.first().unwrap();
|
||||
let auth_string = String::from_utf8(
|
||||
BASE64_STANDARD.decode(&encoded_header[6..]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !bcrypt::verify(secret, client.secret.as_str()).unwrap() {
|
||||
return Ok(custom_resp(StatusCode::UNAUTHORIZED, "Invalid credentials.".to_string()).await);
|
||||
debug!("decoded auth string: {}", auth_string);
|
||||
|
||||
if !Client::verify(auth_string.clone(), config).await {
|
||||
warn!(
|
||||
"Authentication for string {} from {} failed.",
|
||||
auth_string, address
|
||||
);
|
||||
|
||||
return Ok(custom_resp(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Invalid credentials.",
|
||||
)
|
||||
.await);
|
||||
}
|
||||
|
||||
let body = String::from_utf8(req.collect().await.unwrap().to_bytes().iter().cloned().collect::<Vec<u8>>()).unwrap();
|
||||
let body = String::from_utf8(
|
||||
req.collect()
|
||||
.await
|
||||
.unwrap()
|
||||
.to_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>(),
|
||||
)
|
||||
.unwrap();
|
||||
let json = json::parse(body.as_str()).unwrap();
|
||||
|
||||
info!("body: {}", body);
|
||||
debug!("body: {}", body);
|
||||
|
||||
let mut endpoint = Endpoint::new(None, address, json["port"].as_u16().unwrap(), json["callback"].as_str().unwrap_or("/").to_string()).await;
|
||||
let mut endpoint = Endpoint::new(
|
||||
None,
|
||||
address,
|
||||
json["port"].as_u16().unwrap(),
|
||||
json["callback"].as_str().unwrap_or("/").to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
endpoint.register(*database.lock().await, json["hostname"].as_str().unwrap().to_string()).await.unwrap();
|
||||
endpoint
|
||||
.register(
|
||||
*database.lock().await,
|
||||
json["hostname"].as_str().unwrap().to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(custom_resp(StatusCode::OK, "yay".to_string()).await)
|
||||
Ok(custom_resp(StatusCode::OK, "").await)
|
||||
}
|
||||
_ => Ok(default_response().await),
|
||||
},
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
Request, Response,
|
||||
body::{Bytes, Incoming},
|
||||
Request,
|
||||
body::Incoming,
|
||||
service::Service,
|
||||
};
|
||||
use log::error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
config::{self, Client, Config, Host},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
types::{GeneralBody, GeneralResponse, TcpIntercept},
|
||||
server::{GeneralResponse, TcpIntercept},
|
||||
};
|
||||
|
||||
use super::proxy::ProxyService;
|
||||
|
@ -23,8 +20,7 @@ pub struct ControllerService {
|
|||
}
|
||||
|
||||
impl TcpIntercept for ControllerService {
|
||||
fn handle(&mut self, stream: &tokio::net::TcpStream) {
|
||||
}
|
||||
fn stream(&mut self, _: &tokio::net::TcpStream) {}
|
||||
}
|
||||
|
||||
impl Service<Request<Incoming>> for ControllerService {
|
||||
|
|
|
@ -5,7 +5,7 @@ use hyper_util::rt::TokioIo;
|
|||
use log::error;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::types::{GeneralResponse, to_general_response};
|
||||
use crate::server::{GeneralResponse, to_general_response};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProxyService {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue