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:
Vasily.onl
2025-06-10 13:12:13 +08:00
parent 6ab40414e7
commit c28e4a9aaf
5 changed files with 312 additions and 30 deletions

View File

@@ -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")