Skip to content
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

[#195] tls 기능 구현 #196

Merged
merged 11 commits into from
Dec 21, 2024
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ test.json

*.zip

*.http
*.http

*.pem
6 changes: 5 additions & 1 deletion rupring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ chrono = "0.4.31"
log = "0.4.20"
anyhow = "1.0.86"
flate2 = "1.0.34"
tokio-rustls = { version = "0.26.1", optional = true }
rustls-pemfile = { version = "2.2.0", optional = true }
rustls = { version = "0.23.20", optional = true }

[dependencies.uuid]
version = "1.6.1"
Expand All @@ -42,4 +45,5 @@ signal-hook = "0.3.17"
default = []

full = ["aws-lambda"]
aws-lambda = []
aws-lambda = []
tls = ["tokio-rustls", "rustls-pemfile", "rustls"]
17 changes: 17 additions & 0 deletions rupring/src/application_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
| server.thread.limit | The thread limit to use. | None(max) |
| server.request-timeout | The request timeout. (300 = 300 millisecond, 3s = 3 second, 2m = 2 minute) | No Timeout |
| server.http1.keep-alive | Whether to keep-alive for HTTP/1. (false=disable, true=enable) | false |
| server.http2.enabled | Whether to enable HTTP/2. | false |
| server.ssl.key | The SSL key file. (SSL is enabled by feature="tls") | None |
| server.ssl.cert | The SSL cert file. (SSL is enabled by feature="tls") | None |
| banner.enabled | Whether to enable the banner. | true |
| banner.location | The location of the banner file. | None |
| banner.charset | The charset of the banner file. (UTF-8, UTF-16) | UTF-8 |
Expand Down Expand Up @@ -147,6 +150,12 @@ impl From<String> for ShutdownType {
}
}

#[derive(Debug, PartialEq, Clone, Default)]
pub struct SSL {
pub key: String,
pub cert: String,
}

#[derive(Debug, PartialEq, Clone)]
pub struct Http1 {
pub keep_alive: bool,
Expand Down Expand Up @@ -181,6 +190,7 @@ pub struct Server {
pub request_timeout: Option<Duration>,
pub http1: Http1,
pub http2: Http2,
pub ssl: SSL,
}

impl Default for Server {
Expand All @@ -195,6 +205,7 @@ impl Default for Server {
request_timeout: None,
http1: Http1::default(),
http2: Http2::default(),
ssl: Default::default(),
}
}
}
Expand Down Expand Up @@ -331,6 +342,12 @@ impl ApplicationProperties {
server.http2.enabled = value;
}
}
"server.ssl.key" => {
server.ssl.key = value.to_string();
}
"server.ssl.cert" => {
server.ssl.cert = value.to_string();
}
"environment" => {
environment = value.to_string();
}
Expand Down
3 changes: 3 additions & 0 deletions rupring/src/core/bootings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
#[cfg(feature = "aws-lambda")]
pub mod aws_lambda;

#[cfg(feature = "tls")]
pub mod tls;
58 changes: 58 additions & 0 deletions rupring/src/core/bootings/tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::{fs, io, sync::Arc};

use rustls::ServerConfig;
use tokio_rustls::{
rustls::pki_types::{CertificateDer, PrivateKeyDer},
TlsAcceptor,
};

use crate::application_properties::ApplicationProperties;

fn error(err: String) -> io::Error {
io::Error::new(io::ErrorKind::Other, err)
}

fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
// Open certificate file.
let certfile = fs::File::open(filename)
.map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
let mut reader = io::BufReader::new(certfile);

// Load and return certificate.
rustls_pemfile::certs(&mut reader).collect()
}

// Load private key from file.
fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'static>> {
// Open keyfile.
let keyfile = fs::File::open(filename)
.map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
let mut reader = io::BufReader::new(keyfile);

// Load and return a single private key.
rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap())
}

pub fn new_tls_acceptor(
application_properties: &ApplicationProperties,
) -> anyhow::Result<TlsAcceptor> {
let certs = load_certs(&application_properties.server.ssl.cert)?;
// Load private key.
let key = load_private_key(&application_properties.server.ssl.key)?;

let mut server_config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| error(e.to_string()))?;

if application_properties.server.http2.enabled {
server_config.alpn_protocols =
vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
} else {
server_config.alpn_protocols = vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()];
}

let tls_acceptor = TlsAcceptor::from(Arc::new(server_config));

Ok(tls_acceptor)
}
37 changes: 31 additions & 6 deletions rupring/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ mod parse;

#[cfg(feature = "aws-lambda")]
use bootings::aws_lambda::LambdaRequestEvent;

#[cfg(feature = "tls")]
use bootings::tls;
use hyper_util::rt::TokioExecutor;
use tokio::time::error::Elapsed;
use tokio::time::Instant;
Expand Down Expand Up @@ -84,27 +87,30 @@ pub async fn run_server(
let keep_alive = application_properties.server.http1.keep_alive.to_owned();
let http2_enabled = application_properties.server.http2.enabled.to_owned();

#[cfg(feature = "tls")]
let tls_acceptor = {
print_system_log(Level::Info, "TLS Enabled");

tls::new_tls_acceptor(&application_properties)?
};

// 5. Main Server Loop
// Spawns a new async Task for each request.
loop {
let (mut stream, _) = listener.accept().await?;
let (mut tcp_stream, _) = listener.accept().await?;

if is_graceful_shutdown {
if !service_avaliable.load(std::sync::atomic::Ordering::Acquire) {
print_system_log(Level::Info, "Service is not available");

// reject new request
use tokio::io::AsyncWriteExt;
let _ = stream.shutdown();
let _ = tcp_stream.shutdown();

continue;
}
}

// Use an adapter to access something implementing `tokio::io` traits as if they implement
// `hyper::rt` IO traits.
let io = TokioIo::new(stream);

// copy for each request
let di_context = Arc::clone(&di_context);
let application_properties = Arc::clone(&application_properties);
Expand All @@ -113,6 +119,9 @@ pub async fn run_server(
// for Graceful Shutdown
let running_task_count = Arc::clone(&running_task_count);

#[cfg(feature = "tls")]
let tls_acceptor = tls_acceptor.clone();

// 6. create tokio task per HTTP request
tokio::task::spawn(async move {
let service = service_fn(move |request: Request<hyper::body::Incoming>| {
Expand Down Expand Up @@ -194,6 +203,22 @@ pub async fn run_server(
}
});

#[cfg(feature = "tls")]
let io = {
let tls_stream = match tls_acceptor.accept(tcp_stream).await {
Ok(tls_stream) => tls_stream,
Err(err) => {
eprintln!("failed to perform tls handshake: {err:#}");
return;
}
};

TokioIo::new(tls_stream)
};

#[cfg(not(feature = "tls"))]
let io = TokioIo::new(tcp_stream);

if http2_enabled {
let mut http_builder =
hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
Expand Down
2 changes: 1 addition & 1 deletion rupring_example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default-run = "example"

[dependencies]
mockall = "0.13.1"
rupring={ version = "0.12.2", path="../rupring", features=["full"] }
rupring={ version = "0.12.2", path="../rupring", features=["full", "tls"] }
serde = { version="1.0.193", features=["derive"] }

[[bin]]
Expand Down
6 changes: 4 additions & 2 deletions rupring_example/application.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
server.port=8080
server.port=3000
server.shutdown=graceful
server.timeout-per-shutdown-phase=30s
server.compression.enabled=true
Expand All @@ -7,4 +7,6 @@ server.compression.min-response-size=1024
server.compression.algorithm=gzip
server.request-timeout=3s
server.http1.keep-alive=true
server.http2.enabled=true
server.http2.enabled=true
server.ssl.cert=cert.pem
server.ssl.key=key.pem
Loading