Skip to content

feat: implement TLS for proxy and mgmt servers #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 237 additions & 29 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions certs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TLS Certs

> [!CAUTION]
> These are intended for development only!

This directory contains TLS certs for localhost. These are used to make development easier.

## Making your own certificates

```
openssl req -x509 -out localhost.crt -keyout localhost.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
```

[source](https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates)
19 changes: 19 additions & 0 deletions certs/localhost.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDDzCCAfegAwIBAgIUWSq6qNcWO52SspWx9KyPKMaS/UkwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDEyODEzNDcyNFoXDTI0MDIy
NzEzNDcyNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAoyXbnny+Plmo0syJX7r5R0FSlV0NQWmfapKyi3ChZ6ez
H4e21SXAo0UPOvBFUfp+Gj1t2uTP86XGP8tN59/T1iI8Rvzy7bmfm433Ql2JoUFN
iOjqQYyIH6cepM160nXjupS5gbtWbO2Q/4uYgqcPQegK3gKXkA9rKQmj6toRAyBy
e8lxrE+ULQdjxifjlP5IFpukyWdlq+Meuza+Hzzrp/x2EHsidzhNZvwfO8HAB/Ej
phL6bnmQM0ZkmVhoq5L8F3RnrZlw/I62qp6fq8iF64rZoJtuyvg6xu8UhaCHmI4J
bhA6i56UFBDrZ9Om37zxyMlzKxePoeZh0ieVw5ANMwIDAQABo1kwVzAUBgNVHREE
DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MB0GA1UdDgQWBBS/imYFn4ntU1xD5Z5lUfYSVigy7TANBgkqhkiG9w0BAQsFAAOC
AQEAkzZCmv49YZfIHEDEl84pKMLKO81FY9l+0lhB6y9F1fV93Ch8EByEvmBG6G5m
mdfJi7+4LGVkPNsMs0eD6b4MUmV71pHl+NSKtXuMc2YS8oAL8g6+IFDQAZhl0Mpo
bElR86QvW3AoiC1QDJUFzfVC0WMMRA7YmDzWYyJX9H/zPWPyNvkvAppeVGBj+f1V
AaGcg6yBpM9XZB2jYkIXiPO1J+/X0YnMqn6RqF+Zg+nZROPNwYVzn+TUcffwP47D
az/Itnlh0t3aWQ2rI7NkjPDJQ4FJUfseAWd244un8IE0MO6PLvD1hSWiMG4Dn0BE
j3xFGLw7g67KwKxl9rVDAoPGBg==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions certs/localhost.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjJduefL4+WajS
zIlfuvlHQVKVXQ1BaZ9qkrKLcKFnp7Mfh7bVJcCjRQ868EVR+n4aPW3a5M/zpcY/
y03n39PWIjxG/PLtuZ+bjfdCXYmhQU2I6OpBjIgfpx6kzXrSdeO6lLmBu1Zs7ZD/
i5iCpw9B6AreApeQD2spCaPq2hEDIHJ7yXGsT5QtB2PGJ+OU/kgWm6TJZ2Wr4x67
Nr4fPOun/HYQeyJ3OE1m/B87wcAH8SOmEvpueZAzRmSZWGirkvwXdGetmXD8jraq
np+ryIXritmgm27K+DrG7xSFoIeYjgluEDqLnpQUEOtn06bfvPHIyXMrF4+h5mHS
J5XDkA0zAgMBAAECggEAEHFHUhDGZ6hHoH8mtTQ13V2TAiSvqlEH1QjV38HJMpYv
MlVOMussIAhcwZbnlZyGSwS35qC66JZjhZhq0Jy5T9KBerIRla3ojRfgvJqKvWrE
crDusw6DxZTlPLzMzRs+iVZl39JOoonK3EZoZ3qIyh6lwbxHJCi5pxgmogu6PTNF
nf8zULHWP/8D+vsVWWY7scnZbH5Z/6L0fodeNYnsfs4lh9dStbqs0wKVrcbQnPW7
JYpqdJ0Rr5KpWlrPvqZlzUIlkryihZBhIl5eRIa49fOOcliSxH8QWtBczBYTf/Y8
EiPxcEBURxx9q2SruImhFmkI6T42u2rUtFGyaUFswQKBgQDerRB05P5NBGM+f9PN
cV2mQKAqyHyzq6srLQo2a0X//6xN+NsvtBasm2hOYWSg9+wV2/yeF0TA/2UBFkOh
Iza1l0Fb0MjQGGJLH0YDfSw004v54tahhQQR3l3BI5qBUHjNQDfwRIG1/JaOfJ5l
Y8xUi1I0OT0lM0soBOl3VAIw4QKBgQC7kDZSAzt1hTmZbyOCX3yfMw8oXWc/ATpj
WtzuJTY2PUal00yYm58bUrZmhj0XeYHQOvgXJ9KzMbJMzIWt20xUAIflL6JDZebw
Eceq1j3X6JLqhRpgUgOhVf5yLbRWbsY0h3Q7YxEQf90W4vpgj3XrVkQ6KqsXsml0
g9tzPcF8kwKBgQDMJRfoQyRNEY+29dQFDkDQMYFll8aTpffYLoOlXnWffBPIrDSu
qEj9V8Cp0ypBVOnRJIyVlzmGQt6jv3ijGziGBLR7646fESvUOUij3DcR+zviDS++
hsczZozHi8+TbGZDrfNayEOux3J0ERXaWEM040Gq9Sr0lvD5MH+l0ZPsoQKBgB0Q
JIqiu5TjNuCiiwMJnrrgY4nipzvpCc4ZZ0Bzfan75rWNP0IqYwYN0/ug81hu2IGW
kZis8AYaPkGOM2yUHYiqqGQH9IGzCYzLhH/hQKXzAMjcJRElxDA8rfetQ1NdSNMc
5hLJr/w5g92nABr0P9ZegKXutKIwYAzQ3bFGsXOHAoGAdo2A6Ug9wuGUDrJjSSRW
6B3DomKUB47OQbsOjLi0VY8jTp1YJ1WD4ZV7sjgDjcTeit7g5B5dkoLttwT4/ctj
Zz/4YAmtsJTUxIpk8b2jT79HeWrSrT+zmF/zoIGkx9dCks01dh+DfCQeNHx9PDGz
dAGBSME1eIbMfXkKEYbDpI4=
-----END PRIVATE KEY-----
5 changes: 3 additions & 2 deletions crates/proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ default-run = "soldr"

