Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamodb and sqlite are both supported as distinct features. #50

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions tunnelto_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ tracing = "0.1.25"
tracing-subscriber = "0.2.17"
tracing-honeycomb = { git = "https://github.com/agrinman/tracing-honeycomb", rev = "687bafa722ccd584f45aa470fbb637bc57c999cd" }

# auth handler
rusoto_core = "0.46"
rusoto_dynamodb = "0.46"
rusoto_credential = "0.46"
# auth handler: dynamodb
rusoto_core = { version = "0.46", optional = true }
rusoto_dynamodb = { version = "0.46", optional = true }
rusoto_credential = { version = "0.46", optional = true }
# auth handler: sqlite
rusqlite = { version = "0.21.0", features = ["bundled"], optional = true }

[features]
default = ["dynamodb"]
dynamodb = ["rusoto_core", "rusoto_dynamodb", "rusoto_credential"]
sqlite = ["rusqlite"]
7 changes: 5 additions & 2 deletions tunnelto_server/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::Formatter;

pub mod auth_db;
#[cfg(feature = "dynamodb")]
pub mod dynamo_auth_db;
#[cfg(feature = "sqlite")]
pub mod sqlite_auth_db;
pub mod client_auth;
pub mod reconnect_token;

Expand Down Expand Up @@ -78,7 +81,7 @@ pub struct NoAuth;
#[async_trait]
impl AuthService for NoAuth {
type Error = ();
type AuthKey = ();
type AuthKey = String;

/// Authenticate a subdomain with an AuthKey
async fn auth_sub_domain(
Expand Down
190 changes: 190 additions & 0 deletions tunnelto_server/src/auth/sqlite_auth_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use rusqlite::{params, NO_PARAMS, Connection};

use super::AuthResult;
use super::super::CONFIG;
use crate::auth::AuthService;
use async_trait::async_trait;
use sha2::Digest;
use std::str::FromStr;
use thiserror::Error;
use uuid::Uuid;
use std::sync::Mutex;

mod domain_db {
pub const TABLE_NAME: &'static str = "tunnelto_domains";
pub const PRIMARY_KEY: &'static str = "subdomain";
pub const ACCOUNT_ID: &'static str = "account_id";
}

mod key_db {
pub const TABLE_NAME: &'static str = "tunnelto_auth";
pub const PRIMARY_KEY: &'static str = "auth_key_hash";
pub const ACCOUNT_ID: &'static str = "account_id";
}

mod record_db {
pub const TABLE_NAME: &'static str = "tunnelto_record";
pub const PRIMARY_KEY: &'static str = "account_id";
pub const SUBSCRIPTION_ID: &'static str = "subscription_id";
}

pub struct AuthDbService {
connection: Mutex<Connection>,
}

impl AuthDbService {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
Fusion marked this conversation as resolved.
Show resolved Hide resolved
let conn = Connection::open(&CONFIG.db_connection_string)?;
conn.execute(
&format!("CREATE TABLE IF NOT EXISTS {} (
{} TEXT NOT NULL,
{} TEXT NOT NULL
)",
domain_db::TABLE_NAME,
domain_db::PRIMARY_KEY,
domain_db::ACCOUNT_ID
),
NO_PARAMS,
)?;
conn.execute(
&format!("CREATE TABLE IF NOT EXISTS {} (
{} TEXT NOT NULL,
{} TEXT NOT NULL
)",
key_db::TABLE_NAME,
key_db::PRIMARY_KEY,
key_db::ACCOUNT_ID
),
NO_PARAMS,
)?;
conn.execute(
&format!("CREATE TABLE IF NOT EXISTS {} (
{} TEXT NOT NULL,
{} TEXT NOT NULL
)",
record_db::TABLE_NAME,
record_db::PRIMARY_KEY,
record_db::SUBSCRIPTION_ID
),
NO_PARAMS,
)?;
Ok( Self{connection: Mutex::new(conn)} )
}
}

impl Drop for AuthDbService {
fn drop(&mut self) {
let c = &*self.connection.lock().unwrap();
drop(c);
}
}

fn key_id(auth_key: &str) -> String {
let hash = sha2::Sha256::digest(auth_key.as_bytes()).to_vec();
base64::encode_config(&hash, base64::URL_SAFE_NO_PAD)
}

