parse config file

This commit is contained in:
nora 2024-08-26 22:34:26 +02:00
parent 187478464c
commit 13c49524ba
8 changed files with 314 additions and 107 deletions

View file

@ -17,6 +17,8 @@ users = "0.11.0"
futures = "0.3.30"
thiserror = "1.0.63"
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
serde = { version = "1.0.209", features = ["derive"] }
toml = "0.8.19"
[lints]
workspace = true

View file

@ -0,0 +1,13 @@
log_level = "info"
[net]
ip = "0.0.0.0"
port = 2223
[auth]
host_keys = [
# "/etc/ssh/ssh_host_ed25519_key",
"./test_ed25519_key"
]
password_login = false
banner = "welcome to my server!!!\r\ni hope you enjoy your stay.\r\n"

View file

@ -6,6 +6,9 @@ use cluelessh_keys::{
authorized_keys::{self, AuthorizedKeys},
public::{PublicKey, PublicKeyWithComment},
};
use cluelessh_protocol::auth::{CheckPubkey, VerifySignature};
use eyre::eyre;
use tracing::debug;
use users::os::unix::UserExt;
/// A known-authorized public key for a user.
@ -54,3 +57,63 @@ impl UserPublicKey {
self.0.key.verify_signature(data, signature)
}
}
pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<bool> {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
return Ok(false);
};
if auth.pubkey_alg_name != public_key.algorithm_name() {
return Ok(false);
}
let result: std::result::Result<UserPublicKey, AuthError> =
UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature");
match result {
Ok(user_key) => {
// Verify signature...
let sign_data = cluelessh_keys::signature::signature_data(
auth.session_identifier,
&auth.user,
&public_key,
);
if user_key.verify_signature(&sign_data, &auth.signature) {
Ok(true)
} else {
Ok(false)
}
}
Err(
AuthError::UnknownUser
| AuthError::UnauthorizedPublicKey
| AuthError::NoAuthorizedKeys(_),
) => Ok(false),
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
}
}
pub async fn check_pubkey(auth: CheckPubkey) -> eyre::Result<bool> {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
return Ok(false);
};
if auth.pubkey_alg_name != public_key.algorithm_name() {
return Ok(false);
}
let result = UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey check");
match result {
Ok(_) => Ok(true),
Err(
AuthError::UnknownUser
| AuthError::UnauthorizedPublicKey
| AuthError::NoAuthorizedKeys(_),
) => Ok(false),
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
}
}

View file