[dependencies]
anyhow = "1.0"
axum = "0.6.18"
axum = "0.7"
axum-server = { version = "0.6", features = ["tls-rustls"] }
clap = { version = "4.3.8", features = ["derive"] }
hyper = { version = "0.14", features = ["full"] }
lettre = { version = "0.10.4", default-features = false, features = ["smtp-transport", "tokio1", "tokio1-rustls-tls", "builder"] }
Expand All @@ -21,7 +22,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1.0", features = ["full"] }
toml = "0.7.5"
tower = { version = "0.4", features = ["util"] }
tower-http = { version = "0.4.0", features = ["trace", "cors"] }
tower-http = { version = "0.5", features = ["trace", "cors"] }

[dev-dependencies]
criterion = {version = "0.4", features = ["async_tokio"]}
Expand Down
6 changes: 3 additions & 3 deletions crates/proxy/examples/origin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Result;
use axum::http::StatusCode;
use axum::{routing::any, Router};
use tokio::net::TcpListener;
use tokio::time::{sleep, Duration};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

Expand All @@ -20,10 +21,9 @@ async fn main() -> Result<()> {
.route("/timeout", any(timeout_handler));

let addr = "0.0.0.0:8080";
let listener = TcpListener::bind(addr).await?;
tracing::info!("origin listening on {}", addr);
axum::Server::bind(&addr.parse()?)
.serve(origin.into_make_service())
.await?;
axum::serve(listener, origin).await?;

Ok(())
}
Expand Down
20 changes: 16 additions & 4 deletions crates/proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ use std::result::Result as StdResult;
use anyhow::Result;
use axum::body::Body;
use axum::extract::{Extension, State};
use axum::http::Request;
use axum::http::StatusCode;
use axum::http::{HeaderMap, Request, StatusCode};
use axum::response::IntoResponse;
use axum::{routing::any, Router};
use hyper::HeaderMap;
use queue::RetryQueue;
use serde::Deserialize;
use sqlx::sqlite::SqlitePool;
Expand All @@ -32,12 +30,21 @@ use crate::proxy::{proxy, Client};
use crate::request::HttpRequest;
use crate::request::State as RequestState;

