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-shm
*.db-wal *.db-wal
# Tls
cert.key
cert.pem
# IDE configs # IDE configs
.idea .idea
.vscode .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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "arc-swap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.3.14" version = "0.3.14"
@ -155,6 +161,26 @@ dependencies = [
"tower-service", "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]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.66" version = "0.3.66"
@ -627,6 +653,7 @@ version = "0.0.0-dev"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
"axum-server",
"backtrace", "backtrace",
"byte-unit", "byte-unit",
"crypto-utils", "crypto-utils",

View File

@ -37,6 +37,7 @@ toml = "0.5"
# HTTP server # HTTP server
axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] } 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"] } tower-http = { version = "0.3", features = ["full"] }
hyper = { version = "0.14", features = ["full"] } hyper = { version = "0.14", features = ["full"] }
byte-unit = "4.0.14" 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> <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> </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]
# HTTP host
host = "0.0.0.0" host = "0.0.0.0"
# HTTP port httpPort = 8080 # http server port (recommended 80)
port = 8080 httpsPort = 8443 # https server port (recommended 443)
# Cors domains cors = [ "localhost:8000" ] # CORS domains
cors = [ tlsCert = "./cert.pem" # TLS certificate file
"127.0.0.1:8000", tlsKey = "./cert.key" # TLS key file
"localhost:8000",
]
[jwt] [jwt]
# JWT Secret string (used to sign tokens) secret = "secret key used to sign tokens" # jsonwebtoken secret string used to sign tokens
secret = "secret key used to sign tokens" expires = 24 # token expiration time in hours (default one day)
# Token expiration time in hours
expires = 24 # one day
[storage] [storage]
# Directory where user files will be stored path = "/home/homedisk" # path to directory where user files will be stored
path = "/home/homedisk"

View File

@ -2,18 +2,32 @@ mod api;
pub mod error; pub mod error;
pub mod utils; pub mod utils;
use std::path::PathBuf;
use anyhow::anyhow; use anyhow::anyhow;
use axum::{http::HeaderValue, routing::get, Extension, Router, Server}; use axum::{
use tower_http::cors::{AllowOrigin, CorsLayer}; extract::Host,
use tracing::info; 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}; use crate::{config::Config, database::Database};
pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> { pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
info!( let host = format!("{}:{}", config.http.host, config.http.https_port);
"🚀 Server has launched on http://{}:{}",
config.http.host, config.http.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 // change the type from Vec<String> to Vec<HeaderValue> so that the http server can correctly detect CORS hosts
let origins = config 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")) .map(|e| e.parse().expect("Failed to parse CORS hosts"))
.collect::<Vec<HeaderValue>>(); .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() let app = Router::new()
.nest("/api", api::app()) .nest("/api", api::app())
@ -32,9 +51,48 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
.layer(Extension(config)) .layer(Extension(config))
.layer(Extension(db)); .layer(Extension(db));
Server::bind(&host.parse()?) axum_server::bind_rustls(host.parse()?, tls_config)
.serve(app.into_make_service()) .serve(app.into_make_service())
.await?; .await?;
Err(anyhow!("Server unexpected stopped!")) 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}; use serde::{Deserialize, Serialize};
/// Settings configuration.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {
/// HTTP Settings.
pub http: ConfigHTTP, pub http: ConfigHTTP,
/// Json Web Token Settings.
pub jwt: ConfigJWT, pub jwt: ConfigJWT,
/// Storage Settings.
pub storage: ConfigStorage, pub storage: ConfigStorage,
} }
/// HTTP Settings.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigHTTP { pub struct ConfigHTTP {
/// HTTP Host.
pub host: String, pub host: String,
/// Port HTTP Port. pub http_port: u16,
pub port: u16, pub https_port: u16,
/// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]).
pub cors: Vec<String>, pub cors: Vec<String>,
pub tls_cert: String,
pub tls_key: String,
} }
/// Json Web Token Settings.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigJWT { pub struct ConfigJWT {
/// JWT Secret string (used to sign tokens).
pub secret: String, pub secret: String,
/// Token expiration time in hours.
pub expires: i64, pub expires: i64,
} }
/// Storage Settings.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigStorage { pub struct ConfigStorage {
/// Directory where user files will be stored.
pub path: String, pub path: String,
} }