Enhance error handling and security measures in data collection services
- Implemented `_sanitize_error` method in `DataCollectionService` and `CollectorManager` to prevent leaking internal error details. - Improved error handling across various methods by catching specific exceptions and logging sanitized messages with `exc_info=True`. - Added file permission validation in `ServiceConfig` to ensure secure configuration file handling, including detailed logging for permission issues. - Refactored logging practices to enhance clarity and maintainability, ensuring consistent error reporting. These changes significantly bolster the security and robustness of the data collection services, aligning with project standards for error handling and maintainability.
This commit is contained in:
@@ -73,9 +73,25 @@ class ServiceConfig:
|
||||
|
||||
return validated_config
|
||||
|
||||
except Exception as e:
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError) as e:
|
||||
# Handle file system related errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ Failed to load configuration: {e}", exc_info=True)
|
||||
self.logger.error(f"❌ File system error loading configuration: {e}", exc_info=True)
|
||||
raise
|
||||
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
# Handle file parsing errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ Configuration file parsing error: {e}", exc_info=True)
|
||||
raise ValueError(f"Invalid configuration file format: {e}")
|
||||
except (KeyError, ValueError, TypeError) as e:
|
||||
# Handle configuration validation errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ Configuration validation error: {e}", exc_info=True)
|
||||
raise
|
||||
except Exception as e:
|
||||
# Catch any other unexpected errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ Unexpected error loading configuration: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def _validate_file_permissions(self, config_file: Path) -> None:
|
||||
@@ -87,24 +103,84 @@ class ServiceConfig:
|
||||
|
||||
Raises:
|
||||
PermissionError: If file permissions are too permissive
|
||||
ValueError: If file validation fails
|
||||
"""
|
||||
try:
|
||||
file_stat = config_file.stat()
|
||||
file_mode = file_stat.st_mode
|
||||
|
||||
# List of security violations
|
||||
violations = []
|
||||
|
||||
# Check if file is readable by others (security risk)
|
||||
if file_mode & stat.S_IROTH:
|
||||
violations.append("readable by others")
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Configuration file {config_file} is readable by others")
|
||||
|
||||
# Check if file is writable by others (security risk)
|
||||
if file_mode & stat.S_IWOTH:
|
||||
violations.append("writable by others")
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Configuration file {config_file} is writable by others")
|
||||
|
||||
# Check if file is writable by group (security risk)
|
||||
if file_mode & stat.S_IWGRP:
|
||||
violations.append("writable by group")
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Configuration file {config_file} is writable by group")
|
||||
|
||||
# Check if file is readable by group (potential security risk)
|
||||
if file_mode & stat.S_IRGRP:
|
||||
violations.append("readable by group")
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Configuration file {config_file} is readable by group")
|
||||
|
||||
# Check if file is executable (unnecessary for config files)
|
||||
if file_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
|
||||
violations.append("executable")
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Configuration file {config_file} has execute permissions")
|
||||
|
||||
# Enforce security policy - raise exception if critical violations found
|
||||
critical_violations = [v for v in violations if "writable by" in v or "readable by others" in v]
|
||||
if critical_violations:
|
||||
error_msg = f"Configuration file {config_file} has insecure permissions: {', '.join(critical_violations)}. " \
|
||||
f"File should only be readable/writable by owner (mode 600 recommended)."
|
||||
|
||||
if self.logger:
|
||||
self.logger.error(f"🔒 Security violation: {error_msg}", exc_info=True)
|
||||
|
||||
# On Windows, permission checks might not work as expected, so we log but don't fail
|
||||
if os.name == 'nt': # Windows
|
||||
if self.logger:
|
||||
self.logger.warning("⚠️ On Windows, file permission validation is limited. Please ensure config files are properly secured.")
|
||||
else:
|
||||
# On Unix-like systems, enforce strict permissions
|
||||
raise PermissionError(error_msg)
|
||||
|
||||
# Validate file ownership (only on Unix-like systems)
|
||||
if os.name != 'nt':
|
||||
current_uid = os.getuid() if hasattr(os, 'getuid') else None
|
||||
file_uid = file_stat.st_uid
|
||||
|
||||
if current_uid is not None and file_uid != current_uid:
|
||||
warning_msg = f"Configuration file {config_file} is owned by different user (UID: {file_uid}, current: {current_uid})"
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ {warning_msg}")
|
||||
|
||||
# Log successful validation
|
||||
if not violations and self.logger:
|
||||
self.logger.debug(f"✅ Configuration file {config_file} has secure permissions")
|
||||
|
||||
except PermissionError:
|
||||
# Re-raise permission errors
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"Could not validate file permissions for {config_file}: {e}"
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Could not validate file permissions: {e}")
|
||||
self.logger.error(f"❌ {error_msg}", exc_info=True)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
def _validate_config_schema(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -194,15 +270,78 @@ class ServiceConfig:
|
||||
json.dump(default_config, f, indent=2)
|
||||
|
||||
# Set secure file permissions (owner read/write only)
|
||||
try:
|
||||
os.chmod(config_file, stat.S_IRUSR | stat.S_IWUSR)
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Could not set secure file permissions: {e}")
|
||||
self._set_secure_permissions(config_file)
|
||||
|
||||
if self.logger:
|
||||
self.logger.info(f"📄 Created default configuration: {config_file}")
|
||||
|
||||
def _set_secure_permissions(self, config_file: Path) -> None:
|
||||
"""
|
||||
Set secure file permissions for configuration file.
|
||||
|
||||
Args:
|
||||
config_file: Path to the configuration file
|
||||
"""
|
||||
try:
|
||||
# Set permissions to owner read/write only (mode 600)
|
||||
secure_mode = stat.S_IRUSR | stat.S_IWUSR
|
||||
os.chmod(config_file, secure_mode)
|
||||
|
||||
if self.logger:
|
||||
self.logger.debug(f"Set secure permissions (600) for {config_file}")
|
||||
|
||||
except (OSError, PermissionError) as e:
|
||||
# Handle file system permission errors
|
||||
if self.logger:
|
||||
self.logger.warning(f"Could not set secure file permissions for {config_file}: {e}")
|
||||
except Exception as e:
|
||||
# Handle any other unexpected errors
|
||||
if self.logger:
|
||||
self.logger.warning(f"Unexpected error setting file permissions for {config_file}: {e}")
|
||||
|
||||
def fix_file_permissions(self, config_file_path: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Fix file permissions for configuration file to be secure.
|
||||
|
||||
Args:
|
||||
config_file_path: Optional specific file path, defaults to instance config_path
|
||||
|
||||
Returns:
|
||||
bool: True if permissions were fixed successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
file_path = Path(config_file_path or self.config_path)
|
||||
|
||||
if not file_path.exists():
|
||||
if self.logger:
|
||||
self.logger.error(f"Configuration file {file_path} does not exist", exc_info=True)
|
||||
return False
|
||||
|
||||
# Set secure permissions
|
||||
self._set_secure_permissions(file_path)
|
||||
|
||||
# Verify the fix worked
|
||||
try:
|
||||
self._validate_file_permissions(file_path)
|
||||
if self.logger:
|
||||
self.logger.info(f"✅ Successfully fixed permissions for {file_path}")
|
||||
return True
|
||||
except (PermissionError, ValueError):
|
||||
if self.logger:
|
||||
self.logger.warning(f"⚠️ Permissions may still be insecure for {file_path}")
|
||||
return False
|
||||
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
# Handle file system related errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ File system error fixing permissions: {e}", exc_info=True)
|
||||
return False
|
||||
except Exception as e:
|
||||
# Handle any other unexpected errors
|
||||
if self.logger:
|
||||
self.logger.error(f"❌ Unexpected error fixing file permissions: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def _get_default_connection_config(self) -> Dict[str, Any]:
|
||||
"""Get default connection configuration."""
|
||||
return {
|
||||
@@ -318,13 +457,18 @@ class ServiceConfig:
|
||||
self.logger.info("Configuration updated in memory")
|
||||
|
||||
def save_config(self) -> None:
|
||||
"""Save current configuration to file."""
|
||||
"""Save current configuration to file with secure permissions."""
|
||||
if self._config is None:
|
||||
raise RuntimeError("Configuration has not been loaded. Call load_config() first.")
|
||||
|
||||
config_file = Path(self.config_path)
|
||||
|
||||
# Save configuration
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(self._config, f, indent=2)
|
||||
|
||||
# Ensure secure permissions after saving
|
||||
self._set_secure_permissions(config_file)
|
||||
|
||||
if self.logger:
|
||||
self.logger.info(f"Configuration saved to {self.config_path}")
|
||||
self.logger.info(f"Configuration saved to {self.config_path} with secure permissions")
|
||||
Reference in New Issue
Block a user