Includes all pending changes needed for nixos-anywhere: - fiberlb: L7 policy, rule, certificate types - deployer: New service for cluster management - nix-nos: Generic network modules - Various service updates and fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
335 lines
8.2 KiB
Rust
335 lines
8.2 KiB
Rust
//! Server configuration
|
|
//!
|
|
//! Configuration for the IAM server including storage backend, authentication, and server settings.
|
|
|
|
use std::net::SocketAddr;
|
|
use std::path::Path;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Main server configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct ServerConfig {
|
|
/// Server settings
|
|
pub server: ServerSettings,
|
|
|
|
/// Storage backend configuration
|
|
pub store: StoreConfig,
|
|
|
|
/// Authentication configuration
|
|
pub authn: AuthnConfig,
|
|
|
|
/// Logging configuration
|
|
#[serde(default)]
|
|
pub logging: LoggingConfig,
|
|
}
|
|
|
|
impl ServerConfig {
|
|
/// Load configuration from a TOML file
|
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
|
|
let content = std::fs::read_to_string(path).map_err(|e| ConfigError::Io(e.to_string()))?;
|
|
toml::from_str(&content).map_err(|e| ConfigError::Parse(e.to_string()))
|
|
}
|
|
|
|
/// Load configuration from environment variables
|
|
pub fn from_env() -> Result<Self, ConfigError> {
|
|
// Try to find config file from environment
|
|
if let Ok(path) = std::env::var("IAM_CONFIG") {
|
|
return Self::from_file(path);
|
|
}
|
|
|
|
// Default paths to try
|
|
let default_paths = ["iam.toml", "/etc/iam/iam.toml", "config/iam.toml"];
|
|
|
|
for path in &default_paths {
|
|
if Path::new(path).exists() {
|
|
return Self::from_file(path);
|
|
}
|
|
}
|
|
|
|
// Use default configuration
|
|
Ok(Self::default())
|
|
}
|
|
|
|
/// Create a minimal configuration for testing
|
|
#[cfg(test)]
|
|
pub fn for_testing() -> Self {
|
|
Self {
|
|
server: ServerSettings {
|
|
addr: "127.0.0.1:50051".parse().unwrap(),
|
|
http_addr: "127.0.0.1:8083".parse().unwrap(),
|
|
tls: None,
|
|
},
|
|
store: StoreConfig {
|
|
backend: BackendKind::Memory,
|
|
chainfire_endpoints: None,
|
|
flaredb_endpoint: None,
|
|
flaredb_namespace: None,
|
|
},
|
|
authn: AuthnConfig {
|
|
jwt: None,
|
|
internal_token: InternalTokenConfig {
|
|
signing_key: "test-signing-key-for-development-only".into(),
|
|
issuer: "iam-test".into(),
|
|
default_ttl_seconds: 3600,
|
|
max_ttl_seconds: 86400,
|
|
},
|
|
},
|
|
logging: LoggingConfig::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Server settings
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServerSettings {
|
|
/// Listen address
|
|
#[serde(default = "default_addr")]
|
|
pub addr: SocketAddr,
|
|
|
|
/// HTTP REST API listen address
|
|
#[serde(default = "default_http_addr")]
|
|
pub http_addr: SocketAddr,
|
|
|
|
/// TLS configuration
|
|
pub tls: Option<TlsConfig>,
|
|
}
|
|
|
|
impl Default for ServerSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
addr: default_addr(),
|
|
http_addr: default_http_addr(),
|
|
tls: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_addr() -> SocketAddr {
|
|
"0.0.0.0:50051".parse().unwrap()
|
|
}
|
|
|
|
fn default_http_addr() -> SocketAddr {
|
|
"127.0.0.1:8083".parse().unwrap()
|
|
}
|
|
|
|
/// TLS configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TlsConfig {
|
|
/// Path to certificate file (PEM)
|
|
pub cert_file: String,
|
|
|
|
/// Path to private key file (PEM)
|
|
pub key_file: String,
|
|
|
|
/// Path to CA certificate file for client verification (optional)
|
|
pub ca_file: Option<String>,
|
|
|
|
/// Require client certificates
|
|
#[serde(default)]
|
|
pub require_client_cert: bool,
|
|
}
|
|
|
|
/// Storage backend configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StoreConfig {
|
|
/// Backend type
|
|
#[serde(default)]
|
|
pub backend: BackendKind,
|
|
|
|
/// Chainfire endpoints (for chainfire backend)
|
|
pub chainfire_endpoints: Option<Vec<String>>,
|
|
|
|
/// FlareDB endpoint (for flaredb backend)
|
|
pub flaredb_endpoint: Option<String>,
|
|
|
|
/// FlareDB namespace (for flaredb backend)
|
|
pub flaredb_namespace: Option<String>,
|
|
}
|
|
|
|
impl Default for StoreConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
backend: BackendKind::Memory,
|
|
chainfire_endpoints: None,
|
|
flaredb_endpoint: None,
|
|
flaredb_namespace: Some("iam".into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Backend type
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum BackendKind {
|
|
/// In-memory backend (for testing)
|
|
#[default]
|
|
Memory,
|
|
/// Chainfire distributed KV
|
|
Chainfire,
|
|
/// FlareDB distributed database
|
|
FlareDb,
|
|
}
|
|
|
|
/// Authentication configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct AuthnConfig {
|
|
/// JWT/OIDC configuration
|
|
pub jwt: Option<JwtConfig>,
|
|
|
|
/// Internal token configuration
|
|
pub internal_token: InternalTokenConfig,
|
|
}
|
|
|
|
/// JWT/OIDC configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct JwtConfig {
|
|
/// JWKS endpoint URL
|
|
pub jwks_url: String,
|
|
|
|
/// Expected issuer
|
|
pub issuer: String,
|
|
|
|
/// Expected audience
|
|
pub audience: String,
|
|
|
|
/// JWKS cache TTL in seconds
|
|
#[serde(default = "default_jwks_cache_ttl")]
|
|
pub jwks_cache_ttl_seconds: u64,
|
|
}
|
|
|
|
fn default_jwks_cache_ttl() -> u64 {
|
|
3600
|
|
}
|
|
|
|
/// Internal token configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InternalTokenConfig {
|
|
/// Signing key (base64 encoded or raw string)
|
|
pub signing_key: String,
|
|
|
|
/// Token issuer identifier
|
|
#[serde(default = "default_issuer")]
|
|
pub issuer: String,
|
|
|
|
/// Default token TTL in seconds
|
|
#[serde(default = "default_token_ttl")]
|
|
pub default_ttl_seconds: u64,
|
|
|
|
/// Maximum token TTL in seconds
|
|
#[serde(default = "default_max_token_ttl")]
|
|
pub max_ttl_seconds: u64,
|
|
}
|
|
|
|
impl Default for InternalTokenConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
signing_key: String::new(),
|
|
issuer: default_issuer(),
|
|
default_ttl_seconds: default_token_ttl(),
|
|
max_ttl_seconds: default_max_token_ttl(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_issuer() -> String {
|
|
"iam".into()
|
|
}
|
|
|
|
fn default_token_ttl() -> u64 {
|
|
3600 // 1 hour
|
|
}
|
|
|
|
fn default_max_token_ttl() -> u64 {
|
|
86400 * 7 // 7 days
|
|
}
|
|
|
|
/// Logging configuration
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
/// Log level
|
|
#[serde(default = "default_log_level")]
|
|
pub level: String,
|
|
|
|
/// Log format (json or text)
|
|
#[serde(default = "default_log_format")]
|
|
pub format: String,
|
|
}
|
|
|
|
impl Default for LoggingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
level: default_log_level(),
|
|
format: default_log_format(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_log_level() -> String {
|
|
"info".into()
|
|
}
|
|
|
|
fn default_log_format() -> String {
|
|
"text".into()
|
|
}
|
|
|
|
/// Configuration errors
|
|
#[derive(Debug)]
|
|
pub enum ConfigError {
|
|
/// IO error
|
|
Io(String),
|
|
/// Parse error
|
|
Parse(String),
|
|
}
|
|
|
|
impl std::fmt::Display for ConfigError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
ConfigError::Io(e) => write!(f, "IO error: {}", e),
|
|
ConfigError::Parse(e) => write!(f, "Parse error: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ConfigError {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_default_config() {
|
|
let config = ServerConfig::default();
|
|
assert_eq!(config.server.addr, "0.0.0.0:50051".parse().unwrap());
|
|
assert!(matches!(config.store.backend, BackendKind::Memory));
|
|
}
|
|
|
|
#[test]
|
|
fn test_for_testing() {
|
|
let config = ServerConfig::for_testing();
|
|
assert!(!config.authn.internal_token.signing_key.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_toml_parse() {
|
|
let toml = r#"
|
|
[server]
|
|
addr = "127.0.0.1:8080"
|
|
|
|
[store]
|
|
backend = "memory"
|
|
|
|
[authn.internal_token]
|
|
signing_key = "secret"
|
|
issuer = "test"
|
|
default_ttl_seconds = 1800
|
|
|
|
[logging]
|
|
level = "debug"
|
|
"#;
|
|
let config: ServerConfig = toml::from_str(toml).unwrap();
|
|
assert_eq!(config.server.addr, "127.0.0.1:8080".parse().unwrap());
|
|
assert_eq!(config.authn.internal_token.default_ttl_seconds, 1800);
|
|
assert_eq!(config.logging.level, "debug");
|
|
}
|
|
}
|