diff --git a/Cargo.lock b/Cargo.lock index d0eebd4..1422a73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index ed740e5..5590256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/Dockerfile b/Dockerfile index e2e18f3..fa60aef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 @@ -15,10 +17,10 @@ RUN cargo build --release --bin zero2prod FROM debian:bookworm-slim AS runtime WORKDIR /app RUN apt update -y \ -&& apt install -y --no-install-recommends openssl ca-certificates \ -&& apt autoremove -y \ -&& apt clean -y \ -&& rm -rf /var/lib/apt/lists/* + && apt install -y --no-install-recommends openssl ca-certificates \ + && apt autoremove -y \ + && apt clean -y \ + && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/release/zero2prod zero2prod COPY configuration configuration ENV APP_ENVIRONMENT=production diff --git a/configuration/base.yaml b/configuration/base.yaml index 9420ba9..f3fb86b 100644 --- a/configuration/base.yaml +++ b/configuration/base.yaml @@ -1,6 +1,4 @@ -application: - port: 8000 -database: - database_name: "newsletter" email_client: timeout_milliseconds: 10000 + base_url: "https://api.mailersend.com" + sender_email: "MS_PTrumQ@test-r6ke4n1mmzvgon12.mlsender.net" diff --git a/configuration/local.yaml b/configuration/local.yaml index ae1127d..b0211e9 100644 --- a/configuration/local.yaml +++ b/configuration/local.yaml @@ -1,14 +1,15 @@ application: + port: 8000 host: "127.0.0.1" base_url: "http://127.0.0.1:8000" + require_tls: false database: host: "127.0.0.1" port: 5432 + database_name: "newsletter" username: "postgres" password: "password" require_ssl: false email_client: - base_url: "https://api.mailersend.com" - sender_email: "MS_PTrumQ@test-r6ke4n1mmzvgon12.mlsender.net" authorization_token: "secret-token" redis_uri: "redis://127.0.0.1:6379" diff --git a/configuration/production.yaml b/configuration/production.yaml index a10756f..1ecbd34 100644 --- a/configuration/production.yaml +++ b/configuration/production.yaml @@ -1,7 +1,2 @@ application: 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" diff --git a/src/configuration.rs b/src/configuration.rs index 4912ebb..683bd1b 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -69,6 +69,7 @@ pub struct ApplicationSettings { pub port: u16, pub host: String, pub base_url: String, + pub require_tls: bool, } #[derive(Clone, Deserialize)] diff --git a/src/main.rs b/src/main.rs index 89e0fa4..f273c1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"); diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs index e821662..1c9640d 100644 --- a/src/routes/subscriptions.rs +++ b/src/routes/subscriptions.rs @@ -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 diff --git a/src/startup.rs b/src/startup.rs index ba136e9..5235723 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -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, +} + impl Application { - pub async fn build(configuration: Settings) -> Result { + pub async fn build(configuration: Settings) -> Result { 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( diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index ee28006..49eb5a7 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -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::() - .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()