Moved config file out of the publicly served directory. Fixes #8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:05:12 +02:00
co-authored by Claude Opus 5.5
parent ed72dc20f9
commit cc21056ed3
5 changed files with 82 additions and 3 deletions
+47
View File
@@ -3,6 +3,8 @@ use std::fs::read_to_string;
#[cfg(feature = "ssr")]
use std::net::SocketAddr;
#[cfg(feature = "ssr")]
use std::path::Path;
#[cfg(feature = "ssr")]
use std::str::FromStr;
#[cfg(feature = "ssr")]
use serde::Deserialize;
@@ -149,6 +151,16 @@ impl Configuration {
}
}
/// The config file holds passwords and the session key, so it must never lie in the directory
/// that is served as static files. Returns true when `config_path` is inside `site_root`.
#[cfg(feature = "ssr")]
pub fn is_publicly_served(config_path: &Path, site_root: &Path) -> bool {
match (config_path.canonicalize(), site_root.canonicalize()) {
(Ok(config), Ok(root)) => config.starts_with(root),
_ => false
}
}
#[cfg(feature = "ssr")]
pub fn load_config(path: &str) -> Configuration {
toml::from_str(read_to_string(path).expect("Can not open config file").as_str()).expect("Can not parse config file")
@@ -177,6 +189,41 @@ mod tests {
assert_eq!(config.mailing().server(), &None);
}
/// Creates `<temp>/<name>/site/data` and returns the `<temp>/<name>` directory
fn site_dir(name: &str) -> std::path::PathBuf {
let base = std::env::temp_dir().join(format!("rezervator-test-{}-{}", name, std::process::id()));
std::fs::create_dir_all(base.join("site/data")).unwrap();
base
}
#[test]
fn config_inside_site_root_is_publicly_served() {
let base = site_dir("inside");
let config = base.join("site/data/config.toml");
std::fs::write(&config, "").unwrap();
assert!(is_publicly_served(&config, &base.join("site")));
// Relative segments must not hide it
assert!(is_publicly_served(&base.join("site/data/../data/config.toml"), &base.join("site")));
std::fs::remove_dir_all(base).unwrap();
}
#[test]
fn config_outside_site_root_is_not_publicly_served() {
let base = site_dir("outside");
let config = base.join("config.toml");
std::fs::write(&config, "").unwrap();
assert!(!is_publicly_served(&config, &base.join("site")));
// A sibling directory with the same prefix is not inside the site root
std::fs::create_dir_all(base.join("site-config")).unwrap();
std::fs::write(base.join("site-config/config.toml"), "").unwrap();
assert!(!is_publicly_served(&base.join("site-config/config.toml"), &base.join("site")));
std::fs::remove_dir_all(base).unwrap();
}
#[test]
fn sample_session_key_is_long_enough() {
// actix_web::cookie::Key::from panics on keys shorter than 64 bytes
+12 -1
View File
@@ -27,7 +27,8 @@ async fn main() -> std::io::Result<()> {
use sqlx::migrate;
use sqlx::postgres::PgPoolOptions;
use rezervator::backend::auth_middleware::Authentication;
use rezervator::config::load_config;
use rezervator::config::{is_publicly_served, load_config};
use std::path::Path;
use env_logger::Env;
use std::env::args;
use getopts::Options;
@@ -54,6 +55,16 @@ async fn main() -> std::io::Result<()> {
info!("Starting server");
let conf = get_configuration(None).unwrap();
// The site root is served as static files, the config with passwords must not be there
if is_publicly_served(Path::new(cfg_path.trim()), Path::new(&*conf.leptos_options.site_root)) {
let msg = format!(
"Config file {} is inside the publicly served directory {}. Move it elsewhere and change the passwords and the session key in it.",
cfg_path.trim(), conf.leptos_options.site_root);
error!("{}", msg);
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, msg));
}
let key = Key::from(srv_conf.session().key());
let pool = PgPoolOptions::new()