SSH authentication using keys from database
Diff
chartered-db/src/schema.rs | 2 +-
chartered-db/src/users.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
chartered-git/src/main.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
migrations/2021-08-31-214501_create_crates_table/up.sql | 2 +-
4 files changed, 96 insertions(+), 13 deletions(-)
@@ -44,7 +44,7 @@
table! {
users (id) {
id -> Integer,
username -> Integer,
username -> Text,
}
}
@@ -1,10 +1,54 @@
use super::schema::{user_api_keys, user_crate_permissions, user_ssh_keys, users};
use diesel::{Associations, Identifiable, Queryable};
use super::{
schema::{user_api_keys, user_crate_permissions, user_ssh_keys, users},
ConnectionPool, Result,
};
use diesel::{prelude::*, Associations, Identifiable, Queryable};
use std::sync::Arc;
#[derive(Identifiable, Queryable, Associations, PartialEq, Eq, Hash, Debug)]
pub struct User {
id: i32,
username: String,
}
impl User {
pub async fn find_by_api_key(
conn: ConnectionPool,
given_api_key: String,
) -> Result<Option<User>> {
use crate::schema::user_api_keys::dsl::*;
tokio::task::spawn_blocking(move || {
let conn = conn.get().unwrap();
Ok(crate::schema::user_api_keys::table
.filter(api_key.eq(given_api_key))
.inner_join(users::table)
.select((users::dsl::id, users::dsl::username))
.get_result(&conn)
.optional()?)
})
.await?
}
pub async fn find_by_ssh_key(
conn: ConnectionPool,
given_ssh_key: Vec<u8>,
) -> Result<Option<User>> {
use crate::schema::user_ssh_keys::dsl::*;
tokio::task::spawn_blocking(move || {
let conn = conn.get().unwrap();
Ok(crate::schema::user_ssh_keys::table
.filter(ssh_key.eq(given_ssh_key))
.inner_join(users::table)
.select((users::dsl::id, users::dsl::username))
.get_result(&conn)
.optional()?)
})
.await?
}
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Eq, Hash, Debug)]
@@ -17,7 +17,7 @@
server::{self, Auth, Session},
ChannelId, CryptoVec,
};
use thrussh_keys::key;
use thrussh_keys::{key, PublicKeyBase64};
use tokio_util::codec::{Decoder, Encoder as TokioEncoder};
#[tokio::main]
@@ -25,9 +25,11 @@
async fn main() {
env_logger::init();
let mut config = thrussh::server::Config::default();
config.keys.push(key::KeyPair::generate_ed25519().unwrap());
let config = Arc::new(config);
let config = Arc::new(thrussh::server::Config {
methods: thrussh::MethodSet::PUBLICKEY,
keys: vec![key::KeyPair::generate_ed25519().unwrap()],
..thrussh::server::Config::default()
});
let server = Server {
db: chartered_db::init().unwrap(),
@@ -52,6 +54,7 @@
input_bytes: BytesMut::default(),
output_bytes: BytesMut::default(),
db: self.db.clone(),
user: None,
}
}
}
@@ -61,6 +64,7 @@
input_bytes: BytesMut,
output_bytes: BytesMut,
db: chartered_db::ConnectionPool,
user: Option<chartered_db::users::User>,
}
impl Handler {
@@ -84,13 +88,19 @@
impl server::Handler for Handler {
type Error = anyhow::Error;
type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), anyhow::Error>>;
type FutureAuth = Pin<
Box<
dyn Future<
Output = Result<(Handler, server::Auth), <Handler as server::Handler>::Error>,
> + Send,
>,
>;
type FutureUnit = AsyncHandlerFn;
type FutureBool = futures::future::Ready<Result<(Self, Session, bool), anyhow::Error>>;
fn finished_auth(self, auth: Auth) -> Self::FutureAuth {
eprintln!("finished auth");
futures::future::ready(Ok((self, auth)))
Box::pin(futures::future::ready(Ok((self, auth))))
}
fn finished_bool(self, b: bool, s: Session) -> Self::FutureBool {
@@ -155,9 +165,38 @@
Box::pin(futures::future::ready(Ok((self, session))))
}
fn auth_publickey(self, _: &str, _: &key::PublicKey) -> Self::FutureAuth {
eprintln!("finished auth pubkey");
self.finished_auth(server::Auth::Accept)
fn auth_publickey(mut self, _username: &str, key: &key::PublicKey) -> Self::FutureAuth {
let public_key = key.public_key_bytes();
Box::pin(async move {
let login_user =
match chartered_db::users::User::find_by_ssh_key(self.db.clone(), public_key)
.await?
{
Some(user) => user,
None => return self.finished_auth(server::Auth::Reject).await,
};
self.user = Some(login_user);
self.finished_auth(server::Auth::Accept).await
})
}
fn auth_keyboard_interactive(
self,
_user: &str,
_submethods: &str,
_response: Option<server::Response>,
) -> Self::FutureAuth {
self.finished_auth(server::Auth::UnsupportedMethod)
}
fn auth_none(self, _user: &str) -> Self::FutureAuth {
self.finished_auth(server::Auth::UnsupportedMethod)
}
fn auth_password(self, _user: &str, _password: &str) -> Self::FutureAuth {
self.finished_auth(server::Auth::UnsupportedMethod)
}
fn data(mut self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit {
@@ -16,7 +16,7 @@
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username INTEGER NOT NULL UNIQUE
username VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE user_ssh_keys (