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:
Ursprung
9a9786e370
Commit
ed26fa3640
6 geänderte Dateien mit 93 neuen und 13 gelöschten Zeilen
35
Cargo.lock
generiert
35
Cargo.lock
generiert
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
3
build.rs
3
build.rs
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
47
src/mail.rs
47
src/mail.rs
|
@ -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(()),
|
||||
|
|
|
@ -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]
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren