server: Prevent passwords and secrets from being printed

This commit is contained in:
Valentin Tolmer 2021-11-11 10:36:42 +01:00 committed by nitnelave
parent 617a0f53fa
commit 9124339b96
8 changed files with 41 additions and 19 deletions

11
Cargo.lock generated
View File

@ -1803,6 +1803,7 @@ dependencies = [
"orion", "orion",
"rand 0.8.4", "rand 0.8.4",
"sea-query", "sea-query",
"secstr",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -2757,6 +2758,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "secstr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cce2c726741c320e5b8f1edd9a21b3c2c292ae94514afd001d41d81ba143dafc"
dependencies = [
"libc",
"serde",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.4.2" version = "2.4.2"

View File

@ -83,5 +83,9 @@ features = ["with-chrono"]
features = ["env", "toml"] features = ["env", "toml"]
version = "*" version = "*"
[dependencies.secstr]
features = ["serde"]
version = "*"
[dev-dependencies] [dev-dependencies]
mockall = "0.9.1" mockall = "0.9.1"

View File

@ -437,7 +437,7 @@ mod tests {
let sql_pool = get_in_memory_db().await; let sql_pool = get_in_memory_db().await;
let config = ConfigurationBuilder::default() let config = ConfigurationBuilder::default()
.ldap_user_dn("admin".to_string()) .ldap_user_dn("admin".to_string())
.ldap_user_pass("test".to_string()) .ldap_user_pass(secstr::SecUtf8::from("test"))
.build() .build()
.unwrap(); .unwrap();
let handler = SqlBackendHandler::new(config, sql_pool); let handler = SqlBackendHandler::new(config, sql_pool);

View File

@ -9,6 +9,7 @@ use async_trait::async_trait;
use lldap_auth::opaque; use lldap_auth::opaque;
use log::*; use log::*;
use sea_query::{Expr, Iden, Query}; use sea_query::{Expr, Iden, Query};
use secstr::SecUtf8;
use sqlx::Row; use sqlx::Row;
type SqlOpaqueHandler = SqlBackendHandler; type SqlOpaqueHandler = SqlBackendHandler;
@ -83,7 +84,7 @@ impl SqlBackendHandler {
impl LoginHandler for SqlBackendHandler { impl LoginHandler for SqlBackendHandler {
async fn bind(&self, request: BindRequest) -> Result<()> { async fn bind(&self, request: BindRequest) -> Result<()> {
if request.name == self.config.ldap_user_dn { if request.name == self.config.ldap_user_dn {
if request.password == self.config.ldap_user_pass { if SecUtf8::from(request.password) == self.config.ldap_user_pass {
return Ok(()); return Ok(());
} else { } else {
debug!(r#"Invalid password for LDAP bind user"#); debug!(r#"Invalid password for LDAP bind user"#);
@ -220,11 +221,12 @@ impl OpaqueHandler for SqlOpaqueHandler {
pub(crate) async fn register_password( pub(crate) async fn register_password(
opaque_handler: &SqlOpaqueHandler, opaque_handler: &SqlOpaqueHandler,
username: &str, username: &str,
password: &str, password: &SecUtf8,
) -> Result<()> { ) -> Result<()> {
let mut rng = rand::rngs::OsRng; let mut rng = rand::rngs::OsRng;
use registration::*; use registration::*;
let registration_start = opaque::client::registration::start_registration(password, &mut rng)?; let registration_start =
opaque::client::registration::start_registration(password.unsecure(), &mut rng)?;
let start_response = opaque_handler let start_response = opaque_handler
.registration_start(ClientRegistrationStartRequest { .registration_start(ClientRegistrationStartRequest {
username: username.to_string(), username: username.to_string(),
@ -321,7 +323,7 @@ mod tests {
attempt_login(&opaque_handler, "bob", "bob00") attempt_login(&opaque_handler, "bob", "bob00")
.await .await
.unwrap_err(); .unwrap_err();
register_password(&opaque_handler, "bob", "bob00").await?; register_password(&opaque_handler, "bob", &secstr::SecUtf8::from("bob00")).await?;
attempt_login(&opaque_handler, "bob", "wrong_password") attempt_login(&opaque_handler, "bob", "wrong_password")
.await .await
.unwrap_err(); .unwrap_err();

View File

@ -6,6 +6,7 @@ use figment::{
}; };
use lettre::message::Mailbox; use lettre::message::Mailbox;
use lldap_auth::opaque::{server::ServerSetup, KeyPair}; use lldap_auth::opaque::{server::ServerSetup, KeyPair};
use secstr::SecUtf8;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, derive_builder::Builder)] #[derive(Clone, Debug, Deserialize, Serialize, derive_builder::Builder)]
@ -23,8 +24,8 @@ pub struct MailOptions {
pub port: u16, pub port: u16,
#[builder(default = r#""admin".to_string()"#)] #[builder(default = r#""admin".to_string()"#)]
pub user: String, pub user: String,
#[builder(default = r#""".to_string()"#)] #[builder(default = r#"SecUtf8::from("")"#)]
pub password: String, pub password: SecUtf8,
#[builder(default = "true")] #[builder(default = "true")]
pub tls_required: bool, pub tls_required: bool,
} }
@ -47,14 +48,14 @@ pub struct Configuration {
pub ldaps_port: u16, pub ldaps_port: u16,
#[builder(default = "17170")] #[builder(default = "17170")]
pub http_port: u16, pub http_port: u16,
#[builder(default = r#"String::from("secretjwtsecret")"#)] #[builder(default = r#"SecUtf8::from("secretjwtsecret")"#)]
pub jwt_secret: String, pub jwt_secret: SecUtf8,
#[builder(default = r#"String::from("dc=example,dc=com")"#)] #[builder(default = r#"String::from("dc=example,dc=com")"#)]
pub ldap_base_dn: String, pub ldap_base_dn: String,
#[builder(default = r#"String::from("admin")"#)] #[builder(default = r#"String::from("admin")"#)]
pub ldap_user_dn: String, pub ldap_user_dn: String,
#[builder(default = r#"String::from("password")"#)] #[builder(default = r#"SecUtf8::from("password")"#)]
pub ldap_user_pass: String, pub ldap_user_pass: SecUtf8,
#[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)] #[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)]
pub database_url: String, pub database_url: String,
#[builder(default = "false")] #[builder(default = "false")]
@ -188,7 +189,7 @@ impl ConfigOverrider for SmtpOpts {
config.smtp_options.user = user.clone(); config.smtp_options.user = user.clone();
} }
if let Some(password) = &self.smtp_password { if let Some(password) = &self.smtp_password {
config.smtp_options.password = password.clone(); config.smtp_options.password = SecUtf8::from(password.clone());
} }
if let Some(tls_required) = self.smtp_tls_required { if let Some(tls_required) = self.smtp_tls_required {
config.smtp_options.tls_required = tls_required; config.smtp_options.tls_required = tls_required;
@ -219,10 +220,10 @@ where
println!("Configuration: {:#?}", &config); println!("Configuration: {:#?}", &config);
} }
config.server_setup = Some(get_server_setup(&config.key_file)?); config.server_setup = Some(get_server_setup(&config.key_file)?);
if config.jwt_secret == "secretjwtsecret" { if config.jwt_secret == SecUtf8::from("secretjwtsecret") {
println!("WARNING: Default JWT secret used! This is highly unsafe and can allow attackers to log in as admin."); println!("WARNING: Default JWT secret used! This is highly unsafe and can allow attackers to log in as admin.");
} }
if config.ldap_user_pass == "password" { if config.ldap_user_pass == SecUtf8::from("password") {
println!("WARNING: Unsecure default admin password is used."); println!("WARNING: Unsecure default admin password is used.");
} }
Ok(config) Ok(config)

View File

@ -22,7 +22,10 @@ pub fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> {
.to(to) .to(to)
.subject("LLDAP test email") .subject("LLDAP test email")
.body("The test is successful! You can send emails from LLDAP".to_string())?; .body("The test is successful! You can send emails from LLDAP".to_string())?;
let creds = Credentials::new(options.user.clone(), options.password.clone()); let creds = Credentials::new(
options.user.clone(),
options.password.unsecure().to_string(),
);
let mailer = SmtpTransport::relay(&options.server)? let mailer = SmtpTransport::relay(&options.server)?
.credentials(creds) .credentials(creds)
.build(); .build();

View File

@ -44,14 +44,14 @@ pub(crate) fn error_to_http_response(error: DomainError) -> HttpResponse {
fn http_config<Backend>( fn http_config<Backend>(
cfg: &mut web::ServiceConfig, cfg: &mut web::ServiceConfig,
backend_handler: Backend, backend_handler: Backend,
jwt_secret: String, jwt_secret: secstr::SecUtf8,
jwt_blacklist: HashSet<u64>, jwt_blacklist: HashSet<u64>,
) where ) where
Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Sync + 'static, Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Sync + 'static,
{ {
cfg.app_data(web::Data::new(AppState::<Backend> { cfg.app_data(web::Data::new(AppState::<Backend> {
backend_handler, backend_handler,
jwt_key: Hmac::new_varkey(jwt_secret.as_bytes()).unwrap(), jwt_key: Hmac::new_varkey(jwt_secret.unsecure().as_bytes()).unwrap(),
jwt_blacklist: RwLock::new(jwt_blacklist), jwt_blacklist: RwLock::new(jwt_blacklist),
})) }))
// Serve index.html and main.js, and default to index.html. // Serve index.html and main.js, and default to index.html.

View File

@ -20,10 +20,11 @@ mod domain;
mod infra; mod infra;
async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration) -> Result<()> { async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration) -> Result<()> {
let pass_length = config.ldap_user_pass.unsecure().len();
assert!( assert!(
config.ldap_user_pass.len() >= 8, pass_length >= 8,
"Minimum password length is 8 characters, got {} characters", "Minimum password length is 8 characters, got {} characters",
config.ldap_user_pass.len() pass_length
); );
handler handler
.create_user(CreateUserRequest { .create_user(CreateUserRequest {