From 9b80ade5211ac247ecd43419d592fa807b1281c2 Mon Sep 17 00:00:00 2001 From: ravenclaw900 <50060110+ravenclaw900@users.noreply.github.com> Date: Sun, 20 Mar 2022 15:25:54 -0700 Subject: [PATCH] feat(backend): allow using environment variables to set settings (#186) Adds an actual configuration library, uses `serde` and `jsonwebtoken` instead of `biscuit`. Increases binary size quite a bit, so now optimizes most packages for size instead of speed. --- src/backend/Cargo.lock | 208 +++++++++++++---------------- src/backend/Cargo.toml | 17 ++- src/backend/src/config.rs | 136 ++++--------------- src/backend/src/main.rs | 49 +++---- src/backend/src/shared.rs | 8 ++ src/backend/src/socket_handlers.rs | 36 ++--- 6 files changed, 174 insertions(+), 280 deletions(-) diff --git a/src/backend/Cargo.lock b/src/backend/Cargo.lock index 45207a4b..1757c23d 100644 --- a/src/backend/Cargo.lock +++ b/src/backend/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg", +] + [[package]] name = "atty" version = "0.2.14" @@ -60,21 +69,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "biscuit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dee631cea28b00e115fd355a1adedc860b155096941dc01259969eabd434a37" -dependencies = [ - "chrono", - "data-encoding", - "num", - "once_cell", - "ring", - "serde", - "serde_json", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -150,19 +144,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time", - "winapi", -] - [[package]] name = "colored" version = "2.0.0" @@ -222,12 +203,6 @@ dependencies = [ "libc", ] -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - [[package]] name = "derive_more" version = "0.99.17" @@ -243,20 +218,21 @@ dependencies = [ name = "dietpi-dashboard" version = "0.5.1" dependencies = [ - "biscuit", + "figment", "futures", "if-addrs", "include_dir", "infer", + "jsonwebtoken", "lazy_static", "log", "nanoserde", "psutil", "pty-process", + "serde", "sha2", "simple_logger", "tokio", - "toml", "walkdir", "warp", "zip", @@ -281,6 +257,20 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "flate2" version = "1.0.22" @@ -598,6 +588,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "itoa" version = "0.4.8" @@ -619,6 +615,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "012bb02250fdd38faa5feee63235f7a459974440b9b57593822414c31f92839e" +dependencies = [ + "base64", + "ring", + "serde", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -755,82 +763,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.13.1" @@ -853,6 +785,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "pear" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -912,6 +867,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "psutil" version = "3.2.2" @@ -1075,7 +1043,6 @@ version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ - "indexmap", "itoa 1.0.1", "ryu", "serde", @@ -1350,6 +1317,15 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +[[package]] +name = "uncased" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +dependencies = [ + "version_check", +] + [[package]] name = "unescape" version = "0.1.0" @@ -1579,6 +1555,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" + [[package]] name = "zip" version = "0.5.13" diff --git a/src/backend/Cargo.toml b/src/backend/Cargo.toml index 493353b5..48d531a8 100644 --- a/src/backend/Cargo.toml +++ b/src/backend/Cargo.toml @@ -18,11 +18,12 @@ pty-process = "0.2.0" psutil = "3.2.2" infer = { version = "0.7.0", default-features = false } sha2 = "0.10.2" -toml = "0.5.8" +figment = { version = "0.10", features = ["toml", "env"] } if-addrs = "0.7.0" zip = { version = "0.5.13", default-features = false, features = ["deflate", "time"] } walkdir = "2.3.2" -biscuit = "0.5.0" +jsonwebtoken = { version = "8.0.0", default-features = false } +serde = { version = "1.0.0", features = ["derive"] } [features] default = ["frontend"] @@ -32,3 +33,15 @@ frontend = ["include_dir", "warp/compression"] lto = "fat" panic = "abort" codegen-units = 1 + +[profile.release.package.nanoserde] +opt-level = 3 + +[profile.release.package.psutil] +opt-level = 3 + +[profile.release.package.zip] +opt-level = 3 + +[profile.release.package."*"] +opt-level = "s" diff --git a/src/backend/src/config.rs b/src/backend/src/config.rs index e971c264..d8f12c59 100644 --- a/src/backend/src/config.rs +++ b/src/backend/src/config.rs @@ -1,8 +1,12 @@ -use std::str::FromStr; -use toml::Value; +use figment::{ + providers::{Env, Format, Serialized, Toml}, + Figment, +}; +use serde::{Deserialize, Serialize}; +#[derive(Deserialize, Serialize)] pub struct Config { - pub log_level: log::LevelFilter, + pub log_level: String, pub port: u16, @@ -19,114 +23,32 @@ pub struct Config { pub nodes: Vec, } -pub fn config() -> Config { - let mut cfgpath = std::env::current_exe().unwrap(); - cfgpath.set_file_name("config.toml"); - let cfg = &match std::fs::read_to_string(cfgpath) { - Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => { - std::fs::write("config.toml", "").unwrap(); - String::new() - } - Ok(cfg) => cfg, - Err(e) => { - panic!("Config file could not be read: {}", e); - } - } - .parse::() - .expect("Invalid config file"); - - let log_level = log::LevelFilter::from_str( - cfg.get("log_level") - .unwrap_or(&Value::String("info".to_string())) - .as_str() - .unwrap(), - ) - .unwrap(); - - #[allow(clippy::cast_sign_loss)] - #[allow(clippy::cast_possible_truncation)] - let port: u16 = cfg - .get("port") - .unwrap_or(&Value::Integer(5252)) - .as_integer() - .unwrap() as u16; - - let tls = cfg - .get("tls") - .unwrap_or(&Value::Boolean(false)) - .as_bool() - .unwrap(); - let mut cert = String::new(); - let mut key = String::new(); - if tls { - cert = cfg - .get("cert") - .unwrap_or(&Value::String(String::new())) - .as_str() - .unwrap() - .to_string(); - key = cfg - .get("key") - .unwrap_or(&Value::String(String::new())) - .as_str() - .unwrap() - .to_string(); - } +impl Default for Config { + fn default() -> Config { + Config { + log_level: "info".to_string(), - let pass = cfg - .get("pass") - .unwrap_or(&Value::Boolean(false)) - .as_bool() - .unwrap(); + port: 5252, - let mut hash = String::new(); - let mut secret = String::new(); - if pass { - hash = cfg - .get("hash") - .unwrap_or(&Value::String(String::new())) - .as_str() - .unwrap() - .to_string(); - secret = cfg - .get("secret") - .unwrap_or(&Value::String(String::new())) - .as_str() - .unwrap() - .to_string(); - } - - #[allow(clippy::cast_sign_loss)] - let expiry = cfg - .get("expiry") - .unwrap_or(&Value::Integer(3600)) - .as_integer() - .unwrap() as u64; + tls: false, + cert: String::new(), + key: String::new(), - #[cfg(feature = "frontend")] - let mut nodes = Vec::new(); + pass: false, + hash: String::new(), + secret: String::new(), + expiry: 3600, - #[cfg(feature = "frontend")] - for i in cfg - .get("nodes") - .unwrap_or(&Value::Array(Vec::new())) - .as_array() - .unwrap() - { - nodes.push(i.as_str().unwrap().to_string()); + #[cfg(feature = "frontend")] + nodes: Vec::new(), + } } +} - Config { - log_level, - port, - tls, - cert, - key, - pass, - hash, - secret, - expiry, - #[cfg(feature = "frontend")] - nodes, - } +pub fn config() -> Config { + Figment::from(Serialized::defaults(Config::default())) + .merge(Toml::file("config.toml")) + .merge(Env::prefixed("DP_DASHBOARD_").ignore(&["hash", "secret"])) + .extract() + .expect("Error reading config") } diff --git a/src/backend/src/main.rs b/src/backend/src/main.rs index 555b5bfc..258b7516 100644 --- a/src/backend/src/main.rs +++ b/src/backend/src/main.rs @@ -2,6 +2,7 @@ use crate::shared::CONFIG; use sha2::{Digest, Sha512}; use simple_logger::SimpleLogger; +use std::str::FromStr; use warp::Filter; mod config; @@ -23,7 +24,9 @@ fn main() { const DIR: include_dir::Dir = include_dir::include_dir!("dist"); SimpleLogger::new() - .with_level(CONFIG.log_level) + .with_level( + log::LevelFilter::from_str(&CONFIG.log_level).expect("Invalid log level"), + ) .env() .init() .unwrap(); @@ -64,37 +67,21 @@ fn main() { hasher.update(pass); let shasum = format!("{:x}", hasher.finalize()); if shasum == CONFIG.hash { - let secret = biscuit::jws::Secret::bytes_from_str(&CONFIG.secret); - #[allow(clippy::cast_possible_wrap)] - let claims = biscuit::ClaimsSet { - registered: biscuit::RegisteredClaims { - issuer: Some("DietPi Dashboard".to_string()), - expiry: Some(biscuit::Timestamp::from( - (std::time::SystemTime::now() - + std::time::Duration::from_secs(CONFIG.expiry)) - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() as i64, - )), - audience: None, - id: None, - issued_at: None, - not_before: None, - subject: None, - }, - private: biscuit::Empty {}, + let timestamp = jsonwebtoken::get_current_timestamp(); + + let claims = crate::shared::JWTClaims { + iss: "DietPi Dashboard".to_string(), + iat: timestamp, + exp: timestamp + CONFIG.expiry, }; - let decoded = biscuit::JWT::new_decoded( - biscuit::jws::Header::from_registered_header( - biscuit::jws::RegisteredHeader::default(), - ), - claims, - ); - let token = decoded - .into_encoded(&secret) - .unwrap() - .unwrap_encoded() - .to_string(); + + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(CONFIG.secret.as_ref()), + ) + .expect("Error creating login token"); + return warp::reply::with_status(token, warp::http::StatusCode::OK); } return warp::reply::with_status( diff --git a/src/backend/src/shared.rs b/src/backend/src/shared.rs index db868a16..8d8124c0 100644 --- a/src/backend/src/shared.rs +++ b/src/backend/src/shared.rs @@ -1,4 +1,5 @@ use nanoserde::{DeJson, SerJson}; +use serde::{Deserialize, Serialize}; lazy_static::lazy_static! { pub static ref CONFIG: crate::config::Config = crate::config::config(); @@ -148,3 +149,10 @@ pub struct FileSize { pub struct FileUploadFinished { pub finished: bool, } + +#[derive(Serialize, Deserialize)] +pub struct JWTClaims { + pub iss: String, + pub exp: u64, + pub iat: u64, +} diff --git a/src/backend/src/socket_handlers.rs b/src/backend/src/socket_handlers.rs index e0ef24f6..661439d6 100644 --- a/src/backend/src/socket_handlers.rs +++ b/src/backend/src/socket_handlers.rs @@ -10,33 +10,15 @@ use warp::ws::Message; use crate::{page_handlers, shared, systemdata, CONFIG}; fn validate_token(token: &str) -> bool { - let secret = biscuit::jws::Secret::bytes_from_str(&CONFIG.secret); - let encoded: biscuit::jws::Compact, biscuit::Empty> = - biscuit::JWT::new_encoded(token); - let decoded = match encoded.into_decoded(&secret, biscuit::jwa::SignatureAlgorithm::HS256) { - Err(_) => return false, - Ok(unwrapped) => unwrapped, - }; - let payload = &decoded.payload().unwrap().registered; - if payload - .validate_claim_presence(biscuit::ClaimPresenceOptions { - issued_at: biscuit::Presence::Optional, - expiry: biscuit::Presence::Required, - not_before: biscuit::Presence::Optional, - issuer: biscuit::Presence::Required, - audience: biscuit::Presence::Optional, - subject: biscuit::Presence::Optional, - id: biscuit::Presence::Optional, - }) - .is_err() - { - return false; - } - if payload - .validate_exp(biscuit::Validation::Validate( - biscuit::TemporalOptions::default(), - )) - .is_err() + let mut validator = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); + validator.set_issuer(&["DietPi Dashboard"]); + validator.set_required_spec_claims(&["exp", "iat"]); + if jsonwebtoken::decode::( + token, + &jsonwebtoken::DecodingKey::from_secret(CONFIG.secret.as_ref()), + &validator, + ) + .is_err() { return false; }