Dashboard endpoint and page aggregating inventory, vulnerabilities, cluster health, backups and recent jobs. Security headers (CSP, nosniff, DENY, referrer policy), 1 MB body limit, configurable login rate limit, audit steps in CI. Installer script, systemd unit, install/architecture docs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
81 lines
3.4 KiB
Rust
81 lines
3.4 KiB
Rust
//! Runtime configuration read from environment variables (see `.env.example`).
|
|
use std::net::SocketAddr;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Config {
|
|
pub database_url: String,
|
|
pub jwt_secret: String,
|
|
/// 64 hex chars; encrypts secrets at rest.
|
|
pub master_key: String,
|
|
/// Use fake host adapters (dev machines without apt/kubectl).
|
|
pub fake_host: bool,
|
|
/// Path to a kubeconfig; None infers. Defaults to the microk8s client config if present.
|
|
pub kubeconfig: Option<String>,
|
|
/// kubectl invocation for backups, e.g. ["/snap/bin/microk8s", "kubectl"].
|
|
pub kubectl: Vec<String>,
|
|
/// containerd socket for local image scans (default: microk8s socket if present).
|
|
pub containerd_socket: Option<String>,
|
|
/// Scratch directory for backup archives.
|
|
pub work_dir: std::path::PathBuf,
|
|
pub bind: SocketAddr,
|
|
pub bootstrap_admin: Option<(String, String)>,
|
|
pub cookie_secure: bool,
|
|
/// Login attempts per IP and minute.
|
|
pub login_rate_limit: u32,
|
|
pub frontend_dir: String,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env() -> anyhow::Result<Self> {
|
|
let env = |k: &str| std::env::var(k).ok().filter(|v| !v.is_empty());
|
|
let jwt_secret =
|
|
env("JWT_SECRET").ok_or_else(|| anyhow::anyhow!("JWT_SECRET is required"))?;
|
|
anyhow::ensure!(
|
|
jwt_secret.len() >= 32,
|
|
"JWT_SECRET must be at least 32 characters"
|
|
);
|
|
let master_key = env("MASTER_KEY")
|
|
.ok_or_else(|| anyhow::anyhow!("MASTER_KEY is required (64 hex characters)"))?;
|
|
Ok(Self {
|
|
database_url: env("DATABASE_URL")
|
|
.unwrap_or_else(|| "sqlite://data/monitoring.db?mode=rwc".into()),
|
|
jwt_secret,
|
|
master_key,
|
|
fake_host: env("FAKE_HOST").is_some_and(|v| v == "true" || v == "1"),
|
|
kubectl: env("KUBECTL")
|
|
.map(|v| v.split_whitespace().map(String::from).collect())
|
|
.unwrap_or_else(|| {
|
|
if std::path::Path::new("/snap/bin/microk8s").exists() {
|
|
vec!["/snap/bin/microk8s".into(), "kubectl".into()]
|
|
} else {
|
|
vec!["kubectl".into()]
|
|
}
|
|
}),
|
|
containerd_socket: env("CONTAINERD_ADDRESS").or_else(|| {
|
|
let microk8s = "/var/snap/microk8s/common/run/containerd.sock";
|
|
std::path::Path::new(microk8s)
|
|
.exists()
|
|
.then(|| microk8s.to_string())
|
|
}),
|
|
work_dir: env("WORK_DIR")
|
|
.map(Into::into)
|
|
.unwrap_or_else(|| "data/work".into()),
|
|
kubeconfig: env("KUBECONFIG").or_else(|| {
|
|
let microk8s = "/var/snap/microk8s/current/credentials/client.config";
|
|
std::path::Path::new(microk8s)
|
|
.exists()
|
|
.then(|| microk8s.to_string())
|
|
}),
|
|
bind: env("BIND")
|
|
.unwrap_or_else(|| "127.0.0.1:8080".into())
|
|
.parse()?,
|
|
bootstrap_admin: env("BOOTSTRAP_ADMIN_EMAIL").zip(env("BOOTSTRAP_ADMIN_PASSWORD")),
|
|
cookie_secure: env("COOKIE_SECURE").is_some_and(|v| v == "true" || v == "1"),
|
|
login_rate_limit: env("LOGIN_RATE_LIMIT")
|
|
.and_then(|v| v.parse().ok())
|
|
.unwrap_or(10),
|
|
frontend_dir: env("FRONTEND_DIR").unwrap_or_else(|| "../frontend/dist".into()),
|
|
})
|
|
}
|
|
}
|