@ -0,0 +1,62 @@
use eyre::{Context, Result};
use serde::Deserialize;
use std::{
net::{IpAddr, Ipv4Addr},
path::PathBuf,
};
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default = "default_info")]
pub log_level: String,
pub net: NetConfig,
pub auth: AuthConfig,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct NetConfig {
#[serde(default = "addr_default")]
pub ip: IpAddr,
#[serde(default = "port_default")]
pub port: u16,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthConfig {
pub host_keys: Vec<PathBuf>,
#[serde(default = "default_true")]
pub password_login: bool,
pub banner: Option<String>,
}
impl Config {
pub fn find() -> Result<Self> {
let path =
std::env::var("CLUELESSHD_CONFIG").unwrap_or_else(|_| "cluelesshd.toml".to_owned());
let content = std::fs::read_to_string(&path).wrap_err_with(|| {
format!("failed to open config file '{path}', refusing to start. you can change the config file path with the CLUELESSHD_CONFIG environment variable")
})?;
toml::from_str(&content).wrap_err_with(|| format!("invalid config file '{path}'"))
}
}
fn default_info() -> String {
"info".to_owned()
}
fn default_true() -> bool {
true
}
fn addr_default() -> IpAddr {
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
}
fn port_default() -> u16 {
22
}

View file

@ -1,19 +1,20 @@
mod auth;
mod config;
mod pty;
use std::{
io,
net::SocketAddr,
path::PathBuf,
pin::Pin,
process::{ExitStatus, Stdio},
sync::Arc,
};
use auth::AuthError;
use cluelessh_keys::{private::EncryptedPrivateKeys, public::PublicKey};
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys};
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
use cluelessh_transport::server::ServerConfig;
use eyre::{bail, eyre, Context, OptionExt, Result};
use eyre::{bail, Context, OptionExt, Result};
use pty::Pty;
use rustix::termios::Winsize;
use tokio::{
@ -34,115 +35,28 @@ use users::os::unix::UserExt;
#[tokio::main(flavor = "current_thread")]
async fn main() -> eyre::Result<()> {
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
let config = config::Config::find()?;
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let addr = "0.0.0.0:2223".to_owned();
let addr = addr
.parse::<SocketAddr>()
.wrap_err_with(|| format!("failed to parse listen addr '{addr}'"))?;
let addr = SocketAddr::new(config.net.ip, config.net.port);
info!(%addr, "Starting server");
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
let listener = TcpListener::bind(addr)
.await
.wrap_err_with(|| format!("trying to listen on {addr}"))?;
let auth_verify = ServerAuthVerify {
verify_password: None,
verify_signature: Some(Arc::new(|auth| {
Box::pin(async move {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
return Ok(false);
};
if auth.pubkey_alg_name != public_key.algorithm_name() {
return Ok(false);
}
let result: std::result::Result<auth::UserPublicKey, AuthError> =
auth::UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature");
match result {
Ok(user_key) => {
// Verify signature...
let sign_data = cluelessh_keys::signature::signature_data(
auth.session_identifier,
&auth.user,
&public_key,
);
if user_key.verify_signature(&sign_data, &auth.signature) {
Ok(true)
} else {
Ok(false)
}
}
Err(
AuthError::UnknownUser
| AuthError::UnauthorizedPublicKey
| AuthError::NoAuthorizedKeys(_),
) => Ok(false),
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
}
})
})),
check_pubkey: Some(Arc::new(|auth| {
Box::pin(async move {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
return Ok(false);
};
if auth.pubkey_alg_name != public_key.algorithm_name() {
return Ok(false);
}
let result =
auth::UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey check");
match result {
Ok(_) => Ok(true),
Err(
AuthError::UnknownUser
| AuthError::UnauthorizedPublicKey
| AuthError::NoAuthorizedKeys(_),
) => Ok(false),
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
}
})
})),
auth_banner: Some("welcome to my server!!!\r\ni hope you enjoy your stay.\r\n".to_owned()),
verify_password: config.auth.password_login.then(|| todo!("password login")),
verify_signature: Some(Arc::new(|auth| Box::pin(auth::verify_signature(auth)))),
check_pubkey: Some(Arc::new(|auth| Box::pin(auth::check_pubkey(auth)))),
auth_banner: config.auth.banner,
};
let mut host_keys = Vec::new();
let host_key_locations = ["/etc/ssh/ssh_host_ed25519_key", "./test_ed25519_key"];
for host_key_location in host_key_locations {
match tokio::fs::read_to_string(host_key_location).await {
Ok(key) => {
let key = EncryptedPrivateKeys::parse(key.as_bytes())
.wrap_err_with(|| format!("invalid {host_key_location}"))?;
if key.requires_passphrase() {
bail!("{host_key_location} must not require a passphrase");
}
let mut key = key
.decrypt(None)
.wrap_err_with(|| format!("invalid {host_key_location}"))?;
if key.len() != 1 {
bail!("{host_key_location} must contain a single key");
}
host_keys.push(key.remove(0));
info!(?host_key_location, "Loaded host key")
}
Err(err) => {
debug!(?err, ?host_key_location, "Failed to load host key")
}
}
}
let host_keys = load_host_keys(&config.auth.host_keys).await?.into_keys();
if host_keys.is_empty() {
bail!("no host keys found");
@ -173,6 +87,39 @@ async fn main() -> eyre::Result<()> {
}
}
async fn load_host_keys(keys: &[PathBuf]) -> Result<HostKeySet> {
let mut host_keys = HostKeySet::new();
for key_path in keys {
load_host_key(key_path, &mut host_keys)
.await
.wrap_err_with(|| format!("loading host key at '{}'", key_path.display()))?;
}
Ok(host_keys)
}
async fn load_host_key(key_path: &PathBuf, host_keys: &mut HostKeySet) -> Result<()> {
let key = tokio::fs::read_to_string(key_path)
.await
.wrap_err("failed to open")?;
let key = EncryptedPrivateKeys::parse(key.as_bytes()).wrap_err("failed to parse")?;
if key.requires_passphrase() {
bail!("host key requires a passphrase, which is not allowed");
}
let mut key = key.decrypt(None).wrap_err("failed to parse")?;
if key.len() != 1 {
bail!("host key must contain a single key");
}
let key = key.remove(0);
let algorithm = key.private_key.algorithm_name();
host_keys.insert(key)?;
info!(?key_path, ?algorithm, "Loaded host key");
Ok(())
}
async fn handle_connection(
mut conn: cluelessh_tokio::server::ServerConnection<TcpStream>,
) -> Result<()> {