165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
"""
|
|
Configuration Management for DockMon
|
|
Centralizes all environment-based configuration and settings
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
from typing import List
|
|
|
|
|
|
def setup_logging():
|
|
"""Configure application logging with rotation"""
|
|
from .paths import DATA_DIR
|
|
|
|
# Create logs directory with secure permissions
|
|
log_dir = os.path.join(DATA_DIR, 'logs')
|
|
os.makedirs(log_dir, mode=0o700, exist_ok=True)
|
|
|
|
# Set up root logger
|
|
root_logger = logging.getLogger()
|
|
|
|
# Check if handlers are already configured (prevent duplicate handlers)
|
|
if root_logger.handlers:
|
|
return
|
|
|
|
root_logger.setLevel(logging.INFO)
|
|
|
|
# Console handler for stdout
|
|
console_handler = logging.StreamHandler()
|
|
console_handler.setLevel(logging.INFO)
|
|
console_formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
console_handler.setFormatter(console_formatter)
|
|
|
|
# File handler with rotation for application logs
|
|
# Max 10MB per file, keep 14 backups
|
|
file_handler = RotatingFileHandler(
|
|
os.path.join(log_dir, 'dockmon.log'),
|
|
maxBytes=10*1024*1024, # 10MB
|
|
backupCount=14, # Keep 14 old files
|
|
encoding='utf-8'
|
|
)
|
|
file_handler.setLevel(logging.INFO)
|
|
file_handler.setFormatter(console_formatter)
|
|
|
|
# Add handlers to root logger
|
|
root_logger.addHandler(console_handler)
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
def _is_docker_container_id(hostname: str) -> bool:
|
|
"""Check if hostname looks like a Docker container ID"""
|
|
if len(hostname) == 64 or len(hostname) == 12:
|
|
try:
|
|
int(hostname, 16) # Check if it's hexadecimal
|
|
return True
|
|
except ValueError:
|
|
pass
|
|
return False
|
|
|
|
|
|
def get_cors_origins() -> List[str]:
|
|
"""Get CORS origins from environment or use defaults"""
|
|
# Check for custom origins from environment
|
|
custom_origins = os.getenv('DOCKMON_CORS_ORIGINS')
|
|
if custom_origins:
|
|
return [origin.strip() for origin in custom_origins.split(',')]
|
|
|
|
# Default origins for development and common deployment scenarios
|
|
default_origins = [
|
|
"http://localhost:3000",
|
|
"http://localhost:8080",
|
|
"http://localhost:8081",
|
|
"http://127.0.0.1:3000",
|
|
"http://127.0.0.1:8080",
|
|
"http://127.0.0.1:8081"
|
|
]
|
|
|
|
# Auto-detect common production patterns (but skip Docker container IDs)
|
|
hostname = os.getenv('HOSTNAME', 'localhost')
|
|
if hostname != 'localhost' and not _is_docker_container_id(hostname):
|
|
default_origins.extend([
|
|
f"http://{hostname}:3000",
|
|
f"http://{hostname}:8080",
|
|
f"https://{hostname}:3000",
|
|
f"https://{hostname}:8080"
|
|
])
|
|
|
|
return default_origins
|
|
|
|
|
|
class RateLimitConfig:
|
|
"""Rate limiting configuration from environment variables"""
|
|
|
|
@staticmethod
|
|
def get_limits() -> dict:
|
|
"""Get all rate limiting configuration from environment"""
|
|
return {
|
|
# endpoint_pattern: (requests_per_minute, burst_limit, violation_threshold)
|
|
"default": (
|
|
int(os.getenv('DOCKMON_RATE_LIMIT_DEFAULT', 120)),
|
|
int(os.getenv('DOCKMON_RATE_BURST_DEFAULT', 20)),
|
|
int(os.getenv('DOCKMON_RATE_VIOLATIONS_DEFAULT', 8))
|
|
),
|
|
"auth": (
|
|
int(os.getenv('DOCKMON_RATE_LIMIT_AUTH', 60)),
|
|
int(os.getenv('DOCKMON_RATE_BURST_AUTH', 15)),
|
|
int(os.getenv('DOCKMON_RATE_VIOLATIONS_AUTH', 5))
|
|
),
|
|
"hosts": (
|
|
int(os.getenv('DOCKMON_RATE_LIMIT_HOSTS', 60)),
|
|
int(os.getenv('DOCKMON_RATE_BURST_HOSTS', 15)),
|
|
int(os.getenv('DOCKMON_RATE_VIOLATIONS_HOSTS', 8))
|
|
),
|
|
"containers": (
|
|
int(os.getenv('DOCKMON_RATE_LIMIT_CONTAINERS', 200)),
|
|
int(os.getenv('DOCKMON_RATE_BURST_CONTAINERS', 40)),
|
|
int(os.getenv('DOCKMON_RATE_VIOLATIONS_CONTAINERS', 15))
|
|
),
|
|
"notifications": (
|
|
int(os.getenv('DOCKMON_RATE_LIMIT_NOTIFICATIONS', 30)),
|
|
int(os.getenv('DOCKMON_RATE_BURST_NOTIFICATIONS', 10)),
|
|
int(os.getenv('DOCKMON_RATE_VIOLATIONS_NOTIFICATIONS', 5))
|
|
),
|
|
}
|
|
|
|
|
|
class AppConfig:
|
|
"""Main application configuration"""
|
|
|
|
# Server settings
|
|
HOST = os.getenv('DOCKMON_HOST', '0.0.0.0')
|
|
PORT = int(os.getenv('DOCKMON_PORT', 8080))
|
|
|
|
# Security settings
|
|
CORS_ORIGINS = get_cors_origins()
|
|
|
|
# Import centralized paths
|
|
from .paths import DATABASE_URL as DEFAULT_DATABASE_URL, CREDENTIALS_FILE as DEFAULT_CREDENTIALS_FILE
|
|
|
|
# Database settings
|
|
DATABASE_URL = os.getenv('DOCKMON_DATABASE_URL', DEFAULT_DATABASE_URL)
|
|
|
|
# Logging
|
|
LOG_LEVEL = os.getenv('DOCKMON_LOG_LEVEL', 'INFO')
|
|
|
|
# Authentication
|
|
CREDENTIALS_FILE = os.getenv('DOCKMON_CREDENTIALS_FILE', DEFAULT_CREDENTIALS_FILE)
|
|
SESSION_TIMEOUT_HOURS = int(os.getenv('DOCKMON_SESSION_TIMEOUT_HOURS', 24))
|
|
|
|
# Rate limiting
|
|
RATE_LIMITS = RateLimitConfig.get_limits()
|
|
|
|
@classmethod
|
|
def validate(cls):
|
|
"""Validate configuration"""
|
|
if cls.PORT < 1 or cls.PORT > 65535:
|
|
raise ValueError(f"Invalid port: {cls.PORT}")
|
|
|
|
if cls.SESSION_TIMEOUT_HOURS < 1:
|
|
raise ValueError(f"Session timeout must be at least 1 hour: {cls.SESSION_TIMEOUT_HOURS}")
|
|
|
|
return True |