- Add config.py for environment configuration management - Add utils.py with utility functions - Add .env.example for environment variable reference - Add routes_example.py as route reference - Add login.html template for authentication - Update server.py with enhancements - Update all dashboard and log templates - Move documentation to 'explanations and old code' directory - Update database schema
550 lines
14 KiB
Markdown
550 lines
14 KiB
Markdown
# 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
|
|
```
|
|
|