Skip to content

Commit 149eb44

Browse files
authored
Merge pull request #67 from hjr3/tls
feat: implement TLS for proxy and mgmt servers
2 parents 26bb5e1 + 5638330 commit 149eb44

File tree

15 files changed

+405
-92
lines changed

15 files changed

+405
-92
lines changed

Cargo.lock

+237-29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

certs/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# TLS Certs
2+
3+
> [!CAUTION]
4+
> These are intended for development only!
5+
6+
This directory contains TLS certs for localhost. These are used to make development easier.
7+
8+
## Making your own certificates
9+
10+
```
11+
openssl req -x509 -out localhost.crt -keyout localhost.key \
12+
-newkey rsa:2048 -nodes -sha256 \
13+
-subj '/CN=localhost' -extensions EXT -config <( \
14+
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
15+
```
16+
17+
[source](https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates)

certs/localhost.crt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDDzCCAfegAwIBAgIUWSq6qNcWO52SspWx9KyPKMaS/UkwDQYJKoZIhvcNAQEL
3+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDEyODEzNDcyNFoXDTI0MDIy
4+
NzEzNDcyNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
5+
AAOCAQ8AMIIBCgKCAQEAoyXbnny+Plmo0syJX7r5R0FSlV0NQWmfapKyi3ChZ6ez
6+
H4e21SXAo0UPOvBFUfp+Gj1t2uTP86XGP8tN59/T1iI8Rvzy7bmfm433Ql2JoUFN
7+
iOjqQYyIH6cepM160nXjupS5gbtWbO2Q/4uYgqcPQegK3gKXkA9rKQmj6toRAyBy
8+
e8lxrE+ULQdjxifjlP5IFpukyWdlq+Meuza+Hzzrp/x2EHsidzhNZvwfO8HAB/Ej
9+
phL6bnmQM0ZkmVhoq5L8F3RnrZlw/I62qp6fq8iF64rZoJtuyvg6xu8UhaCHmI4J
10+
bhA6i56UFBDrZ9Om37zxyMlzKxePoeZh0ieVw5ANMwIDAQABo1kwVzAUBgNVHREE
11+
DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
12+
MB0GA1UdDgQWBBS/imYFn4ntU1xD5Z5lUfYSVigy7TANBgkqhkiG9w0BAQsFAAOC
13+
AQEAkzZCmv49YZfIHEDEl84pKMLKO81FY9l+0lhB6y9F1fV93Ch8EByEvmBG6G5m
14+
mdfJi7+4LGVkPNsMs0eD6b4MUmV71pHl+NSKtXuMc2YS8oAL8g6+IFDQAZhl0Mpo
15+
bElR86QvW3AoiC1QDJUFzfVC0WMMRA7YmDzWYyJX9H/zPWPyNvkvAppeVGBj+f1V
16+
AaGcg6yBpM9XZB2jYkIXiPO1J+/X0YnMqn6RqF+Zg+nZROPNwYVzn+TUcffwP47D
17+
az/Itnlh0t3aWQ2rI7NkjPDJQ4FJUfseAWd244un8IE0MO6PLvD1hSWiMG4Dn0BE
18+
j3xFGLw7g67KwKxl9rVDAoPGBg==
19+
-----END CERTIFICATE-----