#[derive(Error, Debug)]
pub enum Error {
#[error("The authentication key is invalid")]
AccountNotFound,

#[error("The authentication key is invalid")]
InvalidAccountId(#[from] uuid::Error),

#[error("The subdomain is not authorized")]
SubdomainNotAuthorized,
}

#[async_trait]
impl AuthService for AuthDbService {
type Error = Error;
type AuthKey = String;

async fn auth_sub_domain(
&self,
auth_key: &String,
Fusion marked this conversation as resolved.
Show resolved Hide resolved
subdomain: &str,
) -> Result<AuthResult, Error> {
let authenticated_account_id = self.get_account_id_for_auth_key(auth_key).await?;
let is_pro_account = self
.is_account_in_good_standing(authenticated_account_id)
.await?;

tracing::info!(account=%authenticated_account_id.to_string(), requested_subdomain=%subdomain, is_pro=%is_pro_account, "authenticated client");

if let Some(account_id) = self.get_account_id_for_subdomain(subdomain).await? {
// check you reserved it
if authenticated_account_id != account_id {
tracing::info!(account=%authenticated_account_id.to_string(), "reserved by other");
return Ok(AuthResult::ReservedByOther);
}

// next ensure that the account is in good standing
if !is_pro_account {
tracing::warn!(account=%authenticated_account_id.to_string(), "delinquent");
return Ok(AuthResult::ReservedByYouButDelinquent);
}

return Ok(AuthResult::ReservedByYou);
}

if is_pro_account {
Ok(AuthResult::Available)
} else {
Ok(AuthResult::PaymentRequired)
}
}
}

impl AuthDbService {
async fn get_account_id_for_auth_key(&self, auth_key: &str) -> Result<Uuid, Error> {
let auth_key_hash = key_id(auth_key);

let conn:&Connection = &*self.connection.lock().unwrap();
let row: Result<String, _> = conn.query_row(
&format!("SELECT {} FROM {} WHERE {}=?",
agrinman marked this conversation as resolved.
Show resolved Hide resolved
key_db::ACCOUNT_ID,
key_db::TABLE_NAME,
key_db::PRIMARY_KEY
),
params![auth_key_hash,],
|row| row.get(0)
);
Ok(Uuid::from_str(&row.map_err(|_| Error::AccountNotFound)?)?)
}

async fn is_account_in_good_standing(&self, account_id: Uuid) -> Result<bool, Error> {
let conn:&Connection = &*self.connection.lock().unwrap();
let row: Result<String, _> = conn.query_row(
&format!("SELECT {} FROM {} WHERE {}=?",
record_db::SUBSCRIPTION_ID,
record_db::TABLE_NAME,
record_db::PRIMARY_KEY
),
params![account_id.to_string(),],
|row| row.get(0)
);
Ok(row.map_or_else(|_| false, |_| true))
}

async fn get_account_id_for_subdomain(&self, subdomain: &str) -> Result<Option<Uuid>, Error> {
let conn:&Connection = &*self.connection.lock().unwrap();
let row: Result<String, _> = conn.query_row(
&format!("SELECT {} FROM {} WHERE {}=?",
domain_db::ACCOUNT_ID,
domain_db::TABLE_NAME,
domain_db::PRIMARY_KEY
),
params![subdomain,],
|row| row.get(0)
);
let account_str = row.map_or_else(|_| None, |v| Some(v));

if let Some(account_str) = account_str {
Ok(Some(Uuid::from_str(&account_str)?))
} else {
Ok(None)
}
}
}
7 changes: 7 additions & 0 deletions tunnelto_server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub struct Config {
/// Blocked IP addresses
pub blocked_ips: Vec<IpAddr>,

/// Connection string for "old timey" database engines
pub db_connection_string: String,
/// The host on which we create tunnels on
pub tunnel_host: String,
}
Expand Down Expand Up @@ -74,6 +76,10 @@ impl Config {
})
.unwrap_or(vec![]);

let db_connection_string = match std::env::var("DB_CONNECTION_STRING") {
Ok(connection_string) => connection_string,
_ => "./tunnelto.db".to_string(),
};
let tunnel_host = std::env::var("TUNNEL_HOST").unwrap_or("tunnelto.dev".to_string());

Config {
Expand All @@ -87,6 +93,7 @@ impl Config {
honeycomb_api_key,
instance_id,
blocked_ips,
db_connection_string,
tunnel_host,
}
}
Expand Down
22 changes: 16 additions & 6 deletions tunnelto_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ mod active_stream;
use self::active_stream::*;

mod auth;
pub use self::auth::auth_db;
#[cfg(feature = "dynamodb")]
pub use self::auth::dynamo_auth_db;
#[cfg(feature = "sqlite")]
pub use self::auth::sqlite_auth_db;
pub use self::auth::client_auth;

pub use self::auth_db::AuthDbService;
#[cfg(feature = "dynamodb")]
pub use self::dynamo_auth_db::AuthDbService;
#[cfg(feature = "sqlite")]
pub use self::sqlite_auth_db::AuthDbService;
Fusion marked this conversation as resolved.
Show resolved Hide resolved

mod control_server;
mod remote;
Expand All @@ -42,12 +48,16 @@ use tracing::{error, info, Instrument};
lazy_static! {
pub static ref CONNECTIONS: Connections = Connections::new();
pub static ref ACTIVE_STREAMS: ActiveStreams = Arc::new(DashMap::new());
pub static ref CONFIG: Config = Config::from_env();
}
#[cfg(any(feature = "dynamodb", feature="sqlite"))]
lazy_static! {
pub static ref AUTH_DB_SERVICE: AuthDbService =
AuthDbService::new().expect("failed to init auth-service");
pub static ref CONFIG: Config = Config::from_env();

// To disable all authentication:
// pub static ref AUTH_DB_SERVICE: crate::auth::NoAuth = crate::auth::NoAuth;
}
#[cfg(not(any(feature = "dynamodb", feature="sqlite")))]
lazy_static! {
pub static ref AUTH_DB_SERVICE: crate::auth::NoAuth = crate::auth::NoAuth;
}

#[tokio::main]
Expand Down