Skip to content

Commit

Permalink
feats: sticky folder up tile in explorer, tls with generated self sig…
Browse files Browse the repository at this point in the history
…ned certificates
  • Loading branch information
Nicolas Pernoud committed Nov 25, 2024
1 parent 826ce95 commit 37152f2
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 308 deletions.
19 changes: 12 additions & 7 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ argon2 = { features = ["alloc", "password-hash"], default-features = false, vers
async_zip = { features = ["deflate", "tokio"], default-features = false, version = "0.0.17" }
async-stream = "0.3.6"
async-walkdir = "2.0.0"
axum = { version = "0.7.7", features = ["http2", "json", "query", "tokio"], default-features = false }
axum-extra = { version = "0.9.4", features = ["cookie-private", "typed-header"], default-features = false }
axum = { version = "0.7.9", features = ["http2", "json", "query", "tokio"], default-features = false }
axum-extra = { version = "0.9.6", features = ["cookie-private", "typed-header"], default-features = false }
axum-server = "0.7.1"
base64ct = { version = "1.6.0", features = ["alloc"] }
chacha20poly1305 = { version = "0.10.1", features = ["stream"], default-features = false }
Expand All @@ -29,7 +29,7 @@ futures-util = { default-features = false, version = "0.3.31" }
headers = "0.4.0"
http = "1.1.0"
http-body-util = "0.1.2"
hyper = { version = "1.5.0", default-features = false }
hyper = { version = "1.5.1", default-features = false }
hyper-util = { version = "0.1.10", features = ["client-legacy", "http1", "tokio"], default-features = false }
hyper-rustls = { version = "0.27.3", features = ["http1", "http2", "ring", "tls12", "webpki-tokio"], default-features = false }
hyper-hickory = { version = "0.7.0", default-features = false, features = ["system-config"] }
Expand All @@ -39,13 +39,14 @@ mime_guess = { default-features = false, version = "2.0.5" }
# TEMPORARY
oauth2 = { version = "5.0.0-rc.1", default-features = false }
percent-encoding = { default-features = false, version = "2.3.1" }
quick-xml = "0.37.0"
quick-xml = "0.37.1"
rand = { default-features = false, version = "0.8.5" }
rustls = { default-features = false, version = "0.23.16", features = ["ring"] }
rcgen = { version = "0.13.1", default-features = false, optional = true }
rustls = { default-features = false, version = "0.23.18", features = ["ring"] }
rustls-pki-types = { version = "1.10.0" }
rustls-acme = { version = "0.12.1", features = ["axum", "ring"], default-features = false }
serde = { version = "1.0.215", default-features = false }
serde_json = { default-features = false, version = "1.0.132" }
serde_json = { default-features = false, version = "1.0.133" }
serde_yml = "0.0.12"
sha2 = { default-features = false, version = "0.10.8" }
sysinfo = { default-features = false, version = "0.32.0", features = ["disk", "system"] }
Expand All @@ -54,7 +55,7 @@ tokio = { version = "1.41.1", features = ["full"], default-features = false }
tokio-stream = { version = "0.1.16", default-features = false }
tokio-util = { version = "0.7.12", default-features = false }
tower = { default-features = false, version = "0.5.1", features = ["util"] }
tower-http = { version = "0.6.1", features = ["fs"], default-features = false }
tower-http = { version = "0.6.2", features = ["fs"], default-features = false }
tower-service = "0.3.3"
tracing = { default-features = false, version = "0.1.40" }
tracing-appender = "0.2.3"
Expand All @@ -63,6 +64,10 @@ trim-in-place = "0.1.7"
urlencoding = "2.1.3"
uuid = { version = "1.11.0", features = ["fast-rng", "v4"], default-features = false }

[features]
default = ["self_signed"]
self_signed = ["dep:rcgen"]

[dev-dependencies]
async-tungstenite = { version = "0.28.0", features = ["tokio-runtime"] }
reqwest = { version = "0.12.9", default-features = false, features = ["cookies", "json", "rustls-tls", "stream"] }
Expand Down
2 changes: 1 addition & 1 deletion backend/atrium.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ hostname: atrium.127.0.0.1.nip.io # required : fully qualified domain name of th
debug_mode: true # optional, defaults to false : prints a lot of debug logs ; disable in production as it has a big performance impact
single_proxy: false # optional, default to false : in single proxy mode, atrium will route only to the first app available, it is meant to secure a single proxied application with Open ID Connect
http_port: 8080 # required, defaults to 8080 : http port to listen to if tls mode is not Auto
tls_mode: No # required, defaults to No : use No for development/test http mode, Auto to generate Let's Encrypt certificates automatically (most common production usage) or ̀BehindProxy to use atrium behind a TLS offloading proxy
tls_mode: No # required, defaults to No : use No for development/test http mode, Auto to generate Let's Encrypt certificates automatically (most common production usage) or ̀BehindProxy to use atrium behind a TLS offloading proxy or SelfSigned to generate self signed certificates (using http_port for https)
letsencrypt_email: [email protected] # required if `tls_mode: Auto` is used : email for receiving Let's Encrypt information
#cookie_key : # required, will be generated on first start : cookies and token signing key !!! SENSITIVE INFORMATION : TO BE KEPT HIDDEN !!!
log_to_file: false # optional, defaults to false : log to a file in addition to std out
Expand Down
6 changes: 5 additions & 1 deletion backend/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ pub enum TlsMode {
No,
BehindProxy,
Auto,
#[cfg(feature = "self_signed")]
SelfSigned,
}

impl TlsMode {
pub fn is_secure(&self) -> bool {
match self {
TlsMode::No => false,
TlsMode::BehindProxy | TlsMode::Auto => true,
#[cfg(feature = "self_signed")]
TlsMode::SelfSigned => true,
}
}
}
Expand Down Expand Up @@ -331,7 +335,7 @@ impl HostType {

pub fn secured(&self) -> bool {
match self {
HostType::ReverseApp(app)| HostType::SkipVerifyReverseApp(app)=> app.inner.secured,
HostType::ReverseApp(app) | HostType::SkipVerifyReverseApp(app) => app.inner.secured,
HostType::Dav(dav) => dav.secured,
HostType::StaticApp(app) => app.secured,
}
Expand Down
100 changes: 57 additions & 43 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ use tracing::{error, info};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{fmt, fmt::time::OffsetTime, prelude::*};

#[cfg(feature = "self_signed")]
pub mod self_signed;

pub const CONFIG_FILE: &str = "atrium.yaml";

fn main() -> Result<()> {
// println!("MiMalloc version: {}", mimalloc::MiMalloc.version()); // mimalloc = { version = "0.1", features = ["extended"] } in Cargo.toml to use this
// We need to work out the local time offset before entering multi-threaded context
let cfg: Config = if let Ok(file) = File::open(CONFIG_FILE) { serde_yml::from_reader(file).expect("failed to parse configuration file") } else {
let cfg: Config = if let Ok(file) = File::open(CONFIG_FILE) {
serde_yml::from_reader(file).expect("failed to parse configuration file")
} else {
println!("Configuration file not found, trying to create default configuration file.");
File::create(CONFIG_FILE).expect("could not create default configuration file");
Config::default()
Expand Down Expand Up @@ -97,52 +102,61 @@ async fn run() -> Result<()> {
}
});

if config.0.tls_mode == TlsMode::Auto {
let config = atrium::configuration::load_config(CONFIG_FILE).await?;
let domains: Vec<String> = config.0.domains();
info!(
"Getting let's encrypt certificates for FQDNs : {:?}",
domains
);
let mut state = AcmeConfig::new(domains)
.contact_push(format!("mailto:{}", config.0.letsencrypt_email))
.directory_lets_encrypt(true)
.cache(DirCache::new("./letsencrypt_cache"))
.state();

let mut rustls_config = ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(state.resolver());
rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let acceptor = state.axum_acceptor(Arc::new(rustls_config));

tokio::spawn(async move {
loop {
match state.next().await.unwrap() {
Ok(ok) => info!("ACME (let's encrypt) event: {:?}", ok),
Err(err) => error!("ACME (let's encrypt) error: {:?}", err),
match config.0.tls_mode {
TlsMode::Auto => {
let config = atrium::configuration::load_config(CONFIG_FILE).await?;
let domains: Vec<String> = config.0.domains();
info!(
"Getting let's encrypt certificates for FQDNs : {:?}",
domains
);
let mut state = AcmeConfig::new(domains)
.contact_push(format!("mailto:{}", config.0.letsencrypt_email))
.directory_lets_encrypt(true)
.cache(DirCache::new("./letsencrypt_cache"))
.state();

let mut rustls_config = ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(state.resolver());
rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let acceptor = state.axum_acceptor(Arc::new(rustls_config));

tokio::spawn(async move {
loop {
match state.next().await.expect("could not start ACME loop") {
Ok(ok) => info!("ACME (let's encrypt) event: {:?}", ok),
Err(err) => error!("ACME (let's encrypt) error: {:?}", err),
}
}
}
});
});

// Spawn a server to redirect HTTP to HTTPS
tokio::spawn(redirect_http_to_https(handle.clone()));
// Spawn a server to redirect HTTP to HTTPS
tokio::spawn(redirect_http_to_https(handle.clone()));

// Main server
let addr = format!("[::]:{}", 443)
.parse::<std::net::SocketAddr>()
.unwrap();
// Main server
let addr = format!("[::]:{}", 443).parse::<std::net::SocketAddr>()?;

axum_server::bind(addr)
.acceptor(acceptor)
.handle(handle)
.serve(app)
axum_server::bind(addr)
.acceptor(acceptor)
.handle(handle)
.serve(app)
.await?;
}
#[cfg(feature = "self_signed")]
TlsMode::SelfSigned => {
self_signed::serve_with_self_signed_cert(
ip_bind,
&server.port,
handle,
app,
)
.await?;
} else {
let addr = format!("{ip_bind}:{}", server.port)
.parse::<std::net::SocketAddr>()
.unwrap();
axum_server::bind(addr).handle(handle).serve(app).await?;
}
_ => {
let addr = format!("{ip_bind}:{}", server.port).parse::<std::net::SocketAddr>()?;
axum_server::bind(addr).handle(handle).serve(app).await?;
}
}
}

Expand Down Expand Up @@ -229,7 +243,7 @@ async fn redirect_http_to_https(handle: Handle) -> tokio::io::Result<()> {
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());
parts.path_and_query = Some("/".parse()?);
}
parts.authority = Some(host.parse()?);
Ok(Uri::from_parts(parts)?)
Expand Down
65 changes: 65 additions & 0 deletions backend/src/self_signed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::CONFIG_FILE;
use anyhow::Result;
use axum::{extract::connect_info::IntoMakeServiceWithConnectInfo, routing::MethodRouter};
use axum_server::{tls_rustls::RustlsConfig, Handle};
use std::{net::SocketAddr, path::Path};
use tokio::fs;
use tracing::info;

const CERT_PATH: &str = "cert.pem";
const KEY_PATH: &str = "key.pem";

pub async fn serve_with_self_signed_cert(
ip: &str,
port: &u16,
handle: Handle,
app: IntoMakeServiceWithConnectInfo<MethodRouter, SocketAddr>,
) -> anyhow::Result<()> {
// Certificates
let (cert, key) = load_or_generate_cert().await?;
let rustls_config = RustlsConfig::from_pem(cert, key).await?;

// Main server
let addr = format!("{ip}:{}", port).parse::<std::net::SocketAddr>()?;

// Start the server with TLS
Ok(axum_server::bind_rustls(addr, rustls_config)
.handle(handle)
.serve(app)
.await?)
}

/// Load or generate a self-signed certificate and private key
async fn load_or_generate_cert() -> Result<(Vec<u8>, Vec<u8>)> {
if Path::new(CERT_PATH).exists() && Path::new(KEY_PATH).exists() {
info!("Loading existing certificate and key from disk...");
let cert = fs::read(CERT_PATH).await?;
let key = fs::read(KEY_PATH).await?;
Ok((cert, key))
} else {
info!("Generating new self-signed certificate and key...");
let (cert, key) = generate_self_signed_cert().await?;
persist_cert_and_key(&cert, &key).await?;
Ok((cert, key))
}
}

/// Generate a self-signed certificate and private key
async fn generate_self_signed_cert() -> Result<(Vec<u8>, Vec<u8>)> {
let config = atrium::configuration::load_config(CONFIG_FILE).await?;
let domains: Vec<String> = config.0.domains();
// Generate a self-signed certificate using rcgen
let cert = rcgen::generate_simple_self_signed(domains)?;
Ok((
cert.cert.pem().into_bytes(),
cert.key_pair.serialize_pem().into_bytes(),
))
}

/// Persist the certificate and key to files
async fn persist_cert_and_key(cert: &[u8], key: &[u8]) -> Result<()> {
info!("Persisting certificate and key to disk...");
fs::write(CERT_PATH, cert).await?;
fs::write(KEY_PATH, key).await?;
Ok(())
}
Loading

0 comments on commit 37152f2

Please sign in to comment.