1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-03-12 16:47:03 +01:00

Add AWS SES for sending emails

Dieser Commit ist enthalten in:
Chase Douglas 2025-02-12 12:25:56 -08:00
Ursprung 9a9786e370
Commit ed26fa3640
6 geänderte Dateien mit 93 neuen und 13 gelöschten Zeilen

35
Cargo.lock generiert
Datei anzeigen

@ -405,9 +405,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee7643696e7fdd74c10f9eb42848a87fe469d35eae9c3323f80aa98f350baac"
checksum = "76dd04d39cc12844c0994f2c9c5a6f5184c22e9188ec1ff723de41910a21dcad"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@ -488,6 +488,28 @@ dependencies = [
"url",
]
[[package]]
name = "aws-sdk-sesv2"
version = "1.65.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5771f0ab7f960545a569d2a916363423e2faebeb01f0cc22716d25c6088f0a7b"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sso"
version = "1.52.0"
@ -557,9 +579,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.2.7"
version = "1.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "690118821e46967b3c4501d67d7d52dd75106a9c54cf36cefa1985cedbe94e05"
checksum = "0bc5bbd1e4a2648fd8c5982af03935972c24a2f9846b396de661d351ee3ce837"
dependencies = [
"aws-credential-types",
"aws-smithy-eventstream",
@ -749,9 +771,9 @@ dependencies = [
[[package]]
name = "aws-types"
version = "1.3.4"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0df5a18c4f951c645300d365fec53a61418bcf4650f604f85fe2a665bfaa0c2"
checksum = "dfbd0a668309ec1f66c0f6bda4840dd6d4796ae26d699ebc266d7cc95c6d040f"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@ -4936,6 +4958,7 @@ dependencies = [
"aws-config",
"aws-sdk-dsql",
"aws-sdk-s3",
"aws-sdk-sesv2",
"bigdecimal",
"bytes",
"cached",

Datei anzeigen

@ -20,10 +20,11 @@ build = "build.rs"
enable_syslog = []
mysql = ["diesel/mysql", "diesel_migrations/mysql"]
postgresql = ["diesel/postgres", "diesel_migrations/postgres"]
aws = ["dsql", "s3"]
aws = ["dsql", "s3", "ses"]
dsql = ["postgresql", "dep:aws-config", "dep:aws-sdk-dsql"]
sqlite = ["diesel/sqlite", "diesel_migrations/sqlite", "dep:libsqlite3-sys"]
s3 = ["dep:aws-config", "dep:aws-sdk-s3"]
ses = ["dep:aws-config", "dep:aws-sdk-sesv2"]
# Enable to use a vendored and statically linked openssl
vendored_openssl = ["openssl/vendored"]
# Enable MiMalloc memory allocator to replace the default malloc
@ -91,10 +92,11 @@ diesel-derive-newtype = "2.1.2"
# Bundled/Static SQLite
libsqlite3-sys = { version = "0.31.0", features = ["bundled"], optional = true }
# AWS / Amazon Aurora DSQL
# AWS Services
aws-config = { version = "1.5.12", features = ["behavior-version-latest"], optional = true }
aws-sdk-s3 = { version = "1.72.0", features = ["behavior-version-latest"], optional = true }
aws-sdk-dsql = { version = "1.2.0", features = ["behavior-version-latest"], optional = true }
aws-sdk-sesv2 = { version = "1.65.0", features = ["behavior-version-latest"], optional = true }
# Crypto-related libraries
rand = "0.9.0"

Datei anzeigen

@ -15,6 +15,8 @@ fn main() {
println!("cargo:rustc-cfg=query_logger");
#[cfg(feature = "s3")]
println!("cargo:rustc-cfg=s3");
#[cfg(feature = "ses")]
println!("cargo:rustc-cfg=ses");
#[cfg(feature = "aws")]
println!("cargo:rustc-cfg=aws");
@ -31,6 +33,7 @@ fn main() {
println!("cargo::rustc-check-cfg=cfg(dsql)");
println!("cargo::rustc-check-cfg=cfg(query_logger)");
println!("cargo::rustc-check-cfg=cfg(s3)");
println!("cargo::rustc-check-cfg=cfg(ses)");
println!("cargo::rustc-check-cfg=cfg(aws)");
// Rerun when these paths are changed.

Datei anzeigen

@ -741,12 +741,14 @@ make_config! {
smtp_accept_invalid_certs: bool, true, def, false;
/// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
smtp_accept_invalid_hostnames: bool, true, def, false;
/// Use AWS SES |> Whether to send mail via AWS Simple Email Service (SES)
use_aws_ses: bool, true, def, false;
},
/// Email 2FA Settings
email_2fa: _enable_email_2fa {
/// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
_enable_email_2fa: bool, true, auto, |c| c._enable_smtp && (c.smtp_host.is_some() || c.use_sendmail);
_enable_email_2fa: bool, true, auto, |c| c._enable_smtp && (c.smtp_host.is_some() || c.use_sendmail || c.use_aws_ses);
/// Email token size |> Number of digits in an email 2FA token (min: 6, max: 255). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting.
email_token_size: u8, true, def, 6;
/// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
@ -951,6 +953,9 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
}
}
}
} else if cfg.use_aws_ses {
#[cfg(not(ses))]
err!("`USE_AWS_SES` is set, but the `ses` feature is not enabled in this build");
} else {
if cfg.smtp_host.is_some() == cfg.smtp_from.is_empty() {
err!("Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support without `USE_SENDMAIL`")
@ -961,7 +966,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
}
}
if (cfg.smtp_host.is_some() || cfg.use_sendmail) && !is_valid_email(&cfg.smtp_from) {
if (cfg.smtp_host.is_some() || cfg.use_sendmail || cfg.use_aws_ses) && !is_valid_email(&cfg.smtp_from) {
err!(format!("SMTP_FROM '{}' is not a valid email address", cfg.smtp_from))
}
@ -970,7 +975,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
}
}
if cfg._enable_email_2fa && !(cfg.smtp_host.is_some() || cfg.use_sendmail) {
if cfg._enable_email_2fa && !(cfg.smtp_host.is_some() || cfg.use_sendmail || cfg.use_aws_ses) {
err!("To enable email 2FA, a mail transport must be configured")
}
@ -1288,7 +1293,7 @@ impl Config {
}
pub fn mail_enabled(&self) -> bool {
let inner = &self.inner.read().unwrap().config;
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail || inner.use_aws_ses)
}
pub async fn get_duo_akey(&self) -> String {

Datei anzeigen

@ -95,6 +95,44 @@ fn smtp_transport() -> AsyncSmtpTransport<Tokio1Executor> {
smtp_client.build()
}
#[cfg(ses)]
async fn send_with_aws_ses(email: Message) -> std::io::Result<()> {
use std::io::Error;
use aws_sdk_sesv2::{types::{EmailContent, RawMessage}, Client};
use crate::aws::aws_sdk_config;
fn sesv2_client() -> std::io::Result<Client> {
static AWS_SESV2_CLIENT: std::sync::LazyLock<std::io::Result<Client>> = std::sync::LazyLock::new(|| {
Ok(Client::new(aws_sdk_config()?))
});
(*AWS_SESV2_CLIENT)
.as_ref()
.map(|client| client.clone())
.map_err(|e| match e.get_ref() {
Some(inner) => Error::new(e.kind(), inner),
None => Error::from(e.kind()),
})
}
sesv2_client()?
.send_email()
.content(
EmailContent::builder().raw(
RawMessage::builder()
.data(email.formatted().into())
.build()
.map_err(|e| Error::other(format!("Failed to build AWS SESv2 RawMessage: {e:#?}")))?
)
.build()
)
.send()
.await
.map_err(|e| Error::other(e))?;
Ok(())
}
// This will sanitize the string values by stripping all the html tags to prevent XSS and HTML Injections
fn sanitize_data(data: &mut serde_json::Value) {
use regex::Regex;
@ -605,6 +643,15 @@ async fn send_with_selected_transport(email: Message) -> EmptyResult {
}
}
}
} else if CONFIG.use_aws_ses() {
#[cfg(ses)]
match send_with_aws_ses(email).await {
Ok(_) => Ok(()),
Err(e) => err!("Failed to send email", format!("Failed to send email using AWS SES: {e:?}"))
}
#[cfg(not(ses))]
err!("Failed to send email", "Failed to send email using AWS SES: `ses` feature is not enabled");
} else {
match smtp_transport().send(email).await {
Ok(_) => Ok(()),

Datei anzeigen

@ -45,7 +45,7 @@ use tokio::{
#[cfg(unix)]
use tokio::signal::unix::SignalKind;
#[cfg(any(dsql, s3))]
#[cfg(any(dsql, s3, ses))]
mod aws;
#[macro_use]