# Server Monitorizare - Code Analysis & Improvement Proposals ## Current Architecture Overview The application is a Flask-based device monitoring system with: - SQLite database for logging - REST API endpoints for device communication - Web UI dashboard for visualization - Remote command execution capabilities - Bulk operations support with threading --- ## 🔴 Critical Issues & Improvements ### 1. **Security Vulnerabilities** #### a) No Authentication/Authorization **Issue**: All endpoints are publicly accessible without any authentication ```python @app.route('/logs', methods=['POST']) def log_event(): # No authentication check - anyone can send logs ``` **Impact**: Critical - Anyone can: - Submit fake logs - Execute commands on devices - Reset the database - Access sensitive device information **Proposal**: ```python from flask import session from functools import wraps def require_auth(f): @wraps(f) def decorated(*args, **kwargs): if 'user_id' not in session: return jsonify({"error": "Authentication required"}), 401 return f(*args, **kwargs) return decorated @app.route('/logs', methods=['POST']) @require_auth def log_event(): # Protected endpoint ``` --- #### b) SQL Injection Risk (Minor - Using Parameterized Queries) **Status**: ✅ Good - Already using parameterized queries with `?` placeholders **Recommendation**: Maintain this practice --- #### c) No API Key for Device Communication **Issue**: Devices communicate with server without authentication ```python url = f"http://{device_ip}:80/execute_command" # No API key ``` **Proposal**: Add API key validation ```python API_KEY = os.environ.get('DEVICE_API_KEY', 'default-key') headers = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' } response = requests.post(url, json=payload, headers=headers, timeout=30) ``` --- ### 2. **Code Structure & Maintainability** #### a) No Configuration Management **Issue**: Hardcoded values scattered throughout code ```python DATABASE = 'data/database.db' # Hardcoded port=80 # Hardcoded timeout=30 # Hardcoded ``` **Proposal**: Create a config module ```python # config.py import os from dotenv import load_dotenv load_dotenv() class Config: DATABASE = os.getenv('DATABASE_PATH', 'data/database.db') DEBUG = os.getenv('DEBUG', False) PORT = int(os.getenv('PORT', 80)) REQUEST_TIMEOUT = int(os.getenv('REQUEST_TIMEOUT', 30)) LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') ``` --- #### b) Database Connection Not Optimized **Issue**: Opening new connection for every request - no connection pooling ```python with sqlite3.connect(DATABASE) as conn: # New connection each time cursor = conn.cursor() ``` **Impact**: Performance degradation with many concurrent requests **Proposal**: Use SQLAlchemy with connection pooling ```python from flask_sqlalchemy import SQLAlchemy app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/database.db' db = SQLAlchemy(app) class Log(db.Model): id = db.Column(db.Integer, primary_key=True) hostname = db.Column(db.String(255), nullable=False) device_ip = db.Column(db.String(15), nullable=False) # ... other fields ``` --- #### c) No Logging System **Issue**: Using `print()` for debugging - not production-ready ```python print(f"Database error: {e}") print("Log saved successfully") ``` **Proposal**: Implement proper logging ```python import logging from logging.handlers import RotatingFileHandler logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ RotatingFileHandler('logs/app.log', maxBytes=10485760, backupCount=10), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) # Usage logger.info("Log saved successfully") logger.error(f"Database error: {e}") logger.warning("Missing required fields") ``` --- ### 3. **Error Handling** #### a) Inconsistent Error Responses **Issue**: Errors returned with inconsistent structures ```python return {"error": "Invalid or missing JSON payload"}, 400 return jsonify({"error": f"Database connection failed: {e}"}), 500 ``` **Proposal**: Create a standardized error handler ```python class APIError(Exception): def __init__(self, message, status_code=400): self.message = message self.status_code = status_code @app.errorhandler(APIError) def handle_api_error(e): response = { 'success': False, 'error': e.message, 'timestamp': datetime.now().isoformat() } return jsonify(response), e.status_code # Usage if not hostname: raise APIError("hostname is required", 400) ``` --- #### b) Bare Exception Handling **Issue**: Catching all exceptions without logging details ```python except Exception as e: return {"error": "An unexpected error occurred"}, 500 ``` **Impact**: Difficult to debug issues in production **Proposal**: Specific exception handling with logging ```python except sqlite3.Error as e: logger.error(f"Database error: {e}", exc_info=True) raise APIError("Database operation failed", 500) except requests.exceptions.Timeout: logger.warning(f"Request timeout for device {device_ip}") raise APIError("Device request timeout", 504) except Exception as e: logger.exception("Unexpected error occurred") raise APIError("Internal server error", 500) ``` --- ### 4. **Input Validation** #### a) Minimal Validation **Issue**: Only checking if fields exist, not their format ```python if not hostname or not device_ip or not nume_masa or not log_message: return {"error": "Missing required fields"}, 400 # No validation of format/length ``` **Proposal**: Implement comprehensive validation ```python from marshmallow import Schema, fields, ValidationError class LogSchema(Schema): hostname = fields.Str(required=True, validate=Length(min=1, max=255)) device_ip = fields.IP(required=True) nume_masa = fields.Str(required=True, validate=Length(min=1, max=255)) log_message = fields.Str(required=True, validate=Length(min=1, max=1000)) schema = LogSchema() @app.route('/logs', methods=['POST']) def log_event(): try: data = schema.load(request.json) except ValidationError as err: return jsonify({'errors': err.messages}), 400 ``` --- ### 5. **Threading & Concurrency** #### a) Basic Threading Without Safety **Issue**: Creating unbounded threads for bulk operations ```python for ip in device_ips: thread = threading.Thread(target=execute_on_device, args=(ip,)) threads.append(thread) thread.start() ``` **Impact**: Can exhaust system resources with many requests **Proposal**: Use ThreadPoolExecutor with bounded pool ```python from concurrent.futures import ThreadPoolExecutor, as_completed def execute_command_bulk(): try: data = request.json device_ips = data.get('device_ips', []) command = data.get('command') results = {} max_workers = min(10, len(device_ips)) # Max 10 threads with ThreadPoolExecutor(max_workers=max_workers) as executor: future_to_ip = { executor.submit(execute_command_on_device, ip, command): ip for ip in device_ips } for future in as_completed(future_to_ip): ip = future_to_ip[future] try: results[ip] = future.result() except Exception as e: logger.error(f"Error executing command on {ip}: {e}") results[ip] = {"success": False, "error": str(e)} return jsonify({"results": results}), 200 ``` --- ### 6. **Data Persistence & Backup** #### a) No Database Backup **Issue**: `reset_database()` endpoint can delete all data without backup **Proposal**: Implement automatic backups ```python import shutil from datetime import datetime def backup_database(): """Create a backup of the current database""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') backup_file = f'backups/database_backup_{timestamp}.db' os.makedirs('backups', exist_ok=True) shutil.copy2(DATABASE, backup_file) logger.info(f"Database backup created: {backup_file}") # Keep only last 10 backups backups = sorted(glob.glob('backups/database_backup_*.db')) for backup in backups[:-10]: os.remove(backup) ``` --- ### 7. **Performance Issues** #### a) Inefficient Queries **Issue**: Fetching all results without pagination ```python cursor.execute(''' SELECT * FROM logs LIMIT 60 # Still loads everything into memory ''') logs = cursor.fetchall() ``` **Proposal**: Implement pagination ```python def get_logs_paginated(page=1, per_page=20): offset = (page - 1) * per_page cursor.execute(''' SELECT * FROM logs ORDER BY timestamp DESC LIMIT ? OFFSET ? ''', (per_page, offset)) return cursor.fetchall() ``` --- #### b) No Caching **Issue**: Unique devices query runs on every request ```python @app.route('/unique_devices', methods=['GET']) def unique_devices(): # No caching - database query every time ``` **Proposal**: Add caching layer ```python from flask_caching import Cache cache = Cache(app, config={'CACHE_TYPE': 'simple'}) @app.route('/unique_devices', methods=['GET']) @cache.cached(timeout=300) # Cache for 5 minutes def unique_devices(): # Query only executed once per 5 minutes ``` --- ### 8. **Code Organization** #### a) Monolithic Structure **Issue**: All code in single file (462 lines) - hard to maintain **Proposal**: Split into modular structure ``` server.py (main app) ├── config.py (configuration) ├── models.py (database models) ├── routes/ │ ├── __init__.py │ ├── logs.py (logging endpoints) │ ├── devices.py (device management) │ └── commands.py (command execution) ├── services/ │ ├── __init__.py │ ├── device_service.py │ └── command_service.py ├── utils/ │ ├── __init__.py │ ├── validators.py │ └── decorators.py └── tests/ ├── __init__.py └── test_routes.py ``` --- ### 9. **Missing Features** #### a) Rate Limiting ```python from flask_limiter import Limiter from flask_limiter.util import get_remote_address limiter = Limiter( app=app, key_func=get_remote_address, default_limits=["200 per day", "50 per hour"] ) @app.route('/logs', methods=['POST']) @limiter.limit("10 per minute") def log_event(): # Prevent abuse ``` --- #### b) CORS Configuration ```python from flask_cors import CORS CORS(app, resources={ r"/api/*": { "origins": ["http://localhost:3000"], "methods": ["GET", "POST"], "allow_headers": ["Content-Type", "Authorization"] } }) ``` --- #### c) Health Check Endpoint ```python @app.route('/health', methods=['GET']) def health(): try: with sqlite3.connect(DATABASE) as conn: conn.execute('SELECT 1') return jsonify({"status": "healthy"}), 200 except: return jsonify({"status": "unhealthy"}), 503 ``` --- ### 10. **Testing** #### a) No Unit Tests **Proposal**: Add pytest tests ```python # tests/test_logs.py import pytest from app import app, DATABASE @pytest.fixture def client(): app.config['TESTING'] = True with app.test_client() as client: yield client def test_log_event_success(client): response = client.post('/logs', json={ 'hostname': 'test-host', 'device_ip': '192.168.1.1', 'nume_masa': 'test', 'log_message': 'test message' }) assert response.status_code == 201 def test_log_event_missing_fields(client): response = client.post('/logs', json={ 'hostname': 'test-host' }) assert response.status_code == 400 ``` --- ## 📋 Implementation Priority ### Phase 1 (Critical - Week 1) - [ ] Add authentication/authorization - [ ] Implement proper logging - [ ] Add input validation with Marshmallow - [ ] Create config.py for configuration management ### Phase 2 (High - Week 2) - [ ] Switch to SQLAlchemy with connection pooling - [ ] Add database backups - [ ] Implement pagination for queries - [ ] Add health check endpoint ### Phase 3 (Medium - Week 3) - [ ] Refactor into modular structure - [ ] Add rate limiting - [ ] Implement caching - [ ] Add API documentation (Swagger/OpenAPI) ### Phase 4 (Low - Week 4) - [ ] Add unit tests with pytest - [ ] Add CORS configuration - [ ] Performance optimization - [ ] Docker containerization --- ## 📊 Summary Table | Issue | Severity | Impact | Effort | |-------|----------|--------|--------| | No Authentication | 🔴 Critical | Security breach | Medium | | No Logging | 🔴 Critical | Cannot debug production | Low | | Monolithic Structure | 🟠 High | Unmaintainable | Medium | | No Input Validation | 🟠 High | Data integrity issues | Low | | Basic Threading | 🟠 High | Resource exhaustion | Medium | | No Pagination | 🟡 Medium | Memory issues at scale | Low | | No Tests | 🟡 Medium | Regression risks | High | | No Rate Limiting | 🟡 Medium | Abuse potential | Low | --- ## 🚀 Quick Wins (Easy Improvements) 1. **Add logging** (5 min) - Replace all `print()` statements 2. **Add health check** (5 min) - Simple endpoint 3. **Add rate limiting** (10 min) - Flask-Limiter integration 4. **Add error standardization** (15 min) - Create error handler 5. **Create .env file** (10 min) - Move hardcoded values --- ## 📚 Recommended Dependencies ``` flask==3.0.0 flask-sqlalchemy==3.1.1 flask-cors==4.0.0 flask-limiter==3.5.0 flask-caching==2.1.0 marshmallow==3.20.1 python-dotenv==1.0.0 requests==2.31.0 pytest==7.4.3 gunicorn==21.2.0 ```