Support for TLS encryption

This commit is contained in:
Alphonse Paix
2025-09-05 18:13:35 +02:00
parent 767fc571b6
commit a7d22e6634
8 changed files with 97 additions and 26 deletions

42
Cargo.lock generated
View File

@@ -240,6 +240,28 @@ dependencies = [
"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]]
name = "backtrace"
version = "0.3.75"
@@ -815,6 +837,16 @@ dependencies = [
"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]]
name = "futures"
version = "0.3.31"
@@ -2240,6 +2272,15 @@ dependencies = [
"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]]
name = "rustls-pki-types"
version = "1.12.0"
@@ -3802,6 +3843,7 @@ dependencies = [
"axum",
"axum-extra",
"axum-messages",
"axum-server",
"base64 0.22.1",
"chrono",
"claims",

View File

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

View File

@@ -7,6 +7,8 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
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
COPY . .
ENV SQLX_OFFLINE=true

View File

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

View File

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

View File

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

View File

@@ -9,10 +9,10 @@ use axum::{
routing::{get, post},
};
use axum_messages::MessagesManagerLayer;
use axum_server::tls_rustls::RustlsConfig;
use secrecy::ExposeSecret;
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;
use tokio::net::TcpListener;
use std::{net::TcpListener, sync::Arc};
use tower_http::trace::TraceLayer;
use tower_sessions::SessionManagerLayer;
use tower_sessions_redis_store::{
@@ -21,11 +21,6 @@ use tower_sessions_redis_store::{
};
use uuid::Uuid;
pub struct Application {
listener: TcpListener,
router: Router,
}
#[derive(Clone)]
pub struct AppState {
pub connection_pool: PgPool,
@@ -33,13 +28,19 @@ pub struct AppState {
pub base_url: String,
}
pub struct Application {
listener: TcpListener,
router: Router,
tls_config: Option<RustlsConfig>,
}
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!(
"{}:{}",
configuration.application.host, configuration.application.port
);
let listener = TcpListener::bind(address).await?;
// let listener = TcpListener::bind(address).await?;
let connection_pool =
PgPoolOptions::new().connect_lazy_with(configuration.database.with_db());
let email_client = EmailClient::build(configuration.email_client).unwrap();
@@ -61,17 +62,46 @@ impl Application {
configuration.application.base_url,
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> {
tracing::debug!("listening on {}", self.listener.local_addr().unwrap());
axum::serve(self.listener, self.router).await
tracing::debug!("listening on {}", self.local_addr());
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 {
self.listener.local_addr().unwrap().to_string()
}
pub fn port(&self) -> u16 {
self.listener.local_addr().unwrap().port()
}
}
pub fn app(

View File

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