Add tripcodes for a little security
parent
e7edf84282
commit
13117e4237
|
@ -18,6 +18,7 @@ handlebars = "3.5.1"
|
||||||
http = "0.2.1"
|
http = "0.2.1"
|
||||||
hyper = "0.13.8"
|
hyper = "0.13.8"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
maplit = "1.0.2"
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1.0"
|
||||||
regex = "1.4.1"
|
regex = "1.4.1"
|
||||||
reqwest = { version = "0.10.8", features = ["stream"] }
|
reqwest = { version = "0.10.8", features = ["stream"] }
|
||||||
|
@ -25,4 +26,5 @@ rmp-serde = "0.14.4"
|
||||||
serde = {version = "1.0.117", features = ["derive"]}
|
serde = {version = "1.0.117", features = ["derive"]}
|
||||||
structopt = "0.3.20"
|
structopt = "0.3.20"
|
||||||
tokio = { version = "0.2.22", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
|
toml = "0.5.7"
|
||||||
ulid = "0.4.1"
|
ulid = "0.4.1"
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
use std::error::Error;
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::File,
|
||||||
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main () -> Result <(), Box <dyn Error>> {
|
async fn main () -> Result <(), Box <dyn Error>> {
|
||||||
ptth::relay::main ().await
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut f = File::open ("ptth_relay.toml").unwrap ();
|
||||||
|
let mut buffer = vec! [0u8; 4096];
|
||||||
|
let bytes_read = f.read (&mut buffer).unwrap ();
|
||||||
|
buffer.truncate (bytes_read);
|
||||||
|
|
||||||
|
let config_s = String::from_utf8 (buffer).unwrap ();
|
||||||
|
let config_file: ptth::relay::ConfigFile = toml::from_str (&config_s).unwrap ();
|
||||||
|
|
||||||
|
ptth::relay::main (config_file).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,22 +10,28 @@ struct Opt {
|
||||||
#[structopt (name = "RELAY_URL")]
|
#[structopt (name = "RELAY_URL")]
|
||||||
relay_url: String,
|
relay_url: String,
|
||||||
|
|
||||||
#[structopt (name = "SERVER_NAME")]
|
|
||||||
server_name: String,
|
|
||||||
|
|
||||||
#[structopt (long)]
|
#[structopt (long)]
|
||||||
file_server_root: Option <PathBuf>,
|
file_server_root: Option <PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main () -> Result <(), Box <dyn Error>> {
|
async fn main () -> Result <(), Box <dyn Error>> {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut f = std::fs::File::open ("ptth_server.toml").unwrap ();
|
||||||
|
let mut buffer = vec! [0u8; 4096];
|
||||||
|
let bytes_read = f.read (&mut buffer).unwrap ();
|
||||||
|
buffer.truncate (bytes_read);
|
||||||
|
|
||||||
|
let config_s = String::from_utf8 (buffer).unwrap ();
|
||||||
|
let config_file: ptth::server::ConfigFile = toml::from_str (&config_s).unwrap ();
|
||||||
|
|
||||||
let opt = Opt::from_args ();
|
let opt = Opt::from_args ();
|
||||||
|
|
||||||
let opt = ptth::server::Opt {
|
let opt = ptth::server::Opt {
|
||||||
relay_url: opt.relay_url,
|
relay_url: opt.relay_url,
|
||||||
server_name: opt.server_name,
|
file_server_root: opt.file_server_root.unwrap_or_else (|| "/home/user".into ()),
|
||||||
file_server_root: opt.file_server_root.unwrap_or ("/home/user".into ()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ptth::server::main (opt).await
|
ptth::server::main (config_file, opt).await
|
||||||
}
|
}
|
||||||
|
|
24
src/lib.rs
24
src/lib.rs
|
@ -31,15 +31,25 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn end_to_end () {
|
fn end_to_end () {
|
||||||
|
use maplit::*;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
let mut rt = Runtime::new ().unwrap ();
|
let mut rt = Runtime::new ().unwrap ();
|
||||||
|
|
||||||
// Spawn the root task
|
// Spawn the root task
|
||||||
rt.block_on (async {
|
rt.block_on (async {
|
||||||
let relay_url = "http://127.0.0.1:4000";
|
let server_name = "alien_wildlands";
|
||||||
|
let api_key = "AnacondaHardcoverGrannyUnlatchLankinessMutate";
|
||||||
|
let tripcode = base64::encode (blake3::hash (api_key.as_bytes ()).as_bytes ());
|
||||||
|
println! ("Relay is expecting tripcode {}", tripcode);
|
||||||
|
let config_file = relay::ConfigFile {
|
||||||
|
port: None,
|
||||||
|
server_tripcodes: hashmap! {
|
||||||
|
server_name.into () => tripcode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let relay_state = Arc::new (relay::RelayState::default ());
|
let relay_state = Arc::new (relay::RelayState::from (&config_file));
|
||||||
|
|
||||||
let relay_state_2 = relay_state.clone ();
|
let relay_state_2 = relay_state.clone ();
|
||||||
spawn (async move {
|
spawn (async move {
|
||||||
|
@ -48,18 +58,20 @@ mod tests {
|
||||||
|
|
||||||
assert! (relay_state.list_servers ().await.is_empty ());
|
assert! (relay_state.list_servers ().await.is_empty ());
|
||||||
|
|
||||||
|
let relay_url = "http://127.0.0.1:4000";
|
||||||
let relay_url_2 = relay_url.into ();
|
let relay_url_2 = relay_url.into ();
|
||||||
let server_name = "alien_wildlands";
|
|
||||||
|
|
||||||
let server_name_2 = server_name.into ();
|
let config_file = server::ConfigFile {
|
||||||
|
name: server_name.into (),
|
||||||
|
api_key: api_key.into (),
|
||||||
|
};
|
||||||
spawn (async move {
|
spawn (async move {
|
||||||
let opt = server::Opt {
|
let opt = server::Opt {
|
||||||
relay_url: relay_url_2,
|
relay_url: relay_url_2,
|
||||||
server_name: server_name_2,
|
|
||||||
file_server_root: "./".into (),
|
file_server_root: "./".into (),
|
||||||
};
|
};
|
||||||
|
|
||||||
server::main (opt).await.unwrap ();
|
server::main (config_file, opt).await.unwrap ();
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::time::delay_for (std::time::Duration::from_millis (500)).await;
|
tokio::time::delay_for (std::time::Duration::from_millis (500)).await;
|
||||||
|
|
107
src/relay/mod.rs
107
src/relay/mod.rs
|
@ -4,6 +4,7 @@ use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
collections::*,
|
collections::*,
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
|
iter::FromIterator,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{
|
sync::{
|
||||||
Arc
|
Arc
|
||||||
|
@ -22,7 +23,10 @@ use hyper::{
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use serde::Serialize;
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
@ -67,7 +71,42 @@ enum RequestRendezvous {
|
||||||
|
|
||||||
type ResponseRendezvous = oneshot::Sender <(http_serde::ResponseParts, Body)>;
|
type ResponseRendezvous = oneshot::Sender <(http_serde::ResponseParts, Body)>;
|
||||||
|
|
||||||
|
// Stuff we need to load from the config file and use to
|
||||||
|
// set up the HTTP server
|
||||||
|
|
||||||
|
#[derive (Default, Deserialize)]
|
||||||
|
pub struct ConfigFile {
|
||||||
|
pub port: Option <u16>,
|
||||||
|
pub server_tripcodes: HashMap <String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff we actually need at runtime
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
server_tripcodes: HashMap <String, blake3::Hash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From <&ConfigFile> for Config {
|
||||||
|
fn from (f: &ConfigFile) -> Self {
|
||||||
|
let trips = HashMap::from_iter (f.server_tripcodes.iter ()
|
||||||
|
.map (|(k, v)| {
|
||||||
|
use std::convert::TryInto;
|
||||||
|
let bytes: Vec <u8> = base64::decode (v).unwrap ();
|
||||||
|
let bytes: [u8; 32] = (&bytes [..]).try_into ().unwrap ();
|
||||||
|
|
||||||
|
let v = blake3::Hash::from (bytes);
|
||||||
|
|
||||||
|
(k.clone (), v)
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
server_tripcodes: trips,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RelayState {
|
pub struct RelayState {
|
||||||
|
config: Config,
|
||||||
handlebars: Arc <Handlebars <'static>>,
|
handlebars: Arc <Handlebars <'static>>,
|
||||||
|
|
||||||
// Key: Server ID
|
// Key: Server ID
|
||||||
|
@ -80,6 +119,18 @@ pub struct RelayState {
|
||||||
impl Default for RelayState {
|
impl Default for RelayState {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
config: Config::from (&ConfigFile::default ()),
|
||||||
|
handlebars: Arc::new (load_templates ().unwrap ()),
|
||||||
|
request_rendezvous: Default::default (),
|
||||||
|
response_rendezvous: Default::default (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From <&ConfigFile> for RelayState {
|
||||||
|
fn from (config_file: &ConfigFile) -> Self {
|
||||||
|
Self {
|
||||||
|
config: Config::from (config_file),
|
||||||
handlebars: Arc::new (load_templates ().unwrap ()),
|
handlebars: Arc::new (load_templates ().unwrap ()),
|
||||||
request_rendezvous: Default::default (),
|
request_rendezvous: Default::default (),
|
||||||
response_rendezvous: Default::default (),
|
response_rendezvous: Default::default (),
|
||||||
|
@ -101,9 +152,29 @@ fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
|
||||||
Response::builder ().status (status).body (b.into ()).unwrap ()
|
Response::builder ().status (status).body (b.into ()).unwrap ()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_http_listen (state: Arc <RelayState>, watcher_code: String)
|
async fn handle_http_listen (
|
||||||
|
state: Arc <RelayState>,
|
||||||
|
watcher_code: String,
|
||||||
|
api_key: &[u8],
|
||||||
|
)
|
||||||
-> Response <Body>
|
-> Response <Body>
|
||||||
{
|
{
|
||||||
|
let trip_error = status_reply (StatusCode::UNAUTHORIZED, "Bad X-ApiKey");
|
||||||
|
|
||||||
|
let expected_tripcode = match state.config.server_tripcodes.get (&watcher_code) {
|
||||||
|
None => {
|
||||||
|
eprintln! ("Denied http_listen for non-existent server name {}", watcher_code);
|
||||||
|
return trip_error;
|
||||||
|
},
|
||||||
|
Some (x) => x,
|
||||||
|
};
|
||||||
|
let actual_tripcode = blake3::hash (api_key);
|
||||||
|
|
||||||
|
if expected_tripcode != &actual_tripcode {
|
||||||
|
eprintln! ("Denied http_listen for bad tripcode {}", base64::encode (actual_tripcode.as_bytes ()));
|
||||||
|
return trip_error;
|
||||||
|
}
|
||||||
|
|
||||||
use RequestRendezvous::*;
|
use RequestRendezvous::*;
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel ();
|
let (tx, rx) = oneshot::channel ();
|
||||||
|
@ -236,6 +307,8 @@ async fn handle_all (req: Request <Body>, state: Arc <RelayState>)
|
||||||
let path = req.uri ().path ();
|
let path = req.uri ().path ();
|
||||||
//println! ("{}", path);
|
//println! ("{}", path);
|
||||||
|
|
||||||
|
let api_key = req.headers ().get ("X-ApiKey");
|
||||||
|
|
||||||
if req.method () == Method::POST {
|
if req.method () == Method::POST {
|
||||||
// This is stuff the server can use. Clients can't
|
// This is stuff the server can use. Clients can't
|
||||||
// POST right now
|
// POST right now
|
||||||
|
@ -250,7 +323,11 @@ async fn handle_all (req: Request <Body>, state: Arc <RelayState>)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok (if let Some (listen_code) = prefix_match (path, "/7ZSFUKGV_http_listen/") {
|
Ok (if let Some (listen_code) = prefix_match (path, "/7ZSFUKGV_http_listen/") {
|
||||||
handle_http_listen (state, listen_code.into ()).await
|
let api_key = match api_key {
|
||||||
|
None => return Ok (status_reply (StatusCode::UNAUTHORIZED, "Can't register as server without an API key")),
|
||||||
|
Some (x) => x,
|
||||||
|
};
|
||||||
|
handle_http_listen (state, listen_code.into (), api_key.as_bytes ()).await
|
||||||
}
|
}
|
||||||
else if let Some (rest) = prefix_match (path, "/servers/") {
|
else if let Some (rest) = prefix_match (path, "/servers/") {
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
|
@ -315,9 +392,17 @@ pub fn load_templates ()
|
||||||
Ok (handlebars)
|
Ok (handlebars)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_relay (state: Arc <RelayState>) -> Result <(), Box <dyn Error>>
|
pub async fn run_relay (
|
||||||
|
state: Arc <RelayState>
|
||||||
|
)
|
||||||
|
-> Result <(), Box <dyn Error>>
|
||||||
{
|
{
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 4000));
|
let addr = SocketAddr::from ((
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
4000,
|
||||||
|
));
|
||||||
|
|
||||||
|
eprintln! ("Loaded {} server tripcodes", state.config.server_tripcodes.len ());
|
||||||
|
|
||||||
let make_svc = make_service_fn (|_conn| {
|
let make_svc = make_service_fn (|_conn| {
|
||||||
let state = state.clone ();
|
let state = state.clone ();
|
||||||
|
@ -338,14 +423,10 @@ pub async fn run_relay (state: Arc <RelayState>) -> Result <(), Box <dyn Error>>
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn main () -> Result <(), Box <dyn Error>> {
|
pub async fn main (config_file: ConfigFile)
|
||||||
let state = RelayState {
|
-> Result <(), Box <dyn Error>>
|
||||||
handlebars: Arc::new (load_templates ()?),
|
{
|
||||||
request_rendezvous: Default::default (),
|
let state = Arc::new (RelayState::from (&config_file));
|
||||||
response_rendezvous: Default::default (),
|
|
||||||
};
|
|
||||||
|
|
||||||
let state = Arc::new (state);
|
|
||||||
|
|
||||||
run_relay (state).await
|
run_relay (state).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use hyper::{
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
time::delay_for,
|
time::delay_for,
|
||||||
};
|
};
|
||||||
|
@ -64,15 +65,34 @@ async fn handle_req_resp <'a> (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive (Default, Deserialize)]
|
||||||
|
pub struct ConfigFile {
|
||||||
|
pub name: String,
|
||||||
|
pub api_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive (Clone)]
|
#[derive (Clone)]
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
pub relay_url: String,
|
pub relay_url: String,
|
||||||
pub server_name: String,
|
|
||||||
pub file_server_root: PathBuf,
|
pub file_server_root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> {
|
pub async fn main (config_file: ConfigFile, opt: Opt)
|
||||||
let client = Arc::new (Client::new ());
|
-> Result <(), Box <dyn Error>>
|
||||||
|
{
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
let tripcode = base64::encode (blake3::hash (config_file.api_key.as_bytes ()).as_bytes ());
|
||||||
|
|
||||||
|
println! ("Our tripcode is {}", tripcode);
|
||||||
|
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new ();
|
||||||
|
headers.insert ("X-ApiKey", config_file.api_key.try_into ().unwrap ());
|
||||||
|
|
||||||
|
// TODO: (FN46S2M2) Combine these Arcs
|
||||||
|
let client = Arc::new (Client::builder ()
|
||||||
|
.default_headers (headers)
|
||||||
|
.build ().unwrap ());
|
||||||
let opt = Arc::new (opt);
|
let opt = Arc::new (opt);
|
||||||
let handlebars = Arc::new (file_server::load_templates ()?);
|
let handlebars = Arc::new (file_server::load_templates ()?);
|
||||||
|
|
||||||
|
@ -83,12 +103,14 @@ pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> {
|
||||||
delay_for (Duration::from_millis (backoff_delay)).await;
|
delay_for (Duration::from_millis (backoff_delay)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let req_req = client.get (&format! ("{}/7ZSFUKGV_http_listen/{}", opt.relay_url, opt.server_name));
|
let req_req = client.get (&format! ("{}/7ZSFUKGV_http_listen/{}", opt.relay_url, config_file.name));
|
||||||
|
|
||||||
|
let err_backoff_delay = std::cmp::min (30_000, backoff_delay * 2 + 500);
|
||||||
|
|
||||||
let req_resp = match req_req.send ().await {
|
let req_resp = match req_req.send ().await {
|
||||||
Err (e) => {
|
Err (e) => {
|
||||||
println! ("Err: {:?}", e);
|
eprintln! ("Err: {:?}", e);
|
||||||
backoff_delay = backoff_delay * 2 + 500;
|
backoff_delay = err_backoff_delay;
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
Ok (r) => {
|
Ok (r) => {
|
||||||
|
@ -97,13 +119,20 @@ pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if req_resp.status () != StatusCode::OK {
|
||||||
|
eprintln! ("{}", req_resp.status ());
|
||||||
|
eprintln! ("{}", String::from_utf8 (req_resp.bytes ().await.unwrap ().to_vec ()).unwrap ());
|
||||||
|
backoff_delay = err_backoff_delay;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn another task for each request so we can
|
// Spawn another task for each request so we can
|
||||||
// immediately listen for the next connection
|
// immediately listen for the next connection
|
||||||
|
|
||||||
let client = client.clone ();
|
let client = client.clone ();
|
||||||
let opt = opt.clone ();
|
let opt = opt.clone ();
|
||||||
|
|
||||||
let handlebars = handlebars.clone ();
|
let handlebars = handlebars.clone ();
|
||||||
|
|
||||||
tokio::spawn (async move {
|
tokio::spawn (async move {
|
||||||
handle_req_resp (&opt, handlebars, client, req_resp).await;
|
handle_req_resp (&opt, handlebars, client, req_resp).await;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue