initial-commit

This commit is contained in:
Guillaume GRABÉ
2026-05-12 17:06:43 +02:00
commit 051a080dfa
110 changed files with 26377 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "charybdis-server"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "charybdis"
path = "src/main.rs"
[dependencies]
charybdis = { path = ".." }
charybdis_defectdojo = { package = "charybdis-defectdojo-plugin", path = "../plugins/defectdojo" }
charybdis_dependencytrack = { package = "charybdis-dependencytrack-plugin", path = "../plugins/dependencytrack" }
charybdis_keycloak = { package = "charybdis-keycloak-plugin", path = "../plugins/keycloak" }
# Re-export core dependencies needed by main
tonic = { version = "0.14.2", features = ["tls-ring", "tls-connect-info"] }
tonic-reflection = "0.14"
tokio = { version = "1.48", features = ["full"] }
tracing = "0.1"
anyhow = "1.0"
rustls = "0.23"
+7
View File
@@ -0,0 +1,7 @@
// This is a stub build.rs that just ensures OUT_DIR is available
// The actual protobuf compilation happens in the main charybdis crate
fn main() {
// Nothing to do - we just need this to exist so OUT_DIR is defined
println!("cargo:rerun-if-changed=build.rs");
}
+419
View File
@@ -0,0 +1,419 @@
use std::sync::Arc;
use std::time::Duration;
use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
use tonic_reflection::server::Builder as ReflectionBuilder;
use tracing::{info, warn};
// Plugin imports
extern crate charybdis_defectdojo;
extern crate charybdis_keycloak;
extern crate charybdis_dependencytrack;
// Import MyEntityService and EntityServiceServer from the library crate
use charybdis::{
MyEntityService,
charybdis::entities::entity_service_server::EntityServiceServer,
charybdis::ingestion::ingestion_service_server::IngestionServiceServer,
config::Config,
database,
events::{EventBus, backends::MemoryEventBus},
findings::ingestion::MyIngestionService,
scanners::ParserRegistry,
security::{
audit::AuditLogger,
config::{RoleMapping, RoleRule, SecurityConfig, SubjectMatch},
interceptor::AuthInterceptor,
rbac::RbacEngine,
tls,
},
telemetry::{init_telemetry, shutdown_telemetry},
};
use std::collections::HashMap;
// Use the descriptor from the charybdis library crate
// The descriptor is compiled by the main charybdis crate's build.rs
use charybdis::ENTITY_DESCRIPTOR_SET;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Install default crypto provider for rustls (required in rustls 0.23+)
// Using ring as the crypto backend since we have tls-ring feature enabled
let _ = rustls::crypto::ring::default_provider().install_default();
// Load configuration from file or environment
info!("Loading configuration...");
let config = Config::load()?;
info!("Configuration loaded successfully");
// Initialize OpenTelemetry (traces, metrics, logs)
let metrics = init_telemetry(config.telemetry.clone())?;
let addr = format!("{}:{}", config.server.grpc_host, config.server.grpc_port).parse()?;
info!("Connecting to database...");
let pool = database::create_connection_pool(&config.database.url).await?;
info!("Ensuring database schema exists...");
database::ensure_schema(&pool).await?;
info!("Database ready");
// Initialize event bus
info!("Initializing event bus...");
let event_bus: Arc<dyn EventBus> = Arc::new(MemoryEventBus::new());
event_bus.start().await?;
info!("Event bus started successfully");
// Initialize plugin system
info!("Initializing plugin system...");
let entity_repository = Arc::new(database::EntityRepository::new(pool.clone()));
let mut plugin_manager = charybdis::plugins::manager::PluginManager::new();
// Load DefectDojo plugin if configured
if let Some(dd_config) = &config.plugins.defectdojo {
let enabled = dd_config.get("enabled")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !enabled {
info!("DefectDojo plugin is disabled");
} else {
info!("Loading DefectDojo plugin...");
match charybdis_defectdojo::DefectDojoConfig::from_toml(dd_config) {
Ok(dd_config) => {
match charybdis_defectdojo::DefectDojoPlugin::new(
dd_config,
entity_repository.clone(),
) {
Ok(plugin) => {
plugin_manager.register_event_driven(Arc::new(plugin));
info!("DefectDojo plugin registered successfully");
}
Err(e) => {
warn!("Failed to initialize DefectDojo plugin: {}", e);
}
}
}
Err(e) => {
warn!("Failed to parse DefectDojo configuration: {}", e);
}
}
}
}
// Load Keycloak plugin if configured
if let Some(kc_config) = &config.plugins.keycloak {
let enabled = kc_config.get("enabled")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !enabled {
info!("Keycloak plugin is disabled");
} else {
info!("Loading Keycloak plugin...");
match charybdis_keycloak::KeycloakConfig::from_toml(kc_config) {
Ok(kc_config) => {
match charybdis_keycloak::KeycloakPlugin::new(
kc_config,
entity_repository.clone(),
) {
Ok(plugin) => {
plugin_manager.register_sync(Arc::new(plugin));
info!("Keycloak plugin registered successfully");
}
Err(e) => {
warn!("Failed to initialize Keycloak plugin: {}", e);
}
}
}
Err(e) => {
warn!("Failed to parse Keycloak configuration: {}", e);
}
}
}
}
// Load Dependency-Track plugin if configured
if let Some(dt_config) = &config.plugins.dependencytrack {
let enabled = dt_config.get("enabled")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !enabled {
info!("Dependency-Track plugin is disabled");
} else {
info!("Loading Dependency-Track plugin...");
match charybdis_dependencytrack::DependencyTrackConfig::from_toml(dt_config) {
Ok(dt_config) => {
match charybdis_dependencytrack::DependencyTrackPlugin::new(
dt_config,
entity_repository.clone(),
) {
Ok(plugin) => {
plugin_manager.register_event_driven(Arc::new(plugin));
info!("Dependency-Track plugin registered successfully");
}
Err(e) => {
warn!("Failed to initialize Dependency-Track plugin: {}", e);
}
}
}
Err(e) => {
warn!("Failed to parse Dependency-Track configuration: {}", e);
}
}
}
}
// Create event dispatcher and subscribe to event bus
let event_plugins = plugin_manager.event_driven_plugins().to_vec();
if !event_plugins.is_empty() {
let dispatcher = Arc::new(charybdis::plugins::dispatcher::EventDispatcher::new(
event_plugins,
entity_repository.clone(),
));
// Subscribe dispatcher to all entity events
event_bus.subscribe(dispatcher).await?;
info!("Event dispatcher subscribed to event bus");
}
info!(
"Plugin system initialized with {} event-driven plugins",
plugin_manager.event_driven_plugins().len()
);
// Load security configuration
let mut security_config = config.security.clone();
// Initialize security components if RBAC is enabled
let auth_interceptor = if security_config.rbac.enabled {
info!("Security (RBAC) is enabled");
// Configure default roles if none are defined
if security_config.rbac.role_mappings.is_empty() {
info!("No role mappings configured, setting up defaults");
security_config = setup_default_rbac_config();
}
let rbac_engine = Arc::new(RbacEngine::new(security_config.rbac.clone()));
let audit_logger = Arc::new(AuditLogger::new(security_config.rbac.audit.enabled));
Some(AuthInterceptor::new(rbac_engine, audit_logger))
} else {
warn!("Security (RBAC) is disabled - running in insecure mode");
None
};
// Clone repository for YAML adapter (EntityRepository is Clone)
let yaml_repository = entity_repository.clone();
let my_entity_service = MyEntityService::new(
(*entity_repository).clone(),
event_bus,
metrics,
auth_interceptor,
);
// Initialize parser registry and ingestion service
let parser_registry = Arc::new(ParserRegistry::with_builtins());
let my_ingestion_service =
MyIngestionService::new(entity_repository.clone(), parser_registry);
// Configure and build the reflection service using the embedded descriptor set.
// In tonic-reflection 0.14+, use build_v1() instead of build()
let reflection_service = ReflectionBuilder::configure()
.register_encoded_file_descriptor_set(ENTITY_DESCRIPTOR_SET)
.build_v1()?;
// Configure server with optional mTLS
let mut server_builder = Server::builder()
.http2_keepalive_interval(Some(Duration::from_secs(60)))
.initial_connection_window_size(1048576)
.initial_stream_window_size(1048576)
.http2_max_header_list_size(64 * 1024); // 64KB, generous for gRPC metadata
if security_config.mtls.enabled {
info!("mTLS is enabled - configuring TLS");
// Validate certificate files exist
tls::validate_cert_files(&security_config.mtls)?;
// Load certificate files
let (server_cert, server_key, ca_cert) = tls::load_tls_files(&security_config.mtls)?;
// Create TLS identity and CA certificate
let identity = Identity::from_pem(&server_cert, &server_key);
let client_ca = Certificate::from_pem(&ca_cert);
// Configure TLS with client certificate verification
let tls_config = ServerTlsConfig::new()
.identity(identity)
.client_ca_root(client_ca);
server_builder = server_builder.tls_config(tls_config)?;
info!("mTLS configuration complete");
} else {
warn!("mTLS is disabled - running without transport security");
}
info!("EntityService server listening on {}", addr);
// Start YAML adapter if enabled
let yaml_adapter_handle = if config.server.yaml_adapter.enabled {
let yaml_host = config.server.yaml_adapter.host.clone();
let yaml_port = config.server.yaml_adapter.port;
let yaml_repo = yaml_repository.clone();
let base_url = format!("http://{}:{}", yaml_host, yaml_port);
info!("Starting YAML adapter on {}:{}", yaml_host, yaml_port);
Some(tokio::spawn(async move {
if let Err(e) = charybdis::adapters::yaml::start_yaml_adapter(
yaml_host, yaml_port, yaml_repo, base_url,
)
.await
{
tracing::error!("YAML adapter error: {}", e);
}
}))
} else {
info!("YAML adapter is disabled");
None
};
// Run the server with graceful shutdown
let server = server_builder
.add_service(EntityServiceServer::new(my_entity_service))
.add_service(IngestionServiceServer::new(my_ingestion_service))
.add_service(reflection_service)
.serve(addr);
// Handle shutdown
tokio::select! {
result = server => {
if let Err(e) = result {
tracing::error!("Server error: {}", e);
}
}
_ = tokio::signal::ctrl_c() => {
info!("Received shutdown signal");
}
}
// Abort YAML adapter if it's running
if let Some(handle) = yaml_adapter_handle {
handle.abort();
}
// Gracefully shutdown telemetry
shutdown_telemetry().await;
info!("Shutdown complete");
Ok(())
}
/// Setup default RBAC configuration for testing/development
/// Maps certificate attributes to roles based on OU (Organizational Unit)
fn setup_default_rbac_config() -> SecurityConfig {
use charybdis::security::config::{AuditConfig, RbacConfig};
let mut permissions = HashMap::new();
// Admin role: full access
permissions.insert(
"admin".to_string(),
vec![
"entity:create".to_string(),
"entity:read".to_string(),
"entity:update".to_string(),
"entity:delete".to_string(),
"entity:list".to_string(),
],
);
// Platform team: full access (mapped from platform-team OU)
permissions.insert(
"platform".to_string(),
vec![
"entity:create".to_string(),
"entity:read".to_string(),
"entity:update".to_string(),
"entity:delete".to_string(),
"entity:list".to_string(),
],
);
// Automation/CI: create, read, update, list
permissions.insert(
"automation".to_string(),
vec![
"entity:create".to_string(),
"entity:read".to_string(),
"entity:update".to_string(),
"entity:list".to_string(),
],
);
// Plugins: read and list only
permissions.insert(
"plugin".to_string(),
vec!["entity:read".to_string(), "entity:list".to_string()],
);
let role_mappings = vec![
// Map platform-team OU to platform role
RoleMapping {
role: "platform".to_string(),
rules: vec![RoleRule {
subject: SubjectMatch {
cn: None,
ou: Some("platform-team".to_string()),
o: None,
},
}],
},
// Map automation OU to automation role
RoleMapping {
role: "automation".to_string(),
rules: vec![RoleRule {
subject: SubjectMatch {
cn: None,
ou: Some("automation".to_string()),
o: None,
},
}],
},
// Map plugins OU to plugin role
RoleMapping {
role: "plugin".to_string(),
rules: vec![RoleRule {
subject: SubjectMatch {
cn: None,
ou: Some("plugins".to_string()),
o: None,
},
}],
},
];
SecurityConfig {
mtls: charybdis::security::config::MtlsConfig {
enabled: std::env::var("SECURITY_MTLS_ENABLED")
.unwrap_or_else(|_| "false".to_string())
.parse()
.unwrap_or(false),
server_cert: std::env::var("SECURITY_MTLS_SERVER_CERT").unwrap_or_default(),
server_key: std::env::var("SECURITY_MTLS_SERVER_KEY").unwrap_or_default(),
client_ca_cert: std::env::var("SECURITY_MTLS_CLIENT_CA").unwrap_or_default(),
require_client_cert: true,
crl_file: None,
},
rbac: RbacConfig {
enabled: true,
role_mappings,
permissions,
audit: AuditConfig {
enabled: true,
log_all_requests: true,
log_denied_requests: true,
},
},
}
}