feat(server): add tls/ssl support

Added support for https server.
This commit is contained in:
MedzikUser 2022-09-19 21:13:59 +02:00
parent f440f30ea8
commit 0794bea31d
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
7 changed files with 131 additions and 37 deletions

4
.gitignore vendored
View File

@ -10,6 +10,10 @@
*.db-shm
*.db-wal
# Tls
cert.key
cert.pem
# IDE configs
.idea
.vscode

27
Cargo.lock generated
View File

@ -67,6 +67,12 @@ version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "arc-swap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
[[package]]
name = "async-compression"
version = "0.3.14"
@ -155,6 +161,26 @@ dependencies = [
"tower-service",
]
[[package]]
name = "axum-server"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba6170b61f7b086609dabcae68d2e07352539c6ef04a7c82980bdfa01a159d"
dependencies = [
"arc-swap",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.66"
@ -627,6 +653,7 @@ version = "0.0.0-dev"
dependencies = [
"anyhow",
"axum",
"axum-server",
"backtrace",
"byte-unit",
"crypto-utils",

View File

@ -37,6 +37,7 @@ toml = "0.5"
# HTTP server
axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] }
axum-server = { version = "0.4", features = ["tls-rustls"] }
tower-http = { version = "0.3", features = ["full"] }
hyper = { version = "0.14", features = ["full"] }
byte-unit = "4.0.14"

View File

@ -13,3 +13,22 @@
 
<a href="https://documenter.getpostman.com/view/23280189/VVk9dwRk"><img src="https://img.shields.io/badge/API_Docs-887BB0?style=flat-square&labelColor=555555&logo=postman"></a>
</p>
## Documentation
### 👨‍💻 Compile server
```bash
cargo build --release
```
Now you can run server using command `./target/release/homedisk`.
### 🔒 Generate development TLS certificate
```bash
# Generate private key
openssl genrsa -out cert.key 204
# Generate certificate
openssl req -new -x509 -key cert.key -out cert.pem -days 365
```

View File

@ -1,20 +1,14 @@
[http]
# HTTP host
host = "0.0.0.0"
# HTTP port
port = 8080
# Cors domains
cors = [
"127.0.0.1:8000",
"localhost:8000",
]
httpPort = 8080 # http server port (recommended 80)
httpsPort = 8443 # https server port (recommended 443)
cors = [ "localhost:8000" ] # CORS domains
tlsCert = "./cert.pem" # TLS certificate file
tlsKey = "./cert.key" # TLS key file
[jwt]
# JWT Secret string (used to sign tokens)
secret = "secret key used to sign tokens"
# Token expiration time in hours
expires = 24 # one day
secret = "secret key used to sign tokens" # jsonwebtoken secret string used to sign tokens
expires = 24 # token expiration time in hours (default one day)
[storage]
# Directory where user files will be stored
path = "/home/homedisk"
path = "/home/homedisk" # path to directory where user files will be stored

View File

@ -2,18 +2,32 @@ mod api;
pub mod error;
pub mod utils;
use std::path::PathBuf;
use anyhow::anyhow;
use axum::{http::HeaderValue, routing::get, Extension, Router, Server};
use tower_http::cors::{AllowOrigin, CorsLayer};
use tracing::info;
use axum::{
extract::Host,
handler::HandlerWithoutStateExt,
http::{HeaderValue, StatusCode, Uri},
response::Redirect,
routing::get,
Extension, Router,
};
use axum_server::tls_rustls::RustlsConfig;
use tower_http::{
cors::{AllowOrigin, CorsLayer},
BoxError,
};
use tracing::{debug, info};
use crate::{config::Config, database::Database};
pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
info!(
"🚀 Server has launched on http://{}:{}",
config.http.host, config.http.port
);
let host = format!("{}:{}", config.http.host, config.http.https_port);
tokio::spawn(redirect_http_to_https(config.clone()));
info!("🚀 Server has launched on https://{host}");
// change the type from Vec<String> to Vec<HeaderValue> so that the http server can correctly detect CORS hosts
let origins = config
@ -23,7 +37,12 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
.map(|e| e.parse().expect("Failed to parse CORS hosts"))
.collect::<Vec<HeaderValue>>();
let host = format!("{}:{}", config.http.host, config.http.port);
let tls_config = RustlsConfig::from_pem_file(
PathBuf::from("").join("").join(&config.http.tls_cert),
PathBuf::from("").join("").join(&config.http.tls_key),
)
.await
.unwrap();
let app = Router::new()
.nest("/api", api::app())
@ -32,9 +51,48 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
.layer(Extension(config))
.layer(Extension(db));
Server::bind(&host.parse()?)
axum_server::bind_rustls(host.parse()?, tls_config)
.serve(app.into_make_service())
.await?;
Err(anyhow!("Server unexpected stopped!"))
}
async fn redirect_http_to_https(config: Config) {
fn make_https(host: String, uri: Uri, config: Config) -> Result<Uri, BoxError> {
let mut parts = uri.into_parts();
parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
if parts.path_and_query.is_none() {
parts.path_and_query = Some("/".parse().unwrap());
}
let https_host = host.replace(
&config.http.http_port.to_string(),
&config.http.https_port.to_string(),
);
parts.authority = Some(https_host.parse()?);
Ok(Uri::from_parts(parts)?)
}
let host = format!("{}:{}", config.http.host, config.http.http_port);
let redirect = move |Host(host): Host, uri: Uri| async move {
match make_https(host, uri, config) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(error) => {
tracing::warn!(%error, "Failed to convert URI to HTTPS");
Err(StatusCode::BAD_REQUEST)
},
}
};
debug!("🚀 Http redirect listening on http://{host}");
axum::Server::bind(&host.parse().unwrap())
.serve(redirect.into_make_service())
.await
.unwrap();
}

View File

@ -4,41 +4,32 @@ use std::fs;
use serde::{Deserialize, Serialize};
/// Settings configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// HTTP Settings.
pub http: ConfigHTTP,
/// Json Web Token Settings.
pub jwt: ConfigJWT,
/// Storage Settings.
pub storage: ConfigStorage,
}
/// HTTP Settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigHTTP {
/// HTTP Host.
pub host: String,
/// Port HTTP Port.
pub port: u16,
/// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]).
pub http_port: u16,
pub https_port: u16,
pub cors: Vec<String>,
pub tls_cert: String,
pub tls_key: String,
}
/// Json Web Token Settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigJWT {
/// JWT Secret string (used to sign tokens).
pub secret: String,
/// Token expiration time in hours.
pub expires: i64,
}
/// Storage Settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigStorage {
/// Directory where user files will be stored.
pub path: String,
}