Files
docker_dev/dockmon/backend/config/settings.py

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