#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct Tls {
pub enable: bool,
pub cert_path: Option<String>,
pub key_path: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Config {
pub database_url: String,
pub management_listener: String,
pub ingest_listener: String,
pub tls: Tls,
}

impl Default for Config {
Expand All @@ -48,6 +55,11 @@ impl Default for Config {
database_url: "sqlite::memory:".to_string(),
management_listener: "0.0.0.0:3443".to_string(),
ingest_listener: "0.0.0.0:3000".to_string(),
tls: Tls {
enable: false,
cert_path: None,
key_path: None,
},
}
}
}
Expand Down Expand Up @@ -85,7 +97,7 @@ async fn handler(
let uri = req.uri().to_string();
let headers = transform_headers(req.headers());
let body = req.into_body();
let body = hyper::body::to_bytes(body).await?;
let body = axum::body::to_bytes(body, 1_000_000).await?;
let r = HttpRequest {
method,
uri,
Expand Down
24 changes: 20 additions & 4 deletions crates/proxy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Result;
use axum_server::tls_rustls::RustlsConfig;
use clap::Parser;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

Expand Down Expand Up @@ -32,7 +33,7 @@ async fn main() -> Result<()> {

tokio::spawn(async move {
tracing::info!("management API listening on {}", mgmt_listener);
if let Err(err) = axum::Server::bind(&mgmt_listener)
if let Err(err) = axum_server::bind(mgmt_listener)
.serve(mgmt.into_make_service())
.await
{
Expand All @@ -45,10 +46,25 @@ async fn main() -> Result<()> {
retry_queue.start().await;
});

let tls_config = if config.tls.enable {
let cert_path = config.tls.cert_path.unwrap();
let key_path = config.tls.key_path.unwrap();
Some(RustlsConfig::from_pem_file(cert_path, key_path).await?)
} else {
None
};

tracing::info!("ingest listening on {}", ingest_listener);
axum::Server::bind(&ingest_listener)
.serve(ingest.into_make_service())
.await?;
if let Some(tls_config) = tls_config {
tracing::info!("tls configured for {}", ingest_listener);
axum_server::bind_rustls(ingest_listener, tls_config)
.serve(ingest.into_make_service())
.await?;
} else {
axum_server::bind(ingest_listener)
.serve(ingest.into_make_service())
.await?;
}

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/proxy/src/origin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use axum::http::Uri;
use hyper::Uri;

pub struct Origin {
pub uri: Uri,
Expand Down
5 changes: 1 addition & 4 deletions crates/proxy/src/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use anyhow::{anyhow, Result};
use axum::http::Request;
use axum::http::Uri;
use hyper::client::HttpConnector;
use hyper::Body;
use hyper::Response;
use hyper::{Body, Request, Response, Uri};
use sqlx::SqlitePool;
use tokio::time::{timeout, Duration};

Expand Down
1 change: 1 addition & 0 deletions crates/proxy/tests/integration/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ pub fn config() -> Config {
database_url: "sqlite::memory:".to_string(),
management_listener: "0.0.0.0:3443".to_string(),
ingest_listener: "0.0.0.0:3000".to_string(),
tls: Default::default(),
}
}
56 changes: 28 additions & 28 deletions crates/proxy/tests/integration/ingest.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::common;

use std::net::{SocketAddr, TcpListener};
use std::sync::Arc;

use axum::body::Body;
Expand All @@ -9,6 +8,7 @@ use axum::http::Request;
use axum::http::StatusCode;
use axum::{routing::post, Router};
use soldr::db::RequestState;
use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio::time::{sleep, Duration};
use tower::util::ServiceExt;
Expand Down Expand Up @@ -42,18 +42,14 @@ async fn timeout_handler() -> impl axum::response::IntoResponse {
#[tokio::test]
async fn ingest_save_and_proxy() {
// set up origin server
let listener = TcpListener::bind("0.0.0.0:0".parse::<SocketAddr>().unwrap()).unwrap();
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
let sentinel: Sentinel = Arc::new(Mutex::new(None));
let s2 = sentinel.clone();
let client_app = Router::new().route("/", post(success_handler).with_state(s2));

tokio::spawn(async move {
axum::Server::from_tcp(listener)
.unwrap()
.serve(client_app.into_make_service())
.await
.unwrap();
axum::serve(listener, client_app).await.unwrap();
});

let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
Expand All @@ -74,7 +70,7 @@ async fn ingest_save_and_proxy() {
.method("POST")
.uri("/origins")
.header("Content-Type", "application/json")
.body(body.into())
.body(body)
.unwrap(),
)
.await
Expand Down Expand Up @@ -117,7 +113,9 @@ async fn ingest_save_and_proxy() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let reqs: Vec<db::Request> = serde_json::from_slice(&body).unwrap();
assert_eq!(reqs[0].state, RequestState::Completed);
Expand All @@ -137,7 +135,9 @@ async fn ingest_save_and_proxy() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let attempts: Vec<db::Attempt> = serde_json::from_slice(&body).unwrap();
assert_eq!(attempts[0].id, 1);
Expand All @@ -154,16 +154,12 @@ async fn ingest_proxy_failure() {
common::enable_tracing();

// set up origin server
let listener = TcpListener::bind("0.0.0.0:0".parse::<SocketAddr>().unwrap()).unwrap();
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
let client_app = Router::new().route("/failure", post(failure_handler));

tokio::spawn(async move {
axum::Server::from_tcp(listener)
.unwrap()
.serve(client_app.into_make_service())
.await
.unwrap();
axum::serve(listener, client_app).await.unwrap();
});

let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
Expand All @@ -190,7 +186,7 @@ async fn ingest_proxy_failure() {
.method("POST")
.uri("/origins")
.header("Content-Type", "application/json")
.body(body.into())
.body(body)
.unwrap(),
)
.await
Expand Down Expand Up @@ -231,7 +227,9 @@ async fn ingest_proxy_failure() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let reqs: Vec<db::Request> = serde_json::from_slice(&body).unwrap();
assert_eq!(reqs[0].state, RequestState::Failed);
Expand All @@ -251,7 +249,9 @@ async fn ingest_proxy_failure() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let attempts: Vec<db::Attempt> = serde_json::from_slice(&body).unwrap();
assert_eq!(attempts[0].id, 1);
Expand All @@ -265,16 +265,12 @@ async fn ingest_proxy_timeout() {
common::enable_tracing();

// set up origin server
let listener = TcpListener::bind("0.0.0.0:0".parse::<SocketAddr>().unwrap()).unwrap();
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
let client_app = Router::new().route("/timeout", post(timeout_handler));

tokio::spawn(async move {
axum::Server::from_tcp(listener)
.unwrap()
.serve(client_app.into_make_service())
.await
.unwrap();
axum::serve(listener, client_app).await.unwrap();
});

let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
Expand All @@ -295,7 +291,7 @@ async fn ingest_proxy_timeout() {
.method("POST")
.uri("/origins")
.header("Content-Type", "application/json")
.body(body.into())
.body(body)
.unwrap(),
)
.await
Expand Down Expand Up @@ -336,7 +332,9 @@ async fn ingest_proxy_timeout() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let reqs: Vec<db::Request> = serde_json::from_slice(&body).unwrap();
assert_eq!(reqs[0].state, RequestState::Timeout);
Expand All @@ -356,7 +354,9 @@ async fn ingest_proxy_timeout() {

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
.await
.unwrap();

let attempts: Vec<db::Attempt> = serde_json::from_slice(&body).unwrap();
assert_eq!(attempts[0].id, 1);
Expand Down
Loading