some more config optinos and password auth with bcrypt hashing
This commit is contained in:
parent
d52dc95a30
commit
1efe2ad5c2
4 changed files with 138 additions and 41 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -155,6 +155,19 @@ version = "1.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blowfish",
|
||||
"getrandom 0.3.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt-pbkdf"
|
||||
version = "0.10.0"
|
||||
|
@ -325,7 +338,7 @@ version = "0.1.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
@ -668,6 +681,7 @@ dependencies = [
|
|||
name = "flux-sftp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
"regex",
|
||||
"russh",
|
||||
|
@ -813,10 +827,22 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
|
@ -1202,7 +1228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -1550,6 +1576,12 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
@ -1577,7 +1609,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1676,7 +1708,7 @@ dependencies = [
|
|||
"flate2",
|
||||
"futures",
|
||||
"generic-array",
|
||||
"getrandom",
|
||||
"getrandom 0.2.16",
|
||||
"hex-literal",
|
||||
"hmac",
|
||||
"home",
|
||||
|
@ -2518,6 +2550,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
|
@ -2917,6 +2958,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.1"
|
||||
|
|
|
@ -12,3 +12,4 @@ tokio = { version = "1.45.1", features = ["full"] }
|
|||
sqlx = { version = "0.8.6", features = [ "runtime-tokio", "postgres", "sqlite", "mysql" ] }
|
||||
toml = "0.8.23"
|
||||
serde = "1.0.219"
|
||||
bcrypt = "0.17.0"
|
||||
|
|
|
@ -13,9 +13,17 @@ pub(crate) struct GeneralConfig {
|
|||
pub(crate) jail_dir: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub(crate) struct DBConfig {
|
||||
#[serde(flatten)]
|
||||
pub(crate) driver: DriverConfig,
|
||||
#[serde(flatten)]
|
||||
pub(crate) common: CommonConfig
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "driver")]
|
||||
pub(crate) enum DBConfig {
|
||||
pub(crate) enum DriverConfig {
|
||||
#[serde(rename = "sqlite")]
|
||||
Sqlite {
|
||||
path: String
|
||||
|
@ -38,6 +46,14 @@ pub(crate) enum DBConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub(crate) struct CommonConfig {
|
||||
pub(crate) table: String,
|
||||
pub(crate) username_field: String,
|
||||
pub(crate) public_key_field: Option<String>,
|
||||
pub(crate) password_field: Option<String>
|
||||
}
|
||||
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
|
@ -47,8 +63,16 @@ impl Default for Config {
|
|||
port: 2222,
|
||||
jail_dir: String::from("/srv/sftp")
|
||||
},
|
||||
database: DBConfig::Sqlite {
|
||||
path: String::from("/var/lib/flux-sftp/auth.db")
|
||||
database: DBConfig {
|
||||
driver: DriverConfig::Sqlite {
|
||||
path: String::from("/var/lib/flux-sftp/auth.db")
|
||||
},
|
||||
common: CommonConfig {
|
||||
table: String::from("users"),
|
||||
username_field: String::from("username"),
|
||||
public_key_field: Some(String::from("public_key")),
|
||||
password_field: None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
88
src/main.rs
88
src/main.rs
|
@ -1,21 +1,22 @@
|
|||
mod sftp;
|
||||
mod config;
|
||||
|
||||
use std::{io::ErrorKind, net::SocketAddr, sync::Arc, time::Duration};
|
||||
use config::{Config, DBConfig};
|
||||
use std::{fmt::format, io::ErrorKind, net::SocketAddr, sync::Arc, time::Duration};
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use config::{Config, DriverConfig};
|
||||
use russh::{keys::ssh_key::{rand_core::OsRng, PublicKey}, server::{Auth, Handler as SshHandler, Msg, Server, Session}, Channel, ChannelId};
|
||||
use sftp::SftpSession;
|
||||
use sqlx::{mysql::MySqlPoolOptions, postgres::PgPoolOptions, sqlite::SqlitePoolOptions, MySql, Pool, Postgres, Row, Sqlite};
|
||||
use tokio::fs;
|
||||
|
||||
macro_rules! fetch_pub_key {
|
||||
($pool:ident, $query:literal, $user:ident) => {
|
||||
macro_rules! fetch_col {
|
||||
($col:ident, $pool:ident, $query:expr, $user:ident) => {
|
||||
{
|
||||
let row_res = sqlx::query($query)
|
||||
let row_res = sqlx::query(&$query)
|
||||
.bind($user)
|
||||
.fetch_one($pool).await;
|
||||
match row_res {
|
||||
Ok(row) => Some(row.get("public_key")),
|
||||
Ok(row) => Some(row.get($col as &str)),
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
|
@ -47,35 +48,53 @@ struct SshSession {
|
|||
impl SshHandler for SshSession {
|
||||
type Error = russh::Error;
|
||||
|
||||
async fn auth_password(
|
||||
&mut self,
|
||||
user: &str,
|
||||
password: &str,
|
||||
) -> Result<Auth, Self::Error> {
|
||||
if let Some(password_field) = &self.config.database.common.password_field {
|
||||
self.user = Some(user.to_string());
|
||||
let offered_hash = match hash(password, DEFAULT_COST) {
|
||||
Ok(hash) => hash,
|
||||
Err(_) => return Ok(Auth::reject())
|
||||
};
|
||||
|
||||
let query = format!("SELECT {} FROM {} WHERE {} = ?", password_field, self.config.database.common.table, self.config.database.common.username_field);
|
||||
let stored_password: String = match &*self.pool {
|
||||
DBPool::Sqlite(pool) => fetch_col!(password_field, pool, query, user),
|
||||
DBPool::Postgres(pool) => fetch_col!(password_field, pool, query.replace("?", "$1"), user),
|
||||
DBPool::Mysql(pool) => fetch_col!(password_field, pool, query, user)
|
||||
}.unwrap_or_default();
|
||||
|
||||
if offered_hash == stored_password { Ok(Auth::Accept) } else { Ok(Auth::reject()) }
|
||||
}
|
||||
else {
|
||||
Ok(Auth::reject())
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_publickey_offered(
|
||||
&mut self,
|
||||
user: &str,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Auth, Self::Error> {
|
||||
self.user = Some(user.to_string());
|
||||
if let Some(public_key_field) = &self.config.database.common.public_key_field {
|
||||
self.user = Some(user.to_string());
|
||||
let offered_key = public_key.to_string();
|
||||
|
||||
let offered_key = public_key.to_string();
|
||||
|
||||
let stored_key_opt: Option<String> = match &*self.pool {
|
||||
DBPool::Sqlite(pool) => fetch_pub_key!(pool, "SELECT * FROM users WHERE username = ?", user),
|
||||
DBPool::Postgres(pool) => fetch_pub_key!(pool, "SELECT * FROM users WHERE username = $1", user),
|
||||
DBPool::Mysql(pool) => fetch_pub_key!(pool, "SELECT * FROM users WHERE username = ?", user)
|
||||
};
|
||||
|
||||
if let Some(stored_key) = stored_key_opt {
|
||||
if stored_key == offered_key {
|
||||
Ok(Auth::Accept)
|
||||
}
|
||||
else {
|
||||
println!("invalid key");
|
||||
Ok(Auth::reject())
|
||||
}
|
||||
let query = format!("SELECT {} FROM {} WHERE {} = ?", public_key_field, self.config.database.common.table, self.config.database.common.username_field);
|
||||
let stored_key: String = match &*self.pool {
|
||||
DBPool::Sqlite(pool) => fetch_col!(public_key_field, pool, query, user),
|
||||
DBPool::Postgres(pool) => fetch_col!(public_key_field, pool, query.replace("?", "$1"), user),
|
||||
DBPool::Mysql(pool) => fetch_col!(public_key_field, pool, query, user)
|
||||
}.unwrap_or_default();
|
||||
|
||||
if offered_key == stored_key { Ok(Auth::Accept) } else { Ok(Auth::reject()) }
|
||||
}
|
||||
else {
|
||||
println!("user not found");
|
||||
Ok(Auth::reject())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn auth_publickey(
|
||||
|
@ -132,6 +151,9 @@ enum DBPool {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), sqlx::Error> {
|
||||
// let config = Config::default();
|
||||
// let toml = toml::to_string(&config).unwrap();
|
||||
// println!("{}", toml);
|
||||
|
||||
const CONFIG_PATH: &str = "/etc/flux-sftp/config.toml";
|
||||
let config: Arc<Config>;
|
||||
|
@ -155,16 +177,16 @@ async fn main() -> Result<(), sqlx::Error> {
|
|||
}
|
||||
}
|
||||
|
||||
let url = match &config.database {
|
||||
DBConfig::Sqlite { path } => format!("sqlite:{}", path),
|
||||
DBConfig::Postgres { host, port, user, password, dbname } => format!("postgres://{}:{}@{}:{}/{}", user, password, host, port, dbname),
|
||||
DBConfig::Mysql { host, port, user, password, dbname } => format!("mysql://{}:{}@{}:{}/{}", user, password, host, port, dbname),
|
||||
let url = match &config.database.driver {
|
||||
DriverConfig::Sqlite { path } => format!("sqlite:{}", path),
|
||||
DriverConfig::Postgres { host, port, user, password, dbname } => format!("postgres://{}:{}@{}:{}/{}", user, password, host, port, dbname),
|
||||
DriverConfig::Mysql { host, port, user, password, dbname } => format!("mysql://{}:{}@{}:{}/{}", user, password, host, port, dbname),
|
||||
};
|
||||
|
||||
let pool = match &config.database {
|
||||
DBConfig::Sqlite { .. } => DBPool::Sqlite(SqlitePoolOptions::new().max_connections(3).connect(&url).await?),
|
||||
DBConfig::Postgres { .. } => DBPool::Postgres(PgPoolOptions::new().max_connections(3).connect(&url).await?),
|
||||
DBConfig::Mysql { .. } => DBPool::Mysql(MySqlPoolOptions::new().max_connections(3).connect(&url).await?)
|
||||
let pool = match &config.database.driver {
|
||||
DriverConfig::Sqlite { .. } => DBPool::Sqlite(SqlitePoolOptions::new().max_connections(3).connect(&url).await?),
|
||||
DriverConfig::Postgres { .. } => DBPool::Postgres(PgPoolOptions::new().max_connections(3).connect(&url).await?),
|
||||
DriverConfig::Mysql { .. } => DBPool::Mysql(MySqlPoolOptions::new().max_connections(3).connect(&url).await?)
|
||||
};
|
||||
|
||||
let mut server = SftpServer { pool: Arc::new(pool), config: config.clone() };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue