temp
This commit is contained in:
parent
7757ef32f4
commit
663d508093
6 changed files with 187 additions and 53 deletions
98
src/db.rs
98
src/db.rs
|
@ -5,6 +5,7 @@ use tokio_postgres::{Client, Row};
|
||||||
|
|
||||||
const ENDPOINT_TABLE: &str = "endpoints";
|
const ENDPOINT_TABLE: &str = "endpoints";
|
||||||
const HOSTS_RELATION_TABLE: &str = "hosts";
|
const HOSTS_RELATION_TABLE: &str = "hosts";
|
||||||
|
const CERTIFICATES_TABLE: &str = "certs";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BoxyDatabase {
|
pub struct BoxyDatabase {
|
||||||
|
@ -18,6 +19,86 @@ pub struct Endpoint {
|
||||||
pub callback: String,
|
pub callback: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Certificate {
|
||||||
|
pub hostname: String,
|
||||||
|
pub cert_data: Vec<u8>,
|
||||||
|
pub key_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
pub async fn new(hostname: String, cert_data: Vec<u8>, key_data: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
hostname,
|
||||||
|
cert_data,
|
||||||
|
key_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn get_by_hostname(
|
||||||
|
db: &BoxyDatabase,
|
||||||
|
hostname: String,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let row = db
|
||||||
|
.client
|
||||||
|
.query_one(
|
||||||
|
format!(
|
||||||
|
"SELECT * FROM {HOSTS_RELATION_TABLE}
|
||||||
|
WHERE hostname = $1"
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&[&hostname],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(row.into())
|
||||||
|
}
|
||||||
|
pub async fn get_all(db: &BoxyDatabase) -> Result<Vec<Self>, Box<dyn Error>> {
|
||||||
|
let mut result: Vec<Self> = Vec::new();
|
||||||
|
|
||||||
|
let rows = db
|
||||||
|
.client
|
||||||
|
.query(format!("SELECT * FROM {CERTIFICATES_TABLE}").as_str(), &[])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
result.push(row.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
pub async fn delete(self, db: &mut BoxyDatabase) -> Result<(), tokio_postgres::Error> {
|
||||||
|
let tx = db.client.transaction().await?;
|
||||||
|
|
||||||
|
tx.execute(
|
||||||
|
format!(
|
||||||
|
"DELETE FROM {CERTIFICATES_TABLE}
|
||||||
|
WHERE hostname = $1"
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&[&self.hostname],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
warn!("Removed certificate for host {}", self.hostname);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Row> for Certificate {
|
||||||
|
fn from(value: Row) -> Self {
|
||||||
|
Self {
|
||||||
|
hostname: value.get("hostname"),
|
||||||
|
cert_data: value.get("certificate"),
|
||||||
|
key_data: value.get("key"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
pub async fn new(id: Option<u32>, address: IpAddr, port: u16, callback: String) -> Self {
|
pub async fn new(id: Option<u32>, address: IpAddr, port: u16, callback: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -119,7 +200,7 @@ impl Endpoint {
|
||||||
let id = self.id.unwrap() as i32;
|
let id = self.id.unwrap() as i32;
|
||||||
|
|
||||||
tx.execute(
|
tx.execute(
|
||||||
format!("DELETE FROM {ENDPOINT_TABLE} where id = $1").as_str(),
|
format!("DELETE FROM {ENDPOINT_TABLE} WHERE id = $1").as_str(),
|
||||||
&[&id],
|
&[&id],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -205,6 +286,21 @@ impl BoxyDatabase {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
c.execute(
|
||||||
|
format!(
|
||||||
|
"CREATE TABLE IF NOT EXISTS {CERTIFICATES_TABLE}
|
||||||
|
(
|
||||||
|
hostname text PRIMARY KEY,
|
||||||
|
certificate bytea,
|
||||||
|
key bytea
|
||||||
|
)
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(BoxyDatabase { client: c })
|
Ok(BoxyDatabase { client: c })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use log::{debug, error, info};
|
||||||
use matchers::api::ApiMatcher;
|
use matchers::api::ApiMatcher;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
use services::{controller::ControllerService, matcher::Matcher};
|
use services::{controller::ControllerService, matcher::Matcher};
|
||||||
use tls::TlsOption;
|
use tls::{TlsManager, TlsOption};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{self},
|
time::{self},
|
||||||
|
@ -72,6 +72,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Connected to database.");
|
info!("Connected to database.");
|
||||||
|
|
||||||
|
|
||||||
let database = Box::new(BoxyDatabase::new(client).await.unwrap());
|
let database = Box::new(BoxyDatabase::new(client).await.unwrap());
|
||||||
|
|
||||||
|
@ -87,7 +88,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let proxy_server = Server::new(svc, (config.proxy.listen, config.proxy.port), TlsOption::NoTls)
|
let proxy_server = Server::new(svc, (config.proxy.listen, config.proxy.port), TlsOption::Tls(TlsManager.clone))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tokio::{net::TcpStream, sync::Mutex};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Client, Config},
|
config::{Client, Config},
|
||||||
db::BoxyDatabase,
|
db::BoxyDatabase,
|
||||||
routes::api::{AddHost, RegisterEndpoint, RemoveHost},
|
routes::api::{AddHostToEndpoint, RegisterEndpoint, RemoveHost},
|
||||||
server::{GeneralResponse, custom_resp},
|
server::{GeneralResponse, custom_resp},
|
||||||
services::matcher::Matcher,
|
services::matcher::Matcher,
|
||||||
};
|
};
|
||||||
|
@ -119,7 +119,7 @@ impl Matcher for ApiMatcher {
|
||||||
fn retrieve(&self) -> Vec<Arc<dyn crate::services::matcher::Route<Self> + Sync + Send>> {
|
fn retrieve(&self) -> Vec<Arc<dyn crate::services::matcher::Route<Self> + Sync + Send>> {
|
||||||
vec![
|
vec![
|
||||||
Arc::new(RegisterEndpoint {}),
|
Arc::new(RegisterEndpoint {}),
|
||||||
Arc::new(AddHost {}),
|
Arc::new(AddHostToEndpoint {}),
|
||||||
Arc::new(RemoveHost {}),
|
Arc::new(RemoveHost {}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,14 @@ use crate::{
|
||||||
services::matcher::Route,
|
services::matcher::Route,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AddHost {}
|
pub struct LinkHost {}
|
||||||
|
pub struct UnlinkHost {}
|
||||||
|
pub struct GetHostStatus {}
|
||||||
|
pub struct RegisterEndpoint {}
|
||||||
|
pub struct DeregisterEndpoint {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Route<ApiMatcher> for AddHost {
|
impl Route<ApiMatcher> for LinkHost {
|
||||||
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
||||||
req.uri().path().starts_with("/endpoint/") && req.method() == Method::POST
|
req.uri().path().starts_with("/endpoint/") && req.method() == Method::POST
|
||||||
}
|
}
|
||||||
|
@ -63,8 +67,6 @@ impl Route<ApiMatcher> for AddHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegisterEndpoint {}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Route<ApiMatcher> for RegisterEndpoint {
|
impl Route<ApiMatcher> for RegisterEndpoint {
|
||||||
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
||||||
|
@ -113,10 +115,9 @@ impl Route<ApiMatcher> for RegisterEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RemoveHost {}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Route<ApiMatcher> for RemoveHost {
|
impl Route<ApiMatcher> for DeregisterEndpoint {
|
||||||
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
fn matcher(&self, _: &ApiMatcher, req: &hyper::Request<hyper::body::Incoming>) -> bool {
|
||||||
req.uri().path().starts_with("/endpoint/") && req.method() == Method::DELETE
|
req.uri().path().starts_with("/endpoint/") && req.method() == Method::DELETE
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,12 @@ use hyper::{
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use rustls::server::Acceptor;
|
use rustls::{
|
||||||
|
ConfigBuilder, ServerConfig,
|
||||||
|
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
|
||||||
|
server::Acceptor,
|
||||||
|
sign::SingleCertAndKey,
|
||||||
|
};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio_rustls::{LazyConfigAcceptor, StartHandshake};
|
use tokio_rustls::{LazyConfigAcceptor, StartHandshake};
|
||||||
|
|
||||||
|
@ -73,7 +78,7 @@ where
|
||||||
S::ResBody: Send,
|
S::ResBody: Send,
|
||||||
<S as HttpService<Incoming>>::Future: Send,
|
<S as HttpService<Incoming>>::Future: Send,
|
||||||
{
|
{
|
||||||
pub async fn handle(&self) {
|
pub async fn handle(&'static self) {
|
||||||
info!(
|
info!(
|
||||||
"Server started at http://{} for service: {}",
|
"Server started at http://{} for service: {}",
|
||||||
self.listener.local_addr().unwrap(),
|
self.listener.local_addr().unwrap(),
|
||||||
|
@ -84,11 +89,10 @@ where
|
||||||
let (tcp_stream, _) = self.listener.accept().await.unwrap();
|
let (tcp_stream, _) = self.listener.accept().await.unwrap();
|
||||||
|
|
||||||
let mut svc_clone = self.service.clone();
|
let mut svc_clone = self.service.clone();
|
||||||
let tls = self.tls.clone();
|
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
svc_clone.stream(&tcp_stream);
|
svc_clone.stream(&tcp_stream);
|
||||||
|
|
||||||
match tls {
|
match &self.tls {
|
||||||
TlsOption::NoTls => {
|
TlsOption::NoTls => {
|
||||||
if let Err(err) = http1::Builder::new()
|
if let Err(err) = http1::Builder::new()
|
||||||
.writev(false)
|
.writev(false)
|
||||||
|
@ -103,14 +107,31 @@ where
|
||||||
|
|
||||||
match acceptor.await {
|
match acceptor.await {
|
||||||
Ok(y) => {
|
Ok(y) => {
|
||||||
|
let mut manager = x.lock().await;
|
||||||
let hello = y.client_hello();
|
let hello = y.client_hello();
|
||||||
let hostname = hello.server_name().clone().unwrap();
|
let hostname = hello.server_name().unwrap();
|
||||||
let config = Arc::new(x.matcher(hostname).unwrap());
|
|
||||||
let stream = y
|
let raw_certificate =
|
||||||
.into_stream(config)
|
manager.get_certificate(hostname.clone()).await.unwrap();
|
||||||
.await
|
|
||||||
|
let cert_chain = CertificateDer::pem_slice_iter(
|
||||||
|
raw_certificate.cert_data.as_slice(),
|
||||||
|
)
|
||||||
|
.map(|cert| cert.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let key = PrivateKeyDer::from_pem_slice(
|
||||||
|
raw_certificate.key_data.as_slice(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config = ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(cert_chain, key)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let stream = y.into_stream(Arc::new(config)).await.unwrap();
|
||||||
|
|
||||||
if let Err(err) = http1::Builder::new()
|
if let Err(err) = http1::Builder::new()
|
||||||
.writev(false)
|
.writev(false)
|
||||||
.serve_connection(TokioIo::new(stream), svc_clone)
|
.serve_connection(TokioIo::new(stream), svc_clone)
|
||||||
|
@ -119,10 +140,7 @@ where
|
||||||
error!("Error while trying to serve connection: {err}")
|
error!("Error while trying to serve connection: {err}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => error!("Error while initiating handshake: {e}"),
|
||||||
error!("Error while initiating handshake: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
76
src/tls.rs
76
src/tls.rs
|
@ -1,48 +1,66 @@
|
||||||
use std::{
|
use std::{collections::HashMap, error::Error, sync::Arc};
|
||||||
error::{self, Error},
|
|
||||||
fs::File,
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
|
||||||
io,
|
use tokio::{
|
||||||
path::{self, Path},
|
fs::{self, File},
|
||||||
sync::Arc,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rustls::{
|
use crate::db::BoxyDatabase;
|
||||||
ServerConfig, crypto,
|
|
||||||
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
|
|
||||||
sign::CertifiedKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum TlsOption {
|
pub enum TlsOption {
|
||||||
NoTls,
|
NoTls,
|
||||||
Tls(FileTls),
|
Tls(Mutex<TlsManager>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct FileTls {
|
pub struct TlsManager {
|
||||||
pub certs_path: String,
|
pub certs_path: String,
|
||||||
|
pub certificates: HashMap<String, RawCertificate>,
|
||||||
|
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTls {
|
impl RawCertificate {
|
||||||
pub fn matcher(&self, hostname: &str) -> Result<ServerConfig, Box<dyn Error>> {
|
pub fn new(cert_data: Vec<u8>, key_data: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
cert_data,
|
||||||
|
key_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsManager {
|
||||||
|
pub async fn get_certificate(
|
||||||
|
&mut self,
|
||||||
|
hostname: &str,
|
||||||
|
) -> Result<&RawCertificate, Box<dyn Error>> {
|
||||||
|
if self.certificates.contains_key(hostname) {
|
||||||
|
return Ok(self.certificates.get(hostname).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
let path_to_pem =
|
let path_to_pem =
|
||||||
safe_path::scoped_join(self.certs_path.clone(), format!("{hostname}.pem"))?;
|
safe_path::scoped_join(self.certs_path.clone(), format!("{hostname}.pem"))?;
|
||||||
let path_to_key =
|
let path_to_key =
|
||||||
safe_path::scoped_join(self.certs_path.clone(), format!("{hostname}.key"))?;
|
safe_path::scoped_join(self.certs_path.clone(), format!("{hostname}.key"))?;
|
||||||
|
|
||||||
let certfile = File::open(path_to_pem)?;
|
let cert_file = fs::read(path_to_pem).await.unwrap();
|
||||||
let mut cert_reader = io::BufReader::new(certfile);
|
let key_file = fs::read(path_to_key).await.unwrap();
|
||||||
let certs = rustls_pemfile::certs(&mut cert_reader)
|
|
||||||
.map(|x| x.unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let keyfile = File::open(path_to_key)?;
|
self.certificates.insert(
|
||||||
let mut key_reader = io::BufReader::new(keyfile);
|
hostname.to_string(),
|
||||||
let key = rustls_pemfile::private_key(&mut key_reader).map(|key| key.unwrap())?;
|
RawCertificate::new(cert_file, key_file),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(ServerConfig::builder()
|
// fucking borrow checker
|
||||||
.with_no_client_auth()
|
Ok(self.certificates.get(hostname).unwrap())
|
||||||
.with_single_cert(certs, key)
|
}
|
||||||
.map_err(|e| e)?)
|
}
|
||||||
|
|
||||||
|
impl TlsManager {
|
||||||
|
pub async fn new(path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
certs_path: path,
|
||||||
|
certificates: HashMap::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue