security(P0): fail-closed when auth is enabled but cannot initialize
Previously, if enable_auth=true but the database or auth services failed to initialize, the server silently started in PUBLIC mode — all API routes accessible without authentication. This is a critical fail-open bug. Fixes (fail-closed behavior): 1. main.rs: DB pool creation failure with enable_auth=true now panics instead of falling through with db_pool=None 2. di.rs: create_auth_services() failure now propagates the error via ? instead of logging and continuing with auth_services=None 3. main.rs: Added defensive assert! that auth_service is Some when enable_auth=true, preventing any future refactor from reintroducing the silent degradation The server will now refuse to start if authentication is configured but cannot be properly initialized.
This commit is contained in:
+21
-10
@@ -459,21 +459,32 @@ impl AppServiceFactory {
|
||||
|
||||
// Auth services
|
||||
if self.config.features.enable_auth {
|
||||
match crate::infrastructure::auth_factory::create_auth_services(
|
||||
let services = crate::infrastructure::auth_factory::create_auth_services(
|
||||
&self.config,
|
||||
pool.clone(),
|
||||
Some(apps.folder_service_concrete.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(services) => {
|
||||
tracing::info!("Authentication services initialized successfully");
|
||||
auth_services = Some(services);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to initialize authentication services: {}", e);
|
||||
}
|
||||
}
|
||||
.map_err(|e| {
|
||||
// SECURITY: fail-closed. If auth is required but the auth
|
||||
// services cannot be created, propagate the error so the
|
||||
// server refuses to start — never degrade to public mode.
|
||||
tracing::error!(
|
||||
"FATAL: enable_auth=true but auth services failed to initialize: {}",
|
||||
e
|
||||
);
|
||||
DomainError::internal_error(
|
||||
"AuthInit",
|
||||
format!(
|
||||
"Authentication is enabled but auth services failed: {}. \
|
||||
Refusing to start without authentication.",
|
||||
e
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::info!("Authentication services initialized successfully");
|
||||
auth_services = Some(services);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -72,8 +72,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Some(Arc::new(pool))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to initialize database pool: {}", e);
|
||||
None
|
||||
// SECURITY: fail-closed. If auth is required but the database
|
||||
// is unreachable, the server MUST NOT start in public mode.
|
||||
panic!(
|
||||
"FATAL: enable_auth=true but database connection failed: {}. \
|
||||
Refusing to start without authentication.",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -134,7 +139,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
// Apply auth middleware to protected API routes when auth is enabled
|
||||
if config.features.enable_auth && app_state.auth_service.is_some() {
|
||||
if config.features.enable_auth {
|
||||
// SECURITY: if auth is required, auth_service MUST be present at this
|
||||
// point. The earlier guards in di.rs and main.rs guarantee this, but
|
||||
// add a defensive check so a future refactor cannot silently degrade.
|
||||
assert!(
|
||||
app_state.auth_service.is_some(),
|
||||
"FATAL: enable_auth=true but auth_service is None. \
|
||||
This should have been caught during initialization."
|
||||
);
|
||||
}
|
||||
if config.features.enable_auth {
|
||||
use interfaces::api::handlers::auth_handler::auth_routes;
|
||||
use oxicloud::interfaces::middleware::auth::auth_middleware;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user