extract setup logic
This commit is contained in:
parent
a7c9d554f6
commit
14c7a96c28
4 changed files with 179 additions and 250 deletions
174
src/future_rest_admin_api.txt
Normal file
174
src/future_rest_admin_api.txt
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
Command::FirstLogin {
|
||||||
|
creds,
|
||||||
|
user_secrets,
|
||||||
|
} => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let user_secrets = make_user_secrets(user_secrets);
|
||||||
|
|
||||||
|
println!("Please enter your password for key decryption.");
|
||||||
|
println!("If you are using LDAP login, this must be your LDAP password.");
|
||||||
|
println!("If you are using the static login provider, enter any password, and this will also become your password for local IMAP access.");
|
||||||
|
let password = rpassword::prompt_password("Enter password: ")?;
|
||||||
|
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
||||||
|
if password != password_confirm {
|
||||||
|
bail!("Passwords don't match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoKeys::init(&creds, &user_secrets, &password).await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Cryptographic key setup is complete.");
|
||||||
|
println!("");
|
||||||
|
println!("If you are using the static login provider, add the following section to your .toml configuration file:");
|
||||||
|
println!("");
|
||||||
|
dump_config(&password, &creds);
|
||||||
|
}
|
||||||
|
Command::InitializeLocalKeys { creds } => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
|
||||||
|
println!("Please enter a password for local IMAP access.");
|
||||||
|
println!("This password is not used for key decryption, your keys will be printed below (do not lose them!)");
|
||||||
|
println!(
|
||||||
|
"If you plan on using LDAP login, stop right here and use `first-login` instead"
|
||||||
|
);
|
||||||
|
let password = rpassword::prompt_password("Enter password: ")?;
|
||||||
|
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
||||||
|
if password != password_confirm {
|
||||||
|
bail!("Passwords don't match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let master = gen_key();
|
||||||
|
let (_, secret) = gen_keypair();
|
||||||
|
let keys = CryptoKeys::init_without_password(&creds, &master, &secret).await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Cryptographic key setup is complete.");
|
||||||
|
println!("");
|
||||||
|
println!("Add the following section to your .toml configuration file:");
|
||||||
|
println!("");
|
||||||
|
dump_config(&password, &creds);
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
Command::AddPassword {
|
||||||
|
creds,
|
||||||
|
user_secrets,
|
||||||
|
gen,
|
||||||
|
} => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let user_secrets = make_user_secrets(user_secrets);
|
||||||
|
|
||||||
|
let existing_password =
|
||||||
|
rpassword::prompt_password("Enter existing password to decrypt keys: ")?;
|
||||||
|
let new_password = if gen {
|
||||||
|
let password = base64::encode_config(
|
||||||
|
&u128::to_be_bytes(thread_rng().gen())[..10],
|
||||||
|
base64::URL_SAFE_NO_PAD,
|
||||||
|
);
|
||||||
|
println!("Your new password: {}", password);
|
||||||
|
println!("Keep it safe!");
|
||||||
|
password
|
||||||
|
} else {
|
||||||
|
let password = rpassword::prompt_password("Enter new password: ")?;
|
||||||
|
let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
|
||||||
|
if password != password_confirm {
|
||||||
|
bail!("Passwords don't match.");
|
||||||
|
}
|
||||||
|
password
|
||||||
|
};
|
||||||
|
|
||||||
|
let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
|
||||||
|
keys.add_password(&creds, &user_secrets, &new_password)
|
||||||
|
.await?;
|
||||||
|
println!("");
|
||||||
|
println!("New password added successfully.");
|
||||||
|
}
|
||||||
|
Command::DeletePassword {
|
||||||
|
creds,
|
||||||
|
user_secrets,
|
||||||
|
allow_delete_all,
|
||||||
|
} => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let user_secrets = make_user_secrets(user_secrets);
|
||||||
|
|
||||||
|
let existing_password = rpassword::prompt_password("Enter password to delete: ")?;
|
||||||
|
|
||||||
|
let keys = match allow_delete_all {
|
||||||
|
true => Some(CryptoKeys::open(&creds, &user_secrets, &existing_password).await?),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
CryptoKeys::delete_password(&creds, &existing_password, allow_delete_all).await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Password was deleted successfully.");
|
||||||
|
|
||||||
|
if let Some(keys) = keys {
|
||||||
|
println!("As a reminder, here are your cryptographic keys:");
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::ShowKeys {
|
||||||
|
creds,
|
||||||
|
user_secrets,
|
||||||
|
} => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let user_secrets = make_user_secrets(user_secrets);
|
||||||
|
|
||||||
|
let existing_password = rpassword::prompt_password("Enter key decryption password: ")?;
|
||||||
|
|
||||||
|
let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_storage_creds(c: StorageCredsArgs) -> StorageCredentials {
|
||||||
|
let s3_region = Region {
|
||||||
|
name: c.region.clone(),
|
||||||
|
endpoint: c.s3_endpoint,
|
||||||
|
};
|
||||||
|
let k2v_region = Region {
|
||||||
|
name: c.region,
|
||||||
|
endpoint: c.k2v_endpoint,
|
||||||
|
};
|
||||||
|
StorageCredentials {
|
||||||
|
k2v_region,
|
||||||
|
s3_region,
|
||||||
|
aws_access_key_id: c.aws_access_key_id,
|
||||||
|
aws_secret_access_key: c.aws_secret_access_key,
|
||||||
|
bucket: c.bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_user_secrets(c: UserSecretsArgs) -> UserSecrets {
|
||||||
|
UserSecrets {
|
||||||
|
user_secret: c.user_secret,
|
||||||
|
alternate_user_secrets: c
|
||||||
|
.alternate_user_secrets
|
||||||
|
.split(',')
|
||||||
|
.map(|x| x.trim())
|
||||||
|
.filter(|x| !x.is_empty())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_config(password: &str, creds: &StorageCredentials) {
|
||||||
|
println!("[login_static.users.<username>]");
|
||||||
|
println!(
|
||||||
|
"password = \"{}\"",
|
||||||
|
hash_password(password).expect("unable to hash password")
|
||||||
|
);
|
||||||
|
println!("aws_access_key_id = \"{}\"", creds.aws_access_key_id);
|
||||||
|
println!(
|
||||||
|
"aws_secret_access_key = \"{}\"",
|
||||||
|
creds.aws_secret_access_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_keys(keys: &CryptoKeys) {
|
||||||
|
println!("master_key = \"{}\"", base64::encode(&keys.master));
|
||||||
|
println!("secret_key = \"{}\"", base64::encode(&keys.secret));
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::cryptoblob::{open_deserialize, seal_serialize};
|
use crate::cryptoblob::{open_deserialize, seal_serialize};
|
||||||
use crate::login::{Credentials, StorageCredentials};
|
use crate::login::Credentials;
|
||||||
use crate::mail::incoming::incoming_mail_watch_process;
|
use crate::mail::incoming::incoming_mail_watch_process;
|
||||||
use crate::mail::mailbox::Mailbox;
|
use crate::mail::mailbox::Mailbox;
|
||||||
use crate::mail::uidindex::ImapUidvalidity;
|
use crate::mail::uidindex::ImapUidvalidity;
|
||||||
|
@ -309,7 +309,7 @@ impl User {
|
||||||
Some(x) => self.k2v.from_orphan(x).expect("Source & target must be same storage"),
|
Some(x) => self.k2v.from_orphan(x).expect("Source & target must be same storage"),
|
||||||
None => self.k2v.row(MAILBOX_LIST_PK, MAILBOX_LIST_SK),
|
None => self.k2v.row(MAILBOX_LIST_PK, MAILBOX_LIST_SK),
|
||||||
};
|
};
|
||||||
rref.set_value(list_blob).push().await?;
|
rref.set_value(&list_blob).push().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
247
src/main.rs
247
src/main.rs
|
@ -14,13 +14,10 @@ mod storage;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
use config::*;
|
use config::*;
|
||||||
use cryptoblob::*;
|
|
||||||
use login::{static_provider::*, *};
|
|
||||||
use server::Server;
|
use server::Server;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -36,74 +33,7 @@ enum Command {
|
||||||
Server {
|
Server {
|
||||||
#[clap(short, long, env = "CONFIG_FILE", default_value = "aerogramme.toml")]
|
#[clap(short, long, env = "CONFIG_FILE", default_value = "aerogramme.toml")]
|
||||||
config_file: PathBuf,
|
config_file: PathBuf,
|
||||||
},
|
|
||||||
/// TEST TEST TEST
|
|
||||||
Test {
|
|
||||||
#[clap(short, long, env = "CONFIG_FILE", default_value = "aerogramme.toml")]
|
|
||||||
config_file: PathBuf,
|
|
||||||
},
|
|
||||||
/// Initializes key pairs for a user and adds a key decryption password
|
|
||||||
FirstLogin {
|
|
||||||
#[clap(flatten)]
|
|
||||||
creds: StorageCredsArgs,
|
|
||||||
#[clap(flatten)]
|
|
||||||
user_secrets: UserSecretsArgs,
|
|
||||||
},
|
|
||||||
/// Initializes key pairs for a user and dumps keys to stdout for usage with static
|
|
||||||
/// login provider
|
|
||||||
InitializeLocalKeys {
|
|
||||||
#[clap(flatten)]
|
|
||||||
creds: StorageCredsArgs,
|
|
||||||
},
|
|
||||||
/// Adds a key decryption password for a user
|
|
||||||
AddPassword {
|
|
||||||
#[clap(flatten)]
|
|
||||||
creds: StorageCredsArgs,
|
|
||||||
#[clap(flatten)]
|
|
||||||
user_secrets: UserSecretsArgs,
|
|
||||||
/// Automatically generate password
|
|
||||||
#[clap(short, long)]
|
|
||||||
gen: bool,
|
|
||||||
},
|
|
||||||
/// Deletes a key decription password for a user
|
|
||||||
DeletePassword {
|
|
||||||
#[clap(flatten)]
|
|
||||||
creds: StorageCredsArgs,
|
|
||||||
#[clap(flatten)]
|
|
||||||
user_secrets: UserSecretsArgs,
|
|
||||||
/// Allow to delete all passwords
|
|
||||||
#[clap(long)]
|
|
||||||
allow_delete_all: bool,
|
|
||||||
},
|
|
||||||
/// Dumps all encryption keys for user
|
|
||||||
ShowKeys {
|
|
||||||
#[clap(flatten)]
|
|
||||||
creds: StorageCredsArgs,
|
|
||||||
#[clap(flatten)]
|
|
||||||
user_secrets: UserSecretsArgs,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct StorageCredsArgs {
|
|
||||||
/// Name of the region to use
|
|
||||||
#[clap(short = 'r', long, env = "AWS_REGION")]
|
|
||||||
region: String,
|
|
||||||
/// Url of the endpoint to connect to for K2V
|
|
||||||
#[clap(short = 'k', long, env = "K2V_ENDPOINT")]
|
|
||||||
k2v_endpoint: String,
|
|
||||||
/// Url of the endpoint to connect to for S3
|
|
||||||
#[clap(short = 's', long, env = "S3_ENDPOINT")]
|
|
||||||
s3_endpoint: String,
|
|
||||||
/// Access key ID
|
|
||||||
#[clap(short = 'A', long, env = "AWS_ACCESS_KEY_ID")]
|
|
||||||
aws_access_key_id: String,
|
|
||||||
/// Access key ID
|
|
||||||
#[clap(short = 'S', long, env = "AWS_SECRET_ACCESS_KEY")]
|
|
||||||
aws_secret_access_key: String,
|
|
||||||
/// Bucket name
|
|
||||||
#[clap(short = 'b', long, env = "BUCKET")]
|
|
||||||
bucket: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -140,183 +70,8 @@ async fn main() -> Result<()> {
|
||||||
let server = Server::new(config).await?;
|
let server = Server::new(config).await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
}
|
}
|
||||||
Command::Test { config_file } => {
|
|
||||||
let config = read_config(config_file)?;
|
|
||||||
|
|
||||||
let _server = Server::new(config).await?;
|
|
||||||
//server.test().await?;
|
|
||||||
}
|
|
||||||
Command::FirstLogin {
|
|
||||||
creds,
|
|
||||||
user_secrets,
|
|
||||||
} => {
|
|
||||||
let creds = make_storage_creds(creds);
|
|
||||||
let user_secrets = make_user_secrets(user_secrets);
|
|
||||||
|
|
||||||
println!("Please enter your password for key decryption.");
|
|
||||||
println!("If you are using LDAP login, this must be your LDAP password.");
|
|
||||||
println!("If you are using the static login provider, enter any password, and this will also become your password for local IMAP access.");
|
|
||||||
let password = rpassword::prompt_password("Enter password: ")?;
|
|
||||||
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
|
||||||
if password != password_confirm {
|
|
||||||
bail!("Passwords don't match.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CryptoKeys::init(&creds, &user_secrets, &password).await?;
|
|
||||||
|
|
||||||
println!("");
|
|
||||||
println!("Cryptographic key setup is complete.");
|
|
||||||
println!("");
|
|
||||||
println!("If you are using the static login provider, add the following section to your .toml configuration file:");
|
|
||||||
println!("");
|
|
||||||
dump_config(&password, &creds);
|
|
||||||
}
|
|
||||||
Command::InitializeLocalKeys { creds } => {
|
|
||||||
let creds = make_storage_creds(creds);
|
|
||||||
|
|
||||||
println!("Please enter a password for local IMAP access.");
|
|
||||||
println!("This password is not used for key decryption, your keys will be printed below (do not lose them!)");
|
|
||||||
println!(
|
|
||||||
"If you plan on using LDAP login, stop right here and use `first-login` instead"
|
|
||||||
);
|
|
||||||
let password = rpassword::prompt_password("Enter password: ")?;
|
|
||||||
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
|
||||||
if password != password_confirm {
|
|
||||||
bail!("Passwords don't match.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let master = gen_key();
|
|
||||||
let (_, secret) = gen_keypair();
|
|
||||||
let keys = CryptoKeys::init_without_password(&creds, &master, &secret).await?;
|
|
||||||
|
|
||||||
println!("");
|
|
||||||
println!("Cryptographic key setup is complete.");
|
|
||||||
println!("");
|
|
||||||
println!("Add the following section to your .toml configuration file:");
|
|
||||||
println!("");
|
|
||||||
dump_config(&password, &creds);
|
|
||||||
dump_keys(&keys);
|
|
||||||
}
|
|
||||||
Command::AddPassword {
|
|
||||||
creds,
|
|
||||||
user_secrets,
|
|
||||||
gen,
|
|
||||||
} => {
|
|
||||||
let creds = make_storage_creds(creds);
|
|
||||||
let user_secrets = make_user_secrets(user_secrets);
|
|
||||||
|
|
||||||
let existing_password =
|
|
||||||
rpassword::prompt_password("Enter existing password to decrypt keys: ")?;
|
|
||||||
let new_password = if gen {
|
|
||||||
let password = base64::encode_config(
|
|
||||||
&u128::to_be_bytes(thread_rng().gen())[..10],
|
|
||||||
base64::URL_SAFE_NO_PAD,
|
|
||||||
);
|
|
||||||
println!("Your new password: {}", password);
|
|
||||||
println!("Keep it safe!");
|
|
||||||
password
|
|
||||||
} else {
|
|
||||||
let password = rpassword::prompt_password("Enter new password: ")?;
|
|
||||||
let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
|
|
||||||
if password != password_confirm {
|
|
||||||
bail!("Passwords don't match.");
|
|
||||||
}
|
|
||||||
password
|
|
||||||
};
|
|
||||||
|
|
||||||
let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
|
|
||||||
keys.add_password(&creds, &user_secrets, &new_password)
|
|
||||||
.await?;
|
|
||||||
println!("");
|
|
||||||
println!("New password added successfully.");
|
|
||||||
}
|
|
||||||
Command::DeletePassword {
|
|
||||||
creds,
|
|
||||||
user_secrets,
|
|
||||||
allow_delete_all,
|
|
||||||
} => {
|
|
||||||
let creds = make_storage_creds(creds);
|
|
||||||
let user_secrets = make_user_secrets(user_secrets);
|
|
||||||
|
|
||||||
let existing_password = rpassword::prompt_password("Enter password to delete: ")?;
|
|
||||||
|
|
||||||
let keys = match allow_delete_all {
|
|
||||||
true => Some(CryptoKeys::open(&creds, &user_secrets, &existing_password).await?),
|
|
||||||
false => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
CryptoKeys::delete_password(&creds, &existing_password, allow_delete_all).await?;
|
|
||||||
|
|
||||||
println!("");
|
|
||||||
println!("Password was deleted successfully.");
|
|
||||||
|
|
||||||
if let Some(keys) = keys {
|
|
||||||
println!("As a reminder, here are your cryptographic keys:");
|
|
||||||
dump_keys(&keys);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::ShowKeys {
|
|
||||||
creds,
|
|
||||||
user_secrets,
|
|
||||||
} => {
|
|
||||||
let creds = make_storage_creds(creds);
|
|
||||||
let user_secrets = make_user_secrets(user_secrets);
|
|
||||||
|
|
||||||
let existing_password = rpassword::prompt_password("Enter key decryption password: ")?;
|
|
||||||
|
|
||||||
let keys = CryptoKeys::open(&creds, &user_secrets, &existing_password).await?;
|
|
||||||
dump_keys(&keys);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_storage_creds(c: StorageCredsArgs) -> StorageCredentials {
|
|
||||||
let s3_region = Region {
|
|
||||||
name: c.region.clone(),
|
|
||||||
endpoint: c.s3_endpoint,
|
|
||||||
};
|
|
||||||
let k2v_region = Region {
|
|
||||||
name: c.region,
|
|
||||||
endpoint: c.k2v_endpoint,
|
|
||||||
};
|
|
||||||
StorageCredentials {
|
|
||||||
k2v_region,
|
|
||||||
s3_region,
|
|
||||||
aws_access_key_id: c.aws_access_key_id,
|
|
||||||
aws_secret_access_key: c.aws_secret_access_key,
|
|
||||||
bucket: c.bucket,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_user_secrets(c: UserSecretsArgs) -> UserSecrets {
|
|
||||||
UserSecrets {
|
|
||||||
user_secret: c.user_secret,
|
|
||||||
alternate_user_secrets: c
|
|
||||||
.alternate_user_secrets
|
|
||||||
.split(',')
|
|
||||||
.map(|x| x.trim())
|
|
||||||
.filter(|x| !x.is_empty())
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dump_config(password: &str, creds: &StorageCredentials) {
|
|
||||||
println!("[login_static.users.<username>]");
|
|
||||||
println!(
|
|
||||||
"password = \"{}\"",
|
|
||||||
hash_password(password).expect("unable to hash password")
|
|
||||||
);
|
|
||||||
println!("aws_access_key_id = \"{}\"", creds.aws_access_key_id);
|
|
||||||
println!(
|
|
||||||
"aws_secret_access_key = \"{}\"",
|
|
||||||
creds.aws_secret_access_key
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dump_keys(keys: &CryptoKeys) {
|
|
||||||
println!("master_key = \"{}\"", base64::encode(&keys.master));
|
|
||||||
println!("secret_key = \"{}\"", base64::encode(&keys.secret));
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::config::*;
|
||||||
use crate::imap;
|
use crate::imap;
|
||||||
use crate::lmtp::*;
|
use crate::lmtp::*;
|
||||||
use crate::login::ArcLoginProvider;
|
use crate::login::ArcLoginProvider;
|
||||||
use crate::login::{ldap_provider::*, static_provider::*, Region};
|
use crate::login::{ldap_provider::*, static_provider::*};
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
lmtp_server: Option<Arc<LmtpServer>>,
|
lmtp_server: Option<Arc<LmtpServer>>,
|
||||||
|
|
Loading…
Reference in a new issue