certs/localhost.key

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjJduefL4+WajS
3+
zIlfuvlHQVKVXQ1BaZ9qkrKLcKFnp7Mfh7bVJcCjRQ868EVR+n4aPW3a5M/zpcY/
4+
y03n39PWIjxG/PLtuZ+bjfdCXYmhQU2I6OpBjIgfpx6kzXrSdeO6lLmBu1Zs7ZD/
5+
i5iCpw9B6AreApeQD2spCaPq2hEDIHJ7yXGsT5QtB2PGJ+OU/kgWm6TJZ2Wr4x67
6+
Nr4fPOun/HYQeyJ3OE1m/B87wcAH8SOmEvpueZAzRmSZWGirkvwXdGetmXD8jraq
7+
np+ryIXritmgm27K+DrG7xSFoIeYjgluEDqLnpQUEOtn06bfvPHIyXMrF4+h5mHS
8+
J5XDkA0zAgMBAAECggEAEHFHUhDGZ6hHoH8mtTQ13V2TAiSvqlEH1QjV38HJMpYv
9+
MlVOMussIAhcwZbnlZyGSwS35qC66JZjhZhq0Jy5T9KBerIRla3ojRfgvJqKvWrE
10+
crDusw6DxZTlPLzMzRs+iVZl39JOoonK3EZoZ3qIyh6lwbxHJCi5pxgmogu6PTNF
11+
nf8zULHWP/8D+vsVWWY7scnZbH5Z/6L0fodeNYnsfs4lh9dStbqs0wKVrcbQnPW7
12+
JYpqdJ0Rr5KpWlrPvqZlzUIlkryihZBhIl5eRIa49fOOcliSxH8QWtBczBYTf/Y8
13+
EiPxcEBURxx9q2SruImhFmkI6T42u2rUtFGyaUFswQKBgQDerRB05P5NBGM+f9PN
14+
cV2mQKAqyHyzq6srLQo2a0X//6xN+NsvtBasm2hOYWSg9+wV2/yeF0TA/2UBFkOh
15+
Iza1l0Fb0MjQGGJLH0YDfSw004v54tahhQQR3l3BI5qBUHjNQDfwRIG1/JaOfJ5l
16+
Y8xUi1I0OT0lM0soBOl3VAIw4QKBgQC7kDZSAzt1hTmZbyOCX3yfMw8oXWc/ATpj
17+
WtzuJTY2PUal00yYm58bUrZmhj0XeYHQOvgXJ9KzMbJMzIWt20xUAIflL6JDZebw
18+
Eceq1j3X6JLqhRpgUgOhVf5yLbRWbsY0h3Q7YxEQf90W4vpgj3XrVkQ6KqsXsml0
19+
g9tzPcF8kwKBgQDMJRfoQyRNEY+29dQFDkDQMYFll8aTpffYLoOlXnWffBPIrDSu
20+
qEj9V8Cp0ypBVOnRJIyVlzmGQt6jv3ijGziGBLR7646fESvUOUij3DcR+zviDS++
21+
hsczZozHi8+TbGZDrfNayEOux3J0ERXaWEM040Gq9Sr0lvD5MH+l0ZPsoQKBgB0Q
22+
JIqiu5TjNuCiiwMJnrrgY4nipzvpCc4ZZ0Bzfan75rWNP0IqYwYN0/ug81hu2IGW
23+
kZis8AYaPkGOM2yUHYiqqGQH9IGzCYzLhH/hQKXzAMjcJRElxDA8rfetQ1NdSNMc
24+
5hLJr/w5g92nABr0P9ZegKXutKIwYAzQ3bFGsXOHAoGAdo2A6Ug9wuGUDrJjSSRW
25+
6B3DomKUB47OQbsOjLi0VY8jTp1YJ1WD4ZV7sjgDjcTeit7g5B5dkoLttwT4/ctj
26+
Zz/4YAmtsJTUxIpk8b2jT79HeWrSrT+zmF/zoIGkx9dCks01dh+DfCQeNHx9PDGz
27+
dAGBSME1eIbMfXkKEYbDpI4=
28+
-----END PRIVATE KEY-----

crates/proxy/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ default-run = "soldr"
66

77
[dependencies]
88
anyhow = "1.0"
9-
axum = "0.6.18"
9+
axum = "0.7"
10+
axum-server = { version = "0.6", features = ["tls-rustls"] }
1011
clap = { version = "4.3.8", features = ["derive"] }
1112
hyper = { version = "0.14", features = ["full"] }
1213
lettre = { version = "0.10.4", default-features = false, features = ["smtp-transport", "tokio1", "tokio1-rustls-tls", "builder"] }
@@ -21,7 +22,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2122
tokio = { version = "1.0", features = ["full"] }
2223
toml = "0.7.5"
2324
tower = { version = "0.4", features = ["util"] }
24-
tower-http = { version = "0.4.0", features = ["trace", "cors"] }
25+
tower-http = { version = "0.5", features = ["trace", "cors"] }
2526

2627
[dev-dependencies]
2728
criterion = {version = "0.4", features = ["async_tokio"]}

crates/proxy/examples/origin.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use axum::http::StatusCode;
33
use axum::{routing::any, Router};
4+
use tokio::net::TcpListener;
45
use tokio::time::{sleep, Duration};
56
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
67

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

2223
let addr = "0.0.0.0:8080";
24+
let listener = TcpListener::bind(addr).await?;
2325
tracing::info!("origin listening on {}", addr);
24-
axum::Server::bind(&addr.parse()?)
25-
.serve(origin.into_make_service())
26-
.await?;
26+
axum::serve(listener, origin).await?;
2727

2828
Ok(())
2929
}

crates/proxy/src/lib.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ use std::result::Result as StdResult;
1515
use anyhow::Result;
1616
use axum::body::Body;
1717
use axum::extract::{Extension, State};
18-
use axum::http::Request;
19-
use axum::http::StatusCode;
18+
use axum::http::{HeaderMap, Request, StatusCode};
2019
use axum::response::IntoResponse;
2120
use axum::{routing::any, Router};
22-
use hyper::HeaderMap;
2321
use queue::RetryQueue;
2422
use serde::Deserialize;
2523
use sqlx::sqlite::SqlitePool;
@@ -32,12 +30,21 @@ use crate::proxy::{proxy, Client};
3230
use crate::request::HttpRequest;
3331
use crate::request::State as RequestState;
3432

33+
#[derive(Debug, Default, Deserialize)]
34+
#[serde(default)]
35+
pub struct Tls {
36+
pub enable: bool,
37+
pub cert_path: Option<String>,
38+
pub key_path: Option<String>,
39+
}
40+
3541
#[derive(Debug, Deserialize)]
3642
#[serde(default)]
3743
pub struct Config {
3844
pub database_url: String,
3945
pub management_listener: String,
4046
pub ingest_listener: String,
47+
pub tls: Tls,
4148
}
4249

4350
impl Default for Config {
@@ -48,6 +55,11 @@ impl Default for Config {
4855
database_url: "sqlite::memory:".to_string(),
4956
management_listener: "0.0.0.0:3443".to_string(),
5057
ingest_listener: "0.0.0.0:3000".to_string(),
58+
tls: Tls {
59+
enable: false,
60+
cert_path: None,
61+
key_path: None,
62+
},
5163
}
5264
}
5365
}
@@ -85,7 +97,7 @@ async fn handler(
8597
let uri = req.uri().to_string();
8698
let headers = transform_headers(req.headers());
8799
let body = req.into_body();
88-
let body = hyper::body::to_bytes(body).await?;
100+
let body = axum::body::to_bytes(body, 1_000_000).await?;
89101
let r = HttpRequest {
90102
method,
91103
uri,

crates/proxy/src/main.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::Result;
2+
use axum_server::tls_rustls::RustlsConfig;
23
use clap::Parser;
34
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
45

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

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

49+
let tls_config = if config.tls.enable {
50+
let cert_path = config.tls.cert_path.unwrap();
51+
let key_path = config.tls.key_path.unwrap();
52+
Some(RustlsConfig::from_pem_file(cert_path, key_path).await?)
53+
} else {
54+
None
55+
};
56+
4857
tracing::info!("ingest listening on {}", ingest_listener);
49-
axum::Server::bind(&ingest_listener)
50-
.serve(ingest.into_make_service())
51-
.await?;
58+
if let Some(tls_config) = tls_config {
59+
tracing::info!("tls configured for {}", ingest_listener);
60+
axum_server::bind_rustls(ingest_listener, tls_config)
61+
.serve(ingest.into_make_service())
62+
.await?;
63+
} else {
64+
axum_server::bind(ingest_listener)
65+
.serve(ingest.into_make_service())
66+
.await?;
67+
}
5268

5369
Ok(())
5470
}

crates/proxy/src/origin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use axum::http::Uri;
1+
use hyper::Uri;
22

33
pub struct Origin {
44
pub uri: Uri,

crates/proxy/src/proxy.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use anyhow::{anyhow, Result};
2-
use axum::http::Request;
3-
use axum::http::Uri;
42
use hyper::client::HttpConnector;
5-
use hyper::Body;
6-
use hyper::Response;
3+
use hyper::{Body, Request, Response, Uri};
74
use sqlx::SqlitePool;
85
use tokio::time::{timeout, Duration};
96

crates/proxy/tests/integration/common.rs

+1
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ pub fn config() -> Config {
2727
database_url: "sqlite::memory:".to_string(),
2828
management_listener: "0.0.0.0:3443".to_string(),
2929
ingest_listener: "0.0.0.0:3000".to_string(),
30+
tls: Default::default(),
3031
}
3132
}

crates/proxy/tests/integration/ingest.rs

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::common;
22

3-
use std::net::{SocketAddr, TcpListener};
43
use std::sync::Arc;
54

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

5151
tokio::spawn(async move {
52-
axum::Server::from_tcp(listener)
53-
.unwrap()
54-
.serve(client_app.into_make_service())
55-
.await
56-
.unwrap();
52+
axum::serve(listener, client_app).await.unwrap();
5753
});
5854

5955
let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
@@ -74,7 +70,7 @@ async fn ingest_save_and_proxy() {
7470
.method("POST")
7571
.uri("/origins")
7672
.header("Content-Type", "application/json")
77-
.body(body.into())
73+
.body(body)
7874
.unwrap(),
7975
)
8076
.await
@@ -117,7 +113,9 @@ async fn ingest_save_and_proxy() {
117113

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

120-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
116+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
117+
.await
118+
.unwrap();
121119

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

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

140-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
138+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
139+
.await
140+
.unwrap();
141141

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

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

161161
tokio::spawn(async move {
162-
axum::Server::from_tcp(listener)
163-
.unwrap()
164-
.serve(client_app.into_make_service())
165-
.await
166-
.unwrap();
162+
axum::serve(listener, client_app).await.unwrap();
167163
});
168164

169165
let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
@@ -190,7 +186,7 @@ async fn ingest_proxy_failure() {
190186
.method("POST")
191187
.uri("/origins")
192188
.header("Content-Type", "application/json")
193-
.body(body.into())
189+
.body(body)
194190
.unwrap(),
195191
)
196192
.await
@@ -231,7 +227,9 @@ async fn ingest_proxy_failure() {
231227

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

234-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
230+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
231+
.await
232+
.unwrap();
235233

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

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

254-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
252+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
253+
.await
254+
.unwrap();
255255

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

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

272272
tokio::spawn(async move {
273-
axum::Server::from_tcp(listener)
274-
.unwrap()
275-
.serve(client_app.into_make_service())
276-
.await
277-
.unwrap();
273+
axum::serve(listener, client_app).await.unwrap();
278274
});
279275

280276
let (ingest, mgmt, _) = app(&common::config()).await.unwrap();
@@ -295,7 +291,7 @@ async fn ingest_proxy_timeout() {
295291
.method("POST")
296292
.uri("/origins")
297293
.header("Content-Type", "application/json")
298-
.body(body.into())
294+
.body(body)
299295
.unwrap(),
300296
)
301297
.await
@@ -336,7 +332,9 @@ async fn ingest_proxy_timeout() {
336332

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

339-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
335+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
336+
.await
337+
.unwrap();
340338

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

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

359-
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
357+
let body = axum::body::to_bytes(response.into_body(), 1_000_000)
358+
.await
359+
.unwrap();
360360

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

0 commit comments

Comments
 (0)