Compare commits
No commits in common. "e2d9789f9a7e2b0d827508bece5a55f35a582b50" and "a8bc61f40ce9061cd63a43a2fe07c9a4bb899a64" have entirely different histories.
e2d9789f9a
...
a8bc61f40c
11 changed files with 96 additions and 192 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
@ -26,15 +26,6 @@ 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"
|
||||
|
@ -127,11 +118,9 @@ dependencies = [
|
|||
name = "boxy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi_colours",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bcrypt",
|
||||
"colour",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
|
@ -154,12 +143,6 @@ 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"
|
||||
|
@ -197,15 +180,6 @@ 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"
|
||||
|
@ -856,15 +830,6 @@ 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"
|
||||
|
@ -1321,22 +1286,6 @@ 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"
|
||||
|
@ -1346,12 +1295,6 @@ 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,6 +21,4 @@ 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,4 +1,7 @@
|
|||
database: 'postgresql://postgres:trust@127.0.0.1'
|
||||
db:
|
||||
host: '127.0.0.1'
|
||||
user: 'postgres'
|
||||
password: 'trust'
|
||||
|
||||
proxy:
|
||||
listen: 127.0.0.1
|
||||
|
@ -8,6 +11,10 @@ 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)
|
||||
hashed_secret: '$2b$12$5wH/0p702PPqVp7fCpVS4.1GA2/wAbk89w2nMjwuS8439OhjCUGbK' # password123
|
||||
secret: '$2b$12$5wH/0p702PPqVp7fCpVS4.1GA2/wAbk89w2nMjwuS8439OhjCUGbK' # password123
|
||||
|
|
|
@ -27,34 +27,44 @@ pub struct Api {
|
|||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Client {
|
||||
pub name: String,
|
||||
pub hashed_secret: String,
|
||||
pub secret: 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 Host {
|
||||
pub hostname: String,
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Config {
|
||||
pub database: String,
|
||||
pub db: Db,
|
||||
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,9 +1,6 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
net::IpAddr,
|
||||
};
|
||||
use std::{error::Error, net::{IpAddr, SocketAddr}};
|
||||
|
||||
use tokio_postgres::Client;
|
||||
use tokio_postgres::{Client, Socket, tls::MakeTlsConnect};
|
||||
|
||||
const ENDPOINT_TABLE: &str = "endpoints";
|
||||
const HOSTS_RELATION_TABLE: &str = "hosts";
|
||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -1,28 +1,26 @@
|
|||
mod config;
|
||||
mod db;
|
||||
mod server;
|
||||
mod services;
|
||||
mod types;
|
||||
|
||||
use std::{env, process::exit, sync::Arc};
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
use bcrypt::DEFAULT_COST;
|
||||
use bcrypt::{DEFAULT_COST, bcrypt};
|
||||
use config::Config;
|
||||
use db::BoxyDatabase;
|
||||
use log::{debug, error, info};
|
||||
use server::Server;
|
||||
use log::{error, info};
|
||||
use nanoid::nanoid;
|
||||
use ring::rand::SystemRandom;
|
||||
use services::{api::ApiService, controller::ControllerService};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::NoTls;
|
||||
|
||||
const VERSION: &str = "v0.1a";
|
||||
use tokio::{fs::File, io::AsyncReadExt, sync::Mutex};
|
||||
use tokio_postgres::{NoTls, tls::NoTlsError};
|
||||
use types::Server;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
unsafe { env::set_var("RUST_LOG", "info") };
|
||||
}
|
||||
|
||||
pretty_env_logger::init();
|
||||
pretty_env_logger::formatted_builder()
|
||||
.filter(None, log::LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
|
@ -39,31 +37,26 @@ 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();
|
||||
|
||||
debug!("Database URI: {}", config.database);
|
||||
let db_string = config.db.to_string().await;
|
||||
|
||||
let (client, conn) = tokio_postgres::connect(config.database.as_str(), NoTls)
|
||||
info!("Database string: {}", db_string);
|
||||
|
||||
let (client, conn) = tokio_postgres::connect(db_string.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)));
|
||||
|
@ -71,7 +64,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 {
|
||||
|
@ -87,12 +80,10 @@ 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,3 +1,3 @@
|
|||
pub mod api;
|
||||
pub mod controller;
|
||||
pub mod proxy;
|
||||
pub mod controller;
|
||||
pub mod api;
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
use std::{net::IpAddr, pin::Pin, sync::Arc};
|
||||
use std::{hash, net::{IpAddr, SocketAddr}, pin::Pin, sync::Arc};
|
||||
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use bcrypt::DEFAULT_COST;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{
|
||||
Method, Request, Response, StatusCode,
|
||||
body::{Bytes, Incoming},
|
||||
service::Service,
|
||||
body::{Bytes, Incoming}, service::Service, Method, Request, Response, StatusCode, Uri
|
||||
};
|
||||
use log::{debug, warn};
|
||||
use log::{info, trace};
|
||||
use tokio::{net::TcpStream, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
config::{Client, Config},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
server::{GeneralBody, GeneralResponse, TcpIntercept},
|
||||
config::{Client, Config}, db::{BoxyDatabase, Endpoint}, types::{GeneralBody, GeneralResponse, TcpIntercept}
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -23,7 +20,7 @@ pub struct ApiService {
|
|||
pub _address: Option<IpAddr>,
|
||||
}
|
||||
|
||||
async fn default_response() -> GeneralResponse {
|
||||
async fn default_response() -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
Response::builder()
|
||||
.status(404)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(
|
||||
|
@ -32,7 +29,7 @@ async fn default_response() -> GeneralResponse {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
async fn custom_resp(e: StatusCode, m: &'static str) -> GeneralResponse {
|
||||
async fn custom_resp(e: StatusCode, m: String) -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
Response::builder()
|
||||
.status(e)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
|
||||
|
@ -40,7 +37,7 @@ async fn custom_resp(e: StatusCode, m: &'static str) -> GeneralResponse {
|
|||
}
|
||||
|
||||
impl TcpIntercept for ApiService {
|
||||
fn stream(&mut self, stream: &TcpStream) {
|
||||
fn handle(&mut self, stream: &TcpStream) {
|
||||
self._address = Some(stream.peer_addr().unwrap().ip());
|
||||
}
|
||||
}
|
||||
|
@ -54,73 +51,37 @@ 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" => {
|
||||
debug!("new api register request from {}", address);
|
||||
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();
|
||||
|
||||
let encoded_header = req
|
||||
.headers()
|
||||
.get(hyper::header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
let name = auth_string_split.first().unwrap();
|
||||
let secret = auth_string_split.get(1).unwrap();
|
||||
|
||||
debug!("authorization header: {}", encoded_header);
|
||||
let matched_clients: Vec<&Client> = config.clients.iter().filter(|x| x.name.eq(name)).collect();
|
||||
|
||||
let auth_string = String::from_utf8(
|
||||
BASE64_STANDARD.decode(&encoded_header[6..]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let client = matched_clients.first().unwrap();
|
||||
|
||||
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);
|
||||
if !bcrypt::verify(secret, client.secret.as_str()).unwrap() {
|
||||
return Ok(custom_resp(StatusCode::UNAUTHORIZED, "Invalid credentials.".to_string()).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();
|
||||
|
||||
debug!("body: {}", body);
|
||||
info!("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, "").await)
|
||||
Ok(custom_resp(StatusCode::OK, "yay".to_string()).await)
|
||||
}
|
||||
_ => Ok(default_response().await),
|
||||
},
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
Request,
|
||||
body::Incoming,
|
||||
Request, Response,
|
||||
body::{Bytes, Incoming},
|
||||
service::Service,
|
||||
};
|
||||
use log::error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
config::{self, Client, Config, Host},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
server::{GeneralResponse, TcpIntercept},
|
||||
types::{GeneralBody, GeneralResponse, TcpIntercept},
|
||||
};
|
||||
|
||||
use super::proxy::ProxyService;
|
||||
|
@ -20,7 +23,8 @@ pub struct ControllerService {
|
|||
}
|
||||
|
||||
impl TcpIntercept for ControllerService {
|
||||
fn stream(&mut self, _: &tokio::net::TcpStream) {}
|
||||
fn handle(&mut self, stream: &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::server::{GeneralResponse, to_general_response};
|
||||
use crate::types::{GeneralResponse, to_general_response};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProxyService {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{any::type_name_of_val, error::Error};
|
||||
use std::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, info};
|
||||
use log::error;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
pub type GeneralResponse = Response<GeneralBody>;
|
||||
|
@ -25,7 +25,7 @@ pub struct Server<S> {
|
|||
}
|
||||
|
||||
pub trait TcpIntercept {
|
||||
fn stream(&mut self, stream: &TcpStream);
|
||||
fn handle(&mut self, stream: &TcpStream);
|
||||
}
|
||||
|
||||
impl<S> Server<S>
|
||||
|
@ -39,20 +39,13 @@ 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.stream(&stream);
|
||||
svc_clone.handle(&stream);
|
||||
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = http1::Builder::new()
|
||||
.writev(false)
|
Loading…
Add table
Add a link
Reference in a new issue