talk2me/config.py
Adolfo Delorenzo 9170198c6c Add comprehensive secrets management system for secure configuration
- Implement encrypted secrets storage with AES-128 encryption
- Add secret rotation capabilities with scheduling
- Implement comprehensive audit logging for all secret operations
- Create centralized configuration management system
- Add CLI tool for interactive secret management
- Integrate secrets with Flask configuration
- Support environment-specific configurations
- Add integrity verification for stored secrets
- Implement secure key derivation with PBKDF2

Features:
- Encrypted storage in .secrets.json
- Master key protection with file permissions
- Automatic secret rotation scheduling
- Audit trail for compliance
- Migration from environment variables
- Flask CLI integration
- Validation and sanitization

Security improvements:
- No more hardcoded secrets in configuration
- Encrypted storage at rest
- Secure key management
- Access control via authentication
- Comprehensive audit logging
- Integrity verification

CLI commands:
- manage_secrets.py init - Initialize secrets
- manage_secrets.py set/get/delete - Manage secrets
- manage_secrets.py rotate - Rotate secrets
- manage_secrets.py audit - View audit logs
- manage_secrets.py verify - Check integrity

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-03 00:24:03 -06:00

198 lines
7.7 KiB
Python

# Configuration management with secrets integration
import os
import logging
from datetime import timedelta
from secrets_manager import get_secret, get_secrets_manager
logger = logging.getLogger(__name__)
class Config:
"""Base configuration with secrets management"""
def __init__(self):
self.secrets_manager = get_secrets_manager()
self._load_config()
def _load_config(self):
"""Load configuration from environment and secrets"""
# Flask configuration
self.SECRET_KEY = self._get_secret('FLASK_SECRET_KEY',
os.environ.get('SECRET_KEY', 'dev-key-change-this'))
# Security
self.SESSION_COOKIE_SECURE = self._get_bool('SESSION_COOKIE_SECURE', True)
self.SESSION_COOKIE_HTTPONLY = True
self.SESSION_COOKIE_SAMESITE = 'Lax'
self.PERMANENT_SESSION_LIFETIME = timedelta(hours=24)
# TTS Configuration
self.TTS_SERVER_URL = os.environ.get('TTS_SERVER_URL', 'http://localhost:5050/v1/audio/speech')
self.TTS_API_KEY = self._get_secret('TTS_API_KEY', os.environ.get('TTS_API_KEY', ''))
# Upload configuration
self.UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', None)
self.MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
# CORS configuration
self.CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
self.ADMIN_CORS_ORIGINS = os.environ.get('ADMIN_CORS_ORIGINS', 'http://localhost:*').split(',')
# Admin configuration
self.ADMIN_TOKEN = self._get_secret('ADMIN_TOKEN',
os.environ.get('ADMIN_TOKEN', 'default-admin-token'))
# Database configuration (for future use)
self.DATABASE_URL = self._get_secret('DATABASE_URL',
os.environ.get('DATABASE_URL', 'sqlite:///talk2me.db'))
# Redis configuration (for future use)
self.REDIS_URL = self._get_secret('REDIS_URL',
os.environ.get('REDIS_URL', 'redis://localhost:6379/0'))
# Whisper configuration
self.WHISPER_MODEL_SIZE = os.environ.get('WHISPER_MODEL_SIZE', 'base')
self.WHISPER_DEVICE = os.environ.get('WHISPER_DEVICE', 'auto')
# Ollama configuration
self.OLLAMA_HOST = os.environ.get('OLLAMA_HOST', 'http://localhost:11434')
self.OLLAMA_MODEL = os.environ.get('OLLAMA_MODEL', 'gemma3:27b')
# Rate limiting configuration
self.RATE_LIMIT_ENABLED = self._get_bool('RATE_LIMIT_ENABLED', True)
self.RATE_LIMIT_STORAGE_URL = self._get_secret('RATE_LIMIT_STORAGE_URL',
os.environ.get('RATE_LIMIT_STORAGE_URL', 'memory://'))
# Logging configuration
self.LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
self.LOG_FILE = os.environ.get('LOG_FILE', 'talk2me.log')
# Feature flags
self.ENABLE_PUSH_NOTIFICATIONS = self._get_bool('ENABLE_PUSH_NOTIFICATIONS', True)
self.ENABLE_OFFLINE_MODE = self._get_bool('ENABLE_OFFLINE_MODE', True)
self.ENABLE_STREAMING = self._get_bool('ENABLE_STREAMING', True)
self.ENABLE_MULTI_SPEAKER = self._get_bool('ENABLE_MULTI_SPEAKER', True)
# Performance tuning
self.WORKER_CONNECTIONS = int(os.environ.get('WORKER_CONNECTIONS', '1000'))
self.WORKER_TIMEOUT = int(os.environ.get('WORKER_TIMEOUT', '120'))
# Validate configuration
self._validate_config()
def _get_secret(self, key: str, default: str = None) -> str:
"""Get secret from secrets manager or environment"""
value = self.secrets_manager.get(key)
if value is None:
value = default
if value is None:
logger.warning(f"Configuration {key} not set")
return value
def _get_bool(self, key: str, default: bool = False) -> bool:
"""Get boolean configuration value"""
value = os.environ.get(key, '').lower()
if value in ('true', '1', 'yes', 'on'):
return True
elif value in ('false', '0', 'no', 'off'):
return False
return default
def _validate_config(self):
"""Validate configuration values"""
# Check required secrets
if not self.SECRET_KEY or self.SECRET_KEY == 'dev-key-change-this':
logger.warning("Using default SECRET_KEY - this is insecure for production!")
if not self.TTS_API_KEY:
logger.warning("TTS_API_KEY not configured - TTS functionality may not work")
if self.ADMIN_TOKEN == 'default-admin-token':
logger.warning("Using default ADMIN_TOKEN - this is insecure for production!")
# Validate URLs
if not self._is_valid_url(self.TTS_SERVER_URL):
logger.error(f"Invalid TTS_SERVER_URL: {self.TTS_SERVER_URL}")
# Check file permissions
if self.UPLOAD_FOLDER and not os.access(self.UPLOAD_FOLDER, os.W_OK):
logger.warning(f"Upload folder {self.UPLOAD_FOLDER} is not writable")
def _is_valid_url(self, url: str) -> bool:
"""Check if URL is valid"""
return url.startswith(('http://', 'https://'))
def to_dict(self) -> dict:
"""Export configuration as dictionary (excluding secrets)"""
config = {}
for key in dir(self):
if key.isupper() and not key.startswith('_'):
value = getattr(self, key)
# Mask sensitive values
if any(sensitive in key for sensitive in ['KEY', 'TOKEN', 'PASSWORD', 'SECRET']):
config[key] = '***MASKED***'
else:
config[key] = value
return config
class DevelopmentConfig(Config):
"""Development configuration"""
def _load_config(self):
super()._load_config()
self.DEBUG = True
self.TESTING = False
self.SESSION_COOKIE_SECURE = False # Allow HTTP in development
class ProductionConfig(Config):
"""Production configuration"""
def _load_config(self):
super()._load_config()
self.DEBUG = False
self.TESTING = False
# Enforce security in production
if not self.SECRET_KEY or self.SECRET_KEY == 'dev-key-change-this':
raise ValueError("SECRET_KEY must be set in production")
if self.ADMIN_TOKEN == 'default-admin-token':
raise ValueError("ADMIN_TOKEN must be changed in production")
class TestingConfig(Config):
"""Testing configuration"""
def _load_config(self):
super()._load_config()
self.DEBUG = True
self.TESTING = True
self.WTF_CSRF_ENABLED = False
self.RATE_LIMIT_ENABLED = False
# Configuration factory
def get_config(env: str = None) -> Config:
"""Get configuration based on environment"""
if env is None:
env = os.environ.get('FLASK_ENV', 'development')
configs = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
config_class = configs.get(env, DevelopmentConfig)
return config_class()
# Convenience function for Flask app
def init_app(app):
"""Initialize Flask app with configuration"""
config = get_config()
# Apply configuration to app
for key in dir(config):
if key.isupper():
app.config[key] = getattr(config, key)
# Store config object
app.app_config = config
logger.info(f"Configuration loaded for environment: {os.environ.get('FLASK_ENV', 'development')}")