Support for TLS encryption

This commit is contained in:
Alphonse Paix
2025-09-05 18:13:35 +02:00
parent a4104ca1b2
commit 8d6cab41d0
11 changed files with 102 additions and 37 deletions

42
Cargo.lock generated
View File

@@ -240,6 +240,28 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-server"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab"
dependencies = [
"arc-swap",
"bytes",
"fs-err",
"http",
"http-body",
"hyper",
"hyper-util",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.75" version = "0.3.75"
@@ -815,6 +837,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "fs-err"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683"
dependencies = [
"autocfg",
"tokio",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.31" version = "0.3.31"
@@ -2240,6 +2272,15 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.12.0" version = "1.12.0"
@@ -3802,6 +3843,7 @@ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
"axum-messages", "axum-messages",
"axum-server",
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",
"claims", "claims",

View File

@@ -17,6 +17,7 @@ argon2 = { version = "0.5.3", features = ["std"] }
axum = { version = "0.8.4", features = ["macros"] } axum = { version = "0.8.4", features = ["macros"] }
axum-extra = { version = "0.10.1", features = ["query", "cookie"] } axum-extra = { version = "0.10.1", features = ["query", "cookie"] }
axum-messages = "0.8.0" axum-messages = "0.8.0"
axum-server = { version = "0.7.2", features = ["tls-rustls-no-provider"] }
base64 = "0.22.1" base64 = "0.22.1"
chrono = { version = "0.4.41", default-features = false, features = ["clock"] } chrono = { version = "0.4.41", default-features = false, features = ["clock"] }
config = "0.15.14" config = "0.15.14"

View File

@@ -7,6 +7,8 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json COPY --from=planner /app/recipe.json recipe.json
RUN apt update -y \
&& apt install -y --no-install-recommends clang mold
RUN cargo chef cook --release --recipe-path recipe.json RUN cargo chef cook --release --recipe-path recipe.json
COPY . . COPY . .
ENV SQLX_OFFLINE=true ENV SQLX_OFFLINE=true

View File

@@ -1,6 +1,4 @@
application:
port: 8000
database:
database_name: "newsletter"
email_client: email_client:
timeout_milliseconds: 10000 timeout_milliseconds: 10000
base_url: "https://api.mailersend.com"
sender_email: "MS_PTrumQ@test-r6ke4n1mmzvgon12.mlsender.net"

View File

@@ -1,14 +1,15 @@
application: application:
port: 8000
host: "127.0.0.1" host: "127.0.0.1"
base_url: "http://127.0.0.1:8000" base_url: "http://127.0.0.1:8000"
require_tls: false
database: database:
host: "127.0.0.1" host: "127.0.0.1"
port: 5432 port: 5432
database_name: "newsletter"
username: "postgres" username: "postgres"
password: "password" password: "password"
require_ssl: false require_ssl: false
email_client: email_client:
base_url: "https://api.mailersend.com"
sender_email: "MS_PTrumQ@test-r6ke4n1mmzvgon12.mlsender.net"
authorization_token: "secret-token" authorization_token: "secret-token"
redis_uri: "redis://127.0.0.1:6379" redis_uri: "redis://127.0.0.1:6379"

View File

@@ -1,7 +1,2 @@
application: application:
host: "0.0.0.0" host: "0.0.0.0"
database:
require_ssl: true
email_client:
base_url: "https://api.mailersend.com"
sender_email: "MS_PTrumQ@test-r6ke4n1mmzvgon12.mlsender.net"

View File

@@ -69,6 +69,7 @@ pub struct ApplicationSettings {
pub port: u16, pub port: u16,
pub host: String, pub host: String,
pub base_url: String, pub base_url: String,
pub require_tls: bool,
} }
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]

View File

@@ -4,7 +4,7 @@ use zero2prod::{
}; };
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> Result<(), anyhow::Error> {
init_subscriber(std::io::stdout); init_subscriber(std::io::stdout);
let configuration = get_configuration().expect("Failed to read configuration"); let configuration = get_configuration().expect("Failed to read configuration");

View File

@@ -79,7 +79,7 @@ impl IntoResponse for SubscribeError {
#[tracing::instrument( #[tracing::instrument(
name = "Adding a new subscriber", name = "Adding a new subscriber",
skip(connection_pool, email_client, base_url, form), skip(messages, connection_pool, email_client, base_url, form),
fields( fields(
subscriber_email = %form.email, subscriber_email = %form.email,
subscriber_name = %form.name subscriber_name = %form.name

View File

@@ -9,10 +9,10 @@ use axum::{
routing::{get, post}, routing::{get, post},
}; };
use axum_messages::MessagesManagerLayer; use axum_messages::MessagesManagerLayer;
use axum_server::tls_rustls::RustlsConfig;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use sqlx::{PgPool, postgres::PgPoolOptions}; use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc; use std::{net::TcpListener, sync::Arc};
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use tower_sessions::SessionManagerLayer; use tower_sessions::SessionManagerLayer;
use tower_sessions_redis_store::{ use tower_sessions_redis_store::{
@@ -21,11 +21,6 @@ use tower_sessions_redis_store::{
}; };
use uuid::Uuid; use uuid::Uuid;
pub struct Application {
listener: TcpListener,
router: Router,
}
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
pub connection_pool: PgPool, pub connection_pool: PgPool,
@@ -33,13 +28,19 @@ pub struct AppState {
pub base_url: String, pub base_url: String,
} }
pub struct Application {
listener: TcpListener,
router: Router,
tls_config: Option<RustlsConfig>,
}
impl Application { impl Application {
pub async fn build(configuration: Settings) -> Result<Self, std::io::Error> { pub async fn build(configuration: Settings) -> Result<Self, anyhow::Error> {
let address = format!( let address = format!(
"{}:{}", "{}:{}",
configuration.application.host, configuration.application.port configuration.application.host, configuration.application.port
); );
let listener = TcpListener::bind(address).await?; // let listener = TcpListener::bind(address).await?;
let connection_pool = let connection_pool =
PgPoolOptions::new().connect_lazy_with(configuration.database.with_db()); PgPoolOptions::new().connect_lazy_with(configuration.database.with_db());
let email_client = EmailClient::build(configuration.email_client).unwrap(); let email_client = EmailClient::build(configuration.email_client).unwrap();
@@ -61,17 +62,46 @@ impl Application {
configuration.application.base_url, configuration.application.base_url,
redis_store, redis_store,
); );
Ok(Self { listener, router }) let tls_config = if configuration.application.require_tls {
Some(
RustlsConfig::from_pem_file(
"/home/alphonse/.certs/fullchain.pem",
"/home/alphonse/.certs/privkey.pem",
)
.await
.unwrap(),
)
} else {
None
};
let listener = TcpListener::bind(address).unwrap();
Ok(Self {
listener,
router,
tls_config,
})
} }
pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { pub async fn run_until_stopped(self) -> Result<(), std::io::Error> {
tracing::debug!("listening on {}", self.listener.local_addr().unwrap()); tracing::debug!("listening on {}", self.local_addr());
axum::serve(self.listener, self.router).await if let Some(tls_config) = self.tls_config {
axum_server::from_tcp_rustls(self.listener, tls_config)
.serve(self.router.into_make_service())
.await
} else {
axum_server::from_tcp(self.listener)
.serve(self.router.into_make_service())
.await
}
} }
pub fn local_addr(&self) -> String { pub fn local_addr(&self) -> String {
self.listener.local_addr().unwrap().to_string() self.listener.local_addr().unwrap().to_string()
} }
pub fn port(&self) -> u16 {
self.listener.local_addr().unwrap().port()
}
} }
pub fn app( pub fn app(

View File

@@ -87,19 +87,14 @@ impl TestApp {
c.email_client.base_url = email_server.uri(); c.email_client.base_url = email_server.uri();
c c
}; };
let local_addr = configuration.application.host.clone();
let connection_pool = configure_database(&configuration.database).await; let connection_pool = configure_database(&configuration.database).await;
let email_client = EmailClient::build(configuration.email_client.clone()).unwrap(); let email_client = EmailClient::build(configuration.email_client.clone()).unwrap();
let application = Application::build(configuration) let application = Application::build(configuration)
.await .await
.expect("Failed to build application"); .expect("Failed to build application");
let local_addr = application.local_addr(); let port = application.port();
let port = local_addr let address = format!("http://{}:{}", local_addr, port);
.split(":")
.last()
.unwrap()
.parse::<u16>()
.unwrap();
let address = format!("http://{}", application.local_addr());
let test_user = TestUser::generate(); let test_user = TestUser::generate();
test_user.store(&connection_pool).await; test_user.store(&connection_pool).await;
let api_client = reqwest::Client::builder() let api_client = reqwest::Client::builder()