use std::{collections::HashMap, path::PathBuf};
use mogh_auth_client::config::NamedOauthConfig;
use serde::Deserialize;
use crate::{
deserializers::option_string_list_deserializer,
entities::{
Timelength,
config::DatabaseConfig,
logger::{LogConfig, LogLevel, StdioLogMode},
},
};
use super::{DockerRegistry, GitProvider, empty_or_redacted};
#[derive(Debug, Clone, Deserialize)]
pub struct Env {
#[serde(
default = "default_core_config_paths",
alias = "komodo_config_path"
)]
pub komodo_config_paths: Vec<PathBuf>,
#[serde(
default = "super::default_config_keywords",
alias = "komodo_config_keyword"
)]
pub komodo_config_keywords: Vec<String>,
#[serde(default = "super::default_merge_nested_config")]
pub komodo_merge_nested_config: bool,
#[serde(default = "super::default_extend_config_arrays")]
pub komodo_extend_config_arrays: bool,
#[serde(default)]
pub komodo_config_debug: bool,
pub komodo_title: Option<String>,
pub komodo_host: Option<String>,
pub komodo_port: Option<u16>,
pub komodo_bind_ip: Option<String>,
pub komodo_private_key: Option<String>,
pub komodo_private_key_file: Option<PathBuf>,
#[serde(alias = "komodo_periphery_public_key")]
pub komodo_periphery_public_keys: Option<Vec<String>>,
pub komodo_passkey: Option<String>,
pub komodo_passkey_file: Option<PathBuf>,
#[serde(alias = "tz")]
pub komodo_timezone: Option<String>,
pub komodo_first_server_name: Option<String>,
#[serde(alias = "komodo_first_server")]
pub komodo_first_server_address: Option<String>,
pub komodo_jwt_secret: Option<String>,
pub komodo_jwt_secret_file: Option<PathBuf>,
pub komodo_jwt_ttl: Option<Timelength>,
pub komodo_resource_poll_interval: Option<Timelength>,
pub komodo_monitoring_interval: Option<Timelength>,
pub komodo_keep_stats_for_days: Option<u64>,
pub komodo_keep_alerts_for_days: Option<u64>,
pub komodo_webhook_secret: Option<String>,
pub komodo_webhook_secret_file: Option<PathBuf>,
pub komodo_webhook_base_url: Option<String>,
pub komodo_logging_level: Option<LogLevel>,
pub komodo_logging_stdio: Option<StdioLogMode>,
pub komodo_logging_pretty: Option<bool>,
pub komodo_logging_location: Option<bool>,
pub komodo_logging_ansi: Option<bool>,
pub komodo_logging_otlp_endpoint: Option<String>,
pub komodo_logging_opentelemetry_service_name: Option<String>,
pub komodo_logging_opentelemetry_scope_name: Option<String>,
pub komodo_pretty_startup_config: Option<bool>,
pub komodo_unsafe_unsanitized_startup_config: Option<bool>,
pub komodo_transparent_mode: Option<bool>,
pub komodo_ui_write_disabled: Option<bool>,
pub komodo_enable_new_users: Option<bool>,
pub komodo_disable_user_registration: Option<bool>,
pub komodo_lock_login_credentials_for: Option<Vec<String>>,
pub komodo_disable_confirm_dialog: Option<bool>,
pub komodo_disable_non_admin_create: Option<bool>,
pub komodo_disable_websocket_reconnect: Option<bool>,
pub komodo_disable_init_resources: Option<bool>,
pub komodo_enable_fancy_toml: Option<bool>,
pub komodo_local_auth: Option<bool>,
pub komodo_min_password_length: Option<u16>,
pub komodo_init_admin_username: Option<String>,
pub komodo_init_admin_username_file: Option<PathBuf>,
pub komodo_init_admin_password: Option<String>,
pub komodo_init_admin_password_file: Option<PathBuf>,
pub komodo_oidc_enabled: Option<bool>,
pub komodo_oidc_provider: Option<String>,
pub komodo_oidc_redirect_host: Option<String>,
pub komodo_oidc_client_id: Option<String>,
pub komodo_oidc_client_id_file: Option<PathBuf>,
pub komodo_oidc_client_secret: Option<String>,
pub komodo_oidc_client_secret_file: Option<PathBuf>,
pub komodo_oidc_use_full_email: Option<bool>,
pub komodo_oidc_additional_audiences: Option<Vec<String>>,
pub komodo_oidc_additional_audiences_file: Option<PathBuf>,
pub komodo_google_oauth_enabled: Option<bool>,
pub komodo_google_oauth_id: Option<String>,
pub komodo_google_oauth_id_file: Option<PathBuf>,
pub komodo_google_oauth_secret: Option<String>,
pub komodo_google_oauth_secret_file: Option<PathBuf>,
pub komodo_github_oauth_enabled: Option<bool>,
pub komodo_github_oauth_id: Option<String>,
pub komodo_github_oauth_id_file: Option<PathBuf>,
pub komodo_github_oauth_secret: Option<String>,
pub komodo_github_oauth_secret_file: Option<PathBuf>,
pub komodo_auth_rate_limit_disabled: Option<bool>,
pub komodo_auth_rate_limit_max_attempts: Option<u16>,
pub komodo_auth_rate_limit_window_seconds: Option<u64>,
pub komodo_cors_allowed_origins: Option<Vec<String>>,
pub komodo_cors_allow_credentials: Option<bool>,
pub komodo_session_allow_cross_site: Option<bool>,
#[serde(alias = "komodo_mongo_uri")]
pub komodo_database_uri: Option<String>,
#[serde(alias = "komodo_mongo_uri_file")]
pub komodo_database_uri_file: Option<PathBuf>,
#[serde(alias = "komodo_mongo_address")]
pub komodo_database_address: Option<String>,
#[serde(alias = "komodo_mongo_username")]
pub komodo_database_username: Option<String>,
#[serde(alias = "komodo_mongo_username_file")]
pub komodo_database_username_file: Option<PathBuf>,
#[serde(alias = "komodo_mongo_password")]
pub komodo_database_password: Option<String>,
#[serde(alias = "komodo_mongo_password_file")]
pub komodo_database_password_file: Option<PathBuf>,
#[serde(alias = "komodo_mongo_app_name")]
pub komodo_database_app_name: Option<String>,
#[serde(alias = "komodo_mongo_db_name")]
pub komodo_database_db_name: Option<String>,
pub komodo_aws_access_key_id: Option<String>,
pub komodo_aws_access_key_id_file: Option<PathBuf>,
pub komodo_aws_secret_access_key: Option<String>,
pub komodo_aws_secret_access_key_file: Option<PathBuf>,
pub komodo_internet_interface: Option<String>,
pub komodo_ssl_enabled: Option<bool>,
pub komodo_ssl_key_file: Option<String>,
pub komodo_ssl_cert_file: Option<String>,
pub komodo_ui_path: Option<String>,
pub komodo_ui_index_force_no_cache: Option<bool>,
pub komodo_sync_directory: Option<PathBuf>,
pub komodo_repo_directory: Option<PathBuf>,
pub komodo_action_directory: Option<PathBuf>,
}
fn default_core_config_paths() -> Vec<PathBuf> {
vec![PathBuf::from("/config")]
}
#[derive(Debug, Clone, Deserialize)]
pub struct CoreConfig {
#[serde(default = "default_title")]
pub title: String,
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_core_port")]
pub port: u16,
#[serde(default = "default_core_bind_ip")]
pub bind_ip: String,
#[serde(default)]
pub internet_interface: String,
#[serde(default = "default_private_key")]
pub private_key: String,
#[serde(
default,
alias = "periphery_public_key",
deserialize_with = "option_string_list_deserializer",
skip_serializing_if = "Option::is_none"
)]
pub periphery_public_keys: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub passkey: Option<String>,
#[serde(default)]
pub timezone: String,
#[serde(default)]
pub ui_write_disabled: bool,
#[serde(default)]
pub disable_confirm_dialog: bool,
#[serde(default)]
pub disable_websocket_reconnect: bool,
#[serde(default)]
pub disable_init_resources: bool,
#[serde(default)]
pub enable_fancy_toml: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_server_name: Option<String>,
#[serde(
alias = "first_server",
skip_serializing_if = "Option::is_none"
)]
pub first_server_address: Option<String>,
#[serde(default, alias = "mongo")]
pub database: DatabaseConfig,
#[serde(default)]
pub local_auth: bool,
#[serde(default = "default_min_password_length")]
pub min_password_length: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub init_admin_username: Option<String>,
#[serde(default = "default_init_admin_password")]
pub init_admin_password: String,
#[serde(default)]
pub transparent_mode: bool,
#[serde(default)]
pub enable_new_users: bool,
#[serde(default)]
pub disable_user_registration: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub lock_login_credentials_for: Vec<String>,
#[serde(default)]
pub disable_non_admin_create: bool,
#[serde(default)]
pub jwt_secret: String,
#[serde(default = "default_jwt_ttl")]
pub jwt_ttl: Timelength,
#[serde(default)]
pub oidc_enabled: bool,
#[serde(default)]
pub oidc_provider: String,
#[serde(default)]
pub oidc_redirect_host: String,
#[serde(default)]
pub oidc_client_id: String,
#[serde(default)]
pub oidc_client_secret: String,
#[serde(default)]
pub oidc_use_full_email: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub oidc_additional_audiences: Vec<String>,
#[serde(default)]
pub google_oauth: NamedOauthConfig,
#[serde(default)]
pub github_oauth: NamedOauthConfig,
#[serde(default)]
pub auth_rate_limit_disabled: bool,
#[serde(default = "default_auth_rate_limit_max_attempts")]
pub auth_rate_limit_max_attempts: u16,
#[serde(default = "default_auth_rate_limit_window_seconds")]
pub auth_rate_limit_window_seconds: u64,
#[serde(default)]
pub cors_allowed_origins: Vec<String>,
#[serde(default)]
pub cors_allow_credentials: bool,
#[serde(default)]
pub session_allow_cross_site: bool,
#[serde(default)]
pub webhook_secret: String,
#[serde(default)]
pub webhook_base_url: String,
#[serde(default)]
pub logging: LogConfig,
#[serde(default)]
pub pretty_startup_config: bool,
#[serde(default)]
pub unsafe_unsanitized_startup_config: bool,
#[serde(default = "default_prune_days")]
pub keep_stats_for_days: u64,
#[serde(default = "default_prune_days")]
pub keep_alerts_for_days: u64,
#[serde(default = "default_poll_interval")]
pub resource_poll_interval: Timelength,
#[serde(default = "default_monitoring_interval")]
pub monitoring_interval: Timelength,
#[serde(default)]
pub aws: AwsCredentials,
#[serde(
default,
alias = "git_provider",
skip_serializing_if = "Vec::is_empty"
)]
pub git_providers: Vec<GitProvider>,
#[serde(
default,
alias = "docker_registry",
skip_serializing_if = "Vec::is_empty"
)]
pub docker_registries: Vec<DockerRegistry>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub secrets: HashMap<String, String>,
#[serde(default)]
pub ssl_enabled: bool,
#[serde(default = "default_ssl_key_file")]
pub ssl_key_file: String,
#[serde(default = "default_ssl_cert_file")]
pub ssl_cert_file: String,
#[serde(default = "default_sync_directory")]
pub sync_directory: PathBuf,
#[serde(default = "default_repo_directory")]
pub repo_directory: PathBuf,
#[serde(default = "default_action_directory")]
pub action_directory: PathBuf,
#[serde(default = "default_ui_path")]
pub ui_path: String,
#[serde(default)]
pub ui_index_force_no_cache: bool,
}
fn default_title() -> String {
String::from("Komodo")
}
fn default_host() -> String {
String::from("https://komodo.example.com")
}
fn default_core_port() -> u16 {
9120
}
fn default_core_bind_ip() -> String {
"[::]".to_string()
}
fn default_private_key() -> String {
String::from("file:/config/keys/core.key")
}
fn default_ui_path() -> String {
"/app/ui".to_string()
}
fn default_jwt_ttl() -> Timelength {
Timelength::OneDay
}
fn default_min_password_length() -> u16 {
1
}
fn default_init_admin_password() -> String {
String::from("changeme")
}
fn default_auth_rate_limit_max_attempts() -> u16 {
5
}
fn default_auth_rate_limit_window_seconds() -> u64 {
15
}
fn default_sync_directory() -> PathBuf {
PathBuf::from("/syncs")
}
fn default_repo_directory() -> PathBuf {
PathBuf::from("/repo-cache")
}
fn default_action_directory() -> PathBuf {
PathBuf::from("/action-cache")
}
fn default_prune_days() -> u64 {
14
}
fn default_poll_interval() -> Timelength {
Timelength::OneHour
}
fn default_monitoring_interval() -> Timelength {
Timelength::FifteenSeconds
}
fn default_ssl_key_file() -> String {
"/config/ssl/key.pem".to_string()
}
fn default_ssl_cert_file() -> String {
"/config/ssl/cert.pem".to_string()
}
impl Default for CoreConfig {
fn default() -> Self {
Self {
title: default_title(),
host: default_host(),
port: default_core_port(),
bind_ip: default_core_bind_ip(),
internet_interface: Default::default(),
private_key: Default::default(),
periphery_public_keys: Default::default(),
passkey: Default::default(),
timezone: Default::default(),
ui_write_disabled: Default::default(),
disable_confirm_dialog: Default::default(),
disable_websocket_reconnect: Default::default(),
disable_init_resources: Default::default(),
enable_fancy_toml: Default::default(),
first_server_address: Default::default(),
first_server_name: Default::default(),
database: Default::default(),
local_auth: Default::default(),
min_password_length: default_min_password_length(),
init_admin_username: Default::default(),
init_admin_password: default_init_admin_password(),
transparent_mode: Default::default(),
enable_new_users: Default::default(),
disable_user_registration: Default::default(),
lock_login_credentials_for: Default::default(),
disable_non_admin_create: Default::default(),
jwt_secret: Default::default(),
jwt_ttl: default_jwt_ttl(),
oidc_enabled: Default::default(),
oidc_provider: Default::default(),
oidc_redirect_host: Default::default(),
oidc_client_id: Default::default(),
oidc_client_secret: Default::default(),
oidc_use_full_email: Default::default(),
oidc_additional_audiences: Default::default(),
google_oauth: Default::default(),
github_oauth: Default::default(),
auth_rate_limit_disabled: Default::default(),
auth_rate_limit_max_attempts:
default_auth_rate_limit_max_attempts(),
auth_rate_limit_window_seconds:
default_auth_rate_limit_window_seconds(),
cors_allowed_origins: Default::default(),
cors_allow_credentials: Default::default(),
session_allow_cross_site: Default::default(),
webhook_secret: Default::default(),
webhook_base_url: Default::default(),
logging: Default::default(),
pretty_startup_config: Default::default(),
unsafe_unsanitized_startup_config: Default::default(),
keep_stats_for_days: default_prune_days(),
keep_alerts_for_days: default_prune_days(),
resource_poll_interval: default_poll_interval(),
monitoring_interval: default_monitoring_interval(),
aws: Default::default(),
git_providers: Default::default(),
docker_registries: Default::default(),
secrets: Default::default(),
ssl_enabled: Default::default(),
ssl_key_file: default_ssl_key_file(),
ssl_cert_file: default_ssl_cert_file(),
ui_path: default_ui_path(),
ui_index_force_no_cache: Default::default(),
sync_directory: default_sync_directory(),
repo_directory: default_repo_directory(),
action_directory: default_action_directory(),
}
}
}
impl CoreConfig {
pub fn sanitized(&self) -> CoreConfig {
let config = self.clone();
CoreConfig {
title: config.title,
host: config.host,
port: config.port,
bind_ip: config.bind_ip,
private_key: if self.private_key.starts_with("file:") {
self.private_key.clone()
} else {
empty_or_redacted(&self.private_key)
},
periphery_public_keys: config.periphery_public_keys,
passkey: config.passkey.as_deref().map(empty_or_redacted),
timezone: config.timezone,
first_server_address: config.first_server_address,
first_server_name: config.first_server_name,
jwt_secret: empty_or_redacted(&config.jwt_secret),
jwt_ttl: config.jwt_ttl,
internet_interface: config.internet_interface,
resource_poll_interval: config.resource_poll_interval,
monitoring_interval: config.monitoring_interval,
keep_stats_for_days: config.keep_stats_for_days,
keep_alerts_for_days: config.keep_alerts_for_days,
logging: config.logging,
pretty_startup_config: config.pretty_startup_config,
unsafe_unsanitized_startup_config: config
.unsafe_unsanitized_startup_config,
transparent_mode: config.transparent_mode,
ui_write_disabled: config.ui_write_disabled,
disable_confirm_dialog: config.disable_confirm_dialog,
disable_websocket_reconnect: config.disable_websocket_reconnect,
disable_init_resources: config.disable_init_resources,
enable_fancy_toml: config.enable_fancy_toml,
enable_new_users: config.enable_new_users,
disable_user_registration: config.disable_user_registration,
disable_non_admin_create: config.disable_non_admin_create,
lock_login_credentials_for: config.lock_login_credentials_for,
local_auth: config.local_auth,
min_password_length: config.min_password_length,
init_admin_username: config
.init_admin_username
.map(|u| empty_or_redacted(&u)),
init_admin_password: empty_or_redacted(
&config.init_admin_password,
),
oidc_enabled: config.oidc_enabled,
oidc_provider: config.oidc_provider,
oidc_redirect_host: config.oidc_redirect_host,
oidc_client_id: empty_or_redacted(&config.oidc_client_id),
oidc_client_secret: empty_or_redacted(
&config.oidc_client_secret,
),
oidc_use_full_email: config.oidc_use_full_email,
oidc_additional_audiences: config
.oidc_additional_audiences
.iter()
.map(|aud| empty_or_redacted(aud))
.collect(),
google_oauth: NamedOauthConfig {
enabled: config.google_oauth.enabled,
client_id: empty_or_redacted(&config.google_oauth.client_id),
client_secret: empty_or_redacted(
&config.google_oauth.client_secret,
),
},
github_oauth: NamedOauthConfig {
enabled: config.github_oauth.enabled,
client_id: empty_or_redacted(&config.github_oauth.client_id),
client_secret: empty_or_redacted(
&config.github_oauth.client_secret,
),
},
auth_rate_limit_disabled: config.auth_rate_limit_disabled,
auth_rate_limit_max_attempts: config
.auth_rate_limit_max_attempts,
auth_rate_limit_window_seconds: config
.auth_rate_limit_window_seconds,
cors_allowed_origins: config.cors_allowed_origins,
cors_allow_credentials: config.cors_allow_credentials,
session_allow_cross_site: config.session_allow_cross_site,
webhook_secret: empty_or_redacted(&config.webhook_secret),
webhook_base_url: config.webhook_base_url,
database: config.database.sanitized(),
aws: AwsCredentials {
access_key_id: empty_or_redacted(&config.aws.access_key_id),
secret_access_key: empty_or_redacted(
&config.aws.secret_access_key,
),
},
secrets: config
.secrets
.into_iter()
.map(|(id, secret)| (id, empty_or_redacted(&secret)))
.collect(),
git_providers: config
.git_providers
.into_iter()
.map(|mut provider| {
provider.accounts.iter_mut().for_each(|account| {
account.token = empty_or_redacted(&account.token);
});
provider
})
.collect(),
docker_registries: config
.docker_registries
.into_iter()
.map(|mut provider| {
provider.accounts.iter_mut().for_each(|account| {
account.token = empty_or_redacted(&account.token);
});
provider
})
.collect(),
ssl_enabled: config.ssl_enabled,
ssl_key_file: config.ssl_key_file,
ssl_cert_file: config.ssl_cert_file,
ui_path: config.ui_path,
ui_index_force_no_cache: config.ui_index_force_no_cache,
repo_directory: config.repo_directory,
action_directory: config.action_directory,
sync_directory: config.sync_directory,
}
}
pub fn oidc_enabled(&self) -> bool {
self.oidc_enabled
&& !self.oidc_provider.is_empty()
&& !self.oidc_client_id.is_empty()
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct AwsCredentials {
pub access_key_id: String,
pub secret_access_key: String,
}
impl mogh_server::ServerConfig for &CoreConfig {
fn bind_ip(&self) -> &str {
&self.bind_ip
}
fn port(&self) -> u16 {
self.port
}
fn ssl_enabled(&self) -> bool {
self.ssl_enabled
}
fn ssl_key_file(&self) -> &str {
&self.ssl_key_file
}
fn ssl_cert_file(&self) -> &str {
&self.ssl_cert_file
}
}
impl mogh_server::cors::CorsConfig for &CoreConfig {
fn allowed_origins(&self) -> &[String] {
&self.cors_allowed_origins
}
fn allow_credentials(&self) -> bool {
self.cors_allow_credentials
}
}
impl mogh_server::session::SessionConfig for &CoreConfig {
fn host(&self) -> &str {
&self.host
}
fn host_env_field(&self) -> &str {
"KOMODO_HOST"
}
fn expiry_seconds(&self) -> i64 {
60 * 3
}
fn allow_cross_site(&self) -> bool {
self.session_allow_cross_site
}
}