Security: - Use constant-time password comparison (subtle crate) to prevent timing attacks - Replace permissive CORS with configurable origin-restricted CORS - Add TLS warning when password auth is used without HTTPS Bug fixes: - Convert MetaPlugin panics to anyhow::Result (get_meta_plugin, outputs_mut, options_mut) - Replace item.id.unwrap() with proper error handling across 15 call sites - Fix panic on unknown column type in list mode - Fix conflicting PIPESIZE constant (was 8192 vs 65536, now unified to 8192) - Add 256MB filter chain buffer limit to prevent OOM - Gracefully skip unregistered plugins instead of panicking Dead code removal: - Delete unused filter parser files (filter_parser.rs, filter.pest, parser/ module) - ~260 lines of dead PEG parser code removed Code consolidation: - Add is_content_binary_from_metadata() helper (was duplicated in 4 places) - Simplify save_item_raw() to delegate to save_item_raw_streaming() (~90 lines removed) Incomplete features: - Populate filter_plugins in status output from global registry - Add FallbackMagicFileMetaPlugin (was referenced but never implemented) - Document init_plugins() as intentional no-op Infrastructure: - Add Dockerfile (static musl binary on scratch, 4.8MB) - Add .dockerignore - Add cors_origin to ServerConfig and config.rs
235 lines
8.2 KiB
Rust
235 lines
8.2 KiB
Rust
use crate::config::Settings;
|
|
use crate::meta_plugin::{MetaPlugin, MetaPluginType};
|
|
use crate::modes::common::settings_meta_plugin_types;
|
|
use clap::Command;
|
|
use log::debug;
|
|
use rusqlite::Connection;
|
|
use std::collections::HashMap;
|
|
|
|
pub struct MetaService;
|
|
|
|
impl MetaService {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
|
|
pub fn get_plugins(&self, cmd: &mut Command, settings: &Settings) -> Vec<Box<dyn MetaPlugin>> {
|
|
debug!("META_SERVICE: get_plugins called");
|
|
let meta_plugin_types: Vec<MetaPluginType> = settings_meta_plugin_types(cmd, settings);
|
|
debug!("META_SERVICE: Meta plugin types from settings: {meta_plugin_types:?}");
|
|
|
|
// Create plugins with their configuration
|
|
let meta_plugins: Vec<Box<dyn MetaPlugin>> = meta_plugin_types
|
|
.iter()
|
|
.filter_map(|meta_plugin_type| {
|
|
debug!("META_SERVICE: Creating plugin: {meta_plugin_type:?}");
|
|
|
|
// Get the plugin name using strum's Display implementation
|
|
let plugin_name = meta_plugin_type.to_string();
|
|
|
|
// Get options and outputs from settings
|
|
let (options, outputs) = if let Some(meta_plugin_configs) = &settings.meta_plugins {
|
|
if let Some(config) = meta_plugin_configs.iter().find(|c| c.name == plugin_name)
|
|
{
|
|
// Convert options and outputs to the appropriate types
|
|
let options: std::collections::HashMap<String, serde_yaml::Value> = config
|
|
.options
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.clone()))
|
|
.collect();
|
|
|
|
let outputs: std::collections::HashMap<String, serde_yaml::Value> = config
|
|
.outputs
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), serde_yaml::Value::String(v.clone())))
|
|
.collect();
|
|
|
|
(Some(options), Some(outputs))
|
|
} else {
|
|
(None, None)
|
|
}
|
|
} else {
|
|
(None, None)
|
|
};
|
|
|
|
match crate::meta_plugin::get_meta_plugin(meta_plugin_type.clone(), options, outputs) {
|
|
Ok(plugin) => Some(plugin),
|
|
Err(e) => {
|
|
log::warn!("META_SERVICE: Failed to create plugin {meta_plugin_type:?}: {e}, skipping");
|
|
None
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
meta_plugins
|
|
}
|
|
|
|
pub fn initialize_plugins(
|
|
&self,
|
|
plugins: &mut [Box<dyn MetaPlugin>],
|
|
conn: &Connection,
|
|
item_id: i64,
|
|
) {
|
|
// Check for duplicate output names before initializing plugins
|
|
let mut output_names: std::collections::HashMap<String, Vec<String>> =
|
|
std::collections::HashMap::new();
|
|
|
|
for plugin in plugins.iter() {
|
|
let plugin_name = plugin.meta_type().to_string();
|
|
// For each plugin, collect all the output names it might write to
|
|
for (internal_name, output_config) in plugin.outputs() {
|
|
let output_name = match output_config {
|
|
serde_yaml::Value::String(remapped_name) => remapped_name.clone(),
|
|
serde_yaml::Value::Bool(true) => internal_name.clone(),
|
|
serde_yaml::Value::Bool(false) => continue, // This output is disabled
|
|
_ => internal_name.clone(), // Default to internal name for other types
|
|
};
|
|
|
|
// Only track outputs that will actually be written
|
|
if !matches!(output_config, serde_yaml::Value::Bool(false)) {
|
|
output_names
|
|
.entry(output_name)
|
|
.or_default()
|
|
.push(plugin_name.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print warnings for duplicate output names
|
|
for (output_name, plugin_names) in &output_names {
|
|
if plugin_names.len() > 1 {
|
|
log::warn!(
|
|
"META_SERVICE: Output name '{}' is provided by multiple plugins: {}",
|
|
output_name,
|
|
plugin_names.join(", ")
|
|
);
|
|
}
|
|
}
|
|
|
|
for meta_plugin in plugins.iter_mut() {
|
|
let response = meta_plugin.initialize();
|
|
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response);
|
|
}
|
|
}
|
|
|
|
pub fn process_chunk(
|
|
&self,
|
|
plugins: &mut [Box<dyn MetaPlugin>],
|
|
chunk: &[u8],
|
|
conn: &Connection,
|
|
item_id: i64,
|
|
) {
|
|
for meta_plugin in plugins.iter_mut() {
|
|
// Skip plugins that are already finalized
|
|
if meta_plugin.is_finalized() {
|
|
continue;
|
|
}
|
|
|
|
let response = meta_plugin.update(chunk);
|
|
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response.clone());
|
|
|
|
// Set finalized flag if response indicates finalization
|
|
if response.is_finalized {
|
|
meta_plugin.set_finalized(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn finalize_plugins(
|
|
&self,
|
|
plugins: &mut [Box<dyn MetaPlugin>],
|
|
conn: &Connection,
|
|
item_id: i64,
|
|
) {
|
|
for meta_plugin in plugins.iter_mut() {
|
|
// Skip plugins that are already finalized
|
|
if meta_plugin.is_finalized() {
|
|
continue;
|
|
}
|
|
|
|
let response = meta_plugin.finalize();
|
|
self.process_plugin_response(conn, item_id, &mut **meta_plugin, response.clone());
|
|
|
|
// Set finalized flag if response indicates finalization
|
|
if response.is_finalized {
|
|
meta_plugin.set_finalized(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Internal helper to process a meta plugin response and store metadata.
|
|
///
|
|
/// Iterates over the metadata entries in the response and stores each in the database
|
|
/// using `store_meta`. Logs warnings if storage fails.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `conn` - Database connection.
|
|
/// * `item_id` - Item ID to associate with the metadata.
|
|
/// * `_plugin` - Reference to the plugin (unused).
|
|
/// * `response` - The plugin response containing metadata.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Logs warnings for individual storage failures but does not return errors.
|
|
fn process_plugin_response(
|
|
&self,
|
|
conn: &Connection,
|
|
item_id: i64,
|
|
_plugin: &mut dyn MetaPlugin,
|
|
response: crate::meta_plugin::MetaPluginResponse,
|
|
) {
|
|
for meta_data in response.metadata {
|
|
// The metadata has already been processed by the plugin, so we can use it directly
|
|
// Save to database
|
|
let db_meta = crate::db::Meta {
|
|
id: item_id,
|
|
name: meta_data.name,
|
|
value: meta_data.value,
|
|
};
|
|
if let Err(e) = crate::db::store_meta(conn, db_meta) {
|
|
log::warn!("META_SERVICE: Failed to store metadata: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Collects initial metadata from environment variables and hostname.
|
|
///
|
|
/// Gathers metadata from `KEEP_META_*` environment variables and adds hostname
|
|
/// if not already present.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `HashMap` of initial metadata key-value pairs.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use keep::services::MetaService;
|
|
/// let service = MetaService::new();
|
|
/// let initial_meta = service.collect_initial_meta();
|
|
/// ```
|
|
pub fn collect_initial_meta(&self) -> HashMap<String, String> {
|
|
let mut item_meta: HashMap<String, String> = crate::modes::common::get_meta_from_env();
|
|
|
|
if let Ok(hostname) = gethostname::gethostname().into_string()
|
|
&& !item_meta.contains_key("hostname")
|
|
{
|
|
item_meta.insert("hostname".to_string(), hostname);
|
|
}
|
|
item_meta
|
|
}
|
|
}
|
|
|
|
impl Default for MetaService {
|
|
/// Provides a default `MetaService` instance.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `MetaService` via `new()`.
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|