import os from typing import List, Dict try: from dotenv import load_dotenv load_dotenv() except ImportError: pass def get_bool_env(key: str, default: bool = False) -> bool: return os.getenv(key, str(default)).lower() in ('true', '1', 'yes') def get_int_env(key: str, default: int) -> int: try: return int(os.getenv(key, str(default))) except ValueError: return default def _is_docker(): return os.path.exists('/.dockerenv') or os.path.exists('/run/.containerenv') IS_DOCKER = _is_docker() # ============================================================ # VENDORS CONFIGURATION # ============================================================ VENDORS: List[Dict[str, any]] = [ { 'code': 'microsoft', 'name': 'Microsoft', 'cpe_vendor': 'microsoft', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'microsoft', 'windows', 'office', 'azure', 'exchange', 'sharepoint', 'ms-', 'msft', 'outlook', 'teams', 'edge', 'internet explorer', 'ie', 'sql server', 'visual studio', 'dotnet', '.net', 'iis', 'hyper-v', 'active directory', 'powershell', 'windows server', 'defender', 'onedrive', 'dynamics', 'skype', 'surface', 'xbox' ], 'icon': 'fa-windows' }, { 'code': 'apple', 'name': 'Apple', 'cpe_vendor': 'apple', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'apple', 'macos', 'ios', 'ipados', 'safari', 'webkit', 'iphone', 'ipad', 'mac os', 'watchos', 'tvos', 'xcode', 'swift', 'darwin', 'core foundation' ], 'icon': 'fa-apple' }, { 'code': 'fortinet', 'name': 'Fortinet', 'cpe_vendor': 'fortinet', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'fortinet', 'fortigate', 'fortios', 'fortianalyzer', 'fortimanager', 'fortiweb', 'fortimail', 'fortisandbox', 'forticlient', 'fortiadc', 'fortiap', 'fortiswitch', 'fortiwan', 'fortiddos', 'fortiextender' ], 'icon': 'fa-shield-halved' }, { 'code': 'cisco', 'name': 'Cisco', 'cpe_vendor': 'cisco', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'cisco', 'ios', 'nx-os', 'asa', 'webex', 'firepower', 'ios xe', 'ios xr', 'nexus', 'catalyst', 'meraki', 'duo', 'umbrella', 'anyconnect', 'jabber', 'telepresence' ], 'icon': 'fa-network-wired' }, { 'code': 'oracle', 'name': 'Oracle', 'cpe_vendor': 'oracle', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'oracle', 'java', 'jdk', 'jre', 'mysql', 'weblogic', 'solaris', 'virtualbox', 'glassfish', 'peoplesoft', 'siebel', 'fusion', 'coherence', 'primavera' ], 'icon': 'fa-database' }, { 'code': 'google', 'name': 'Google', 'cpe_vendor': 'google', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'google', 'chrome', 'android', 'chromium', 'chromeos', 'pixel', 'nest', 'workspace', 'cloud platform', 'gcp', 'firebase', 'tensorflow', 'kubernetes', 'golang' ], 'icon': 'fa-google' }, { 'code': 'linux', 'name': 'Linux Kernel', 'cpe_vendor': 'linux', 'cpe_product': 'linux_kernel', 'use_cpe': True, 'strict_matching': True, 'require_cvss': True, 'keywords': [ 'in the linux kernel', 'linux kernel vulnerability' ], 'exclude_keywords': [ 'android', 'solaredge', 'solar edge', 'inverter', 'router', 'camera', 'nas device', 'synology', 'qnap', 'netgear', 'tp-link', 'asus router', 'd-link', 'linksys', 'firmware update', 'embedded system', 'iot', 'smart tv', 'television', 'printer' ], 'icon': 'fa-linux' }, { 'code': 'vmware', 'name': 'VMware', 'cpe_vendor': 'vmware', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'vmware', 'vsphere', 'esxi', 'vcenter', 'workstation', 'fusion', 'horizon', 'nsx', 'vsan', 'vrealize', 'tanzu', 'aria', 'carbon black', 'workspace one' ], 'icon': 'fa-server' }, { 'code': 'paloalto', 'name': 'Palo Alto Networks', 'cpe_vendor': 'paloaltonetworks', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'palo alto', 'pan-os', 'panorama', 'globalprotect', 'palo alto networks', 'wildfire', 'cortex', 'prisma', 'expedition', 'traps' ], 'icon': 'fa-fire' }, { 'code': 'mikrotik', 'name': 'MikroTik', 'cpe_vendor': 'mikrotik', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'mikrotik', 'routeros', 'routerboard', 'winbox', 'capsman', 'the dude', 'user manager', 'swos', 'rb', 'crs', 'ccr', 'hap', 'hex', 'groove' ], 'icon': 'fa-router' }, { 'code': 'proxmox', 'name': 'Proxmox', 'cpe_vendor': 'proxmox', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'proxmox', 'proxmox ve', 'proxmox virtual environment', 'pve', 'proxmox backup server', 'pbs', 'qemu-server', 'pve-manager', 'corosync', 'ceph proxmox' ], 'icon': 'fa-server' }, { 'code': 'openssl', 'name': 'OpenSSL', 'cpe_vendor': 'openssl', 'cpe_product': 'openssl', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'openssl', 'openssl project', 'libssl', 'libcrypto', 'openssl library', 'tls implementation', 'ssl library' ], 'icon': 'fa-lock' }, { 'code': 'php', 'name': 'PHP', 'cpe_vendor': 'php', 'cpe_product': 'php', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'php', 'php group', 'php language', 'php interpreter', 'php-fpm', 'php core', 'zend engine', 'php runtime' ], 'icon': 'fa-code' }, { 'code': 'wordpress', 'name': 'WordPress', 'cpe_vendor': 'wordpress', 'cpe_product': 'wordpress', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'wordpress', 'wordpress core', 'wp-admin', 'wp-content', 'wordpress cms', 'automattic', 'woocommerce core', 'wordpress multisite', 'gutenberg' ], 'exclude_keywords': [ 'plugin', 'theme', 'elementor', 'yoast', 'jetpack', 'contact form 7', 'akismet', 'wordfence' ], 'icon': 'fa-wordpress' }, { 'code': 'f5', 'name': 'F5 Networks', 'cpe_vendor': 'f5', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'f5 networks', 'big-ip', 'bigip', 'f5 big-ip', 'tmos', 'nginx plus', 'f5 nginx', 'traffix', 'nginx controller', 'nginx ingress', 'f5os', 'icontrol', 'asm', 'afm', 'apm' ], 'icon': 'fa-network-wired' }, { 'code': 'nginx', 'name': 'NGINX (OSS)', 'cpe_vendor': 'f5', 'cpe_product': 'nginx', 'use_cpe': True, 'require_cvss': True, 'keywords': [ 'nginx', 'nginx web server', 'nginx http server', 'nginx open source', 'nginx reverse proxy', 'nginx inc', 'nginx software' ], 'exclude_keywords': [ 'nginx plus', 'nginx controller', 'f5 nginx' ], 'icon': 'fa-server' }, ] # ============================================================ # API SOURCES CONFIGURATION # ============================================================ NVD_API_URL = os.getenv('NVD_API_URL', 'https://services.nvd.nist.gov/rest/json/cves/2.0') NVD_API_KEY = os.getenv('NVD_API_KEY', '') NVD_RATE_LIMIT = get_int_env('NVD_RATE_LIMIT', 5) NVD_TIMEOUT = get_int_env('NVD_TIMEOUT', 30) GITHUB_API_URL = os.getenv('GITHUB_API_URL', 'https://api.github.com/advisories') GITHUB_TOKEN = os.getenv('GITHUB_TOKEN', '') GITHUB_RATE_LIMIT = get_int_env('GITHUB_RATE_LIMIT', 60) GITHUB_TIMEOUT = get_int_env('GITHUB_TIMEOUT', 15) CVE_SOURCES = { 'nvd': { 'url': NVD_API_URL, 'rate_limit': NVD_RATE_LIMIT, 'timeout': NVD_TIMEOUT }, 'github': { 'url': GITHUB_API_URL, 'rate_limit': GITHUB_RATE_LIMIT, 'timeout': GITHUB_TIMEOUT } } # ============================================================ # DATABASE CONFIGURATION # ============================================================ if IS_DOCKER: DEFAULT_DB_PATH = '/app/cve_db/cve_cache.db' else: DEFAULT_DB_PATH = './cve_db/cve_cache.db' DATABASE_PATH = os.getenv('DATABASE_PATH', DEFAULT_DB_PATH) DATABASE_WAL_MODE = get_bool_env('DATABASE_WAL_MODE', True) DATABASE_CACHE_SIZE = get_int_env('DATABASE_CACHE_SIZE', 64000) # ============================================================ # CACHE SETTINGS # ============================================================ CACHE_HOURS = get_int_env('CACHE_HOURS', 24) UPDATE_INTERVAL_HOURS = get_int_env('UPDATE_INTERVAL_HOURS', 6) INITIAL_LOOKBACK_DAYS = get_int_env('INITIAL_LOOKBACK_DAYS', 365) # ============================================================ # APPLICATION SETTINGS # ============================================================ DEBUG = get_bool_env('DEBUG', False) HOST = os.getenv('HOST', '0.0.0.0') PORT = get_int_env('PORT', 5000) WORKERS = get_int_env('WORKERS', 4) WORKER_TIMEOUT = get_int_env('WORKER_TIMEOUT', 120) APP_NAME = os.getenv('APP_NAME', 'CVE Monitor') APP_VERSION = os.getenv('APP_VERSION', '1.0.0') # ============================================================ # SECURITY SETTINGS # ============================================================ ENABLE_SECURITY_HEADERS = get_bool_env('ENABLE_SECURITY_HEADERS', not IS_DOCKER) ENABLE_RATE_LIMITING = get_bool_env('ENABLE_RATE_LIMITING', True) ENABLE_COMPRESSION = get_bool_env('ENABLE_COMPRESSION', True) ENABLE_ETAG = get_bool_env('ENABLE_ETAG', True) CSP_DEFAULT_SRC = os.getenv('CSP_DEFAULT_SRC', "'self'") CSP_SCRIPT_SRC = os.getenv('CSP_SCRIPT_SRC', "'self' 'unsafe-inline' cdn.jsdelivr.net cdnjs.cloudflare.com") CSP_STYLE_SRC = os.getenv('CSP_STYLE_SRC', "'self' 'unsafe-inline' cdn.jsdelivr.net cdnjs.cloudflare.com") CSP_FONT_SRC = os.getenv('CSP_FONT_SRC', "'self' cdnjs.cloudflare.com") CSP_IMG_SRC = os.getenv('CSP_IMG_SRC', "'self' data:") CSP_CONNECT_SRC = os.getenv('CSP_CONNECT_SRC', "'self' cdn.jsdelivr.net") SECURITY_HEADERS = { 'Content-Security-Policy': f"default-src {CSP_DEFAULT_SRC}; script-src {CSP_SCRIPT_SRC}; style-src {CSP_STYLE_SRC}; font-src {CSP_FONT_SRC}; img-src {CSP_IMG_SRC}; connect-src {CSP_CONNECT_SRC};", 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': os.getenv('X_FRAME_OPTIONS', 'DENY'), 'X-XSS-Protection': '1; mode=block', 'Strict-Transport-Security': f"max-age={get_int_env('HSTS_MAX_AGE', 31536000)}; includeSubDomains" } # ============================================================ # DISCORD BOT CONFIGURATION # ============================================================ ENABLE_DISCORD_BOT = get_bool_env('ENABLE_DISCORD_BOT', False) DISCORD_BOT_TOKEN = os.getenv('DISCORD_BOT_TOKEN', '') DISCORD_CHANNEL_ID = os.getenv('DISCORD_CHANNEL_ID', '') DISCORD_CHECK_INTERVAL_MINUTES = get_int_env('DISCORD_CHECK_INTERVAL_MINUTES', 60) DISCORD_NOTIFY_CRITICAL = get_bool_env('DISCORD_NOTIFY_CRITICAL', True) DISCORD_NOTIFY_HIGH = get_bool_env('DISCORD_NOTIFY_HIGH', True) DISCORD_MIN_CVSS = float(os.getenv('DISCORD_MIN_CVSS', '7.0')) DISCORD_BOT_ENABLED = ENABLE_DISCORD_BOT DISCORD_UPDATE_INTERVAL = DISCORD_CHECK_INTERVAL_MINUTES * 60 # Convert to seconds DISCORD_CVE_LOOKBACK_HOURS = DISCORD_CHECK_INTERVAL_MINUTES / 60 DISCORD_MIN_SEVERITY = os.getenv('DISCORD_MIN_SEVERITY', 'HIGH') # ============================================================ # VALIDATION # ============================================================ def validate_config(): """Walidacja krytycznych ustawień""" errors = [] if ENABLE_DISCORD_BOT: if not DISCORD_BOT_TOKEN: errors.append("ENABLE_DISCORD_BOT=True but DISCORD_BOT_TOKEN is empty") if not DISCORD_CHANNEL_ID: errors.append("ENABLE_DISCORD_BOT=True but DISCORD_CHANNEL_ID is empty") try: int(DISCORD_CHANNEL_ID) except ValueError: errors.append("DISCORD_CHANNEL_ID must be a valid integer") if CACHE_HOURS < 1: errors.append("CACHE_HOURS must be at least 1") if UPDATE_INTERVAL_HOURS < 1: errors.append("UPDATE_INTERVAL_HOURS must be at least 1") if WORKERS < 1: errors.append("WORKERS must be at least 1") if errors: raise ValueError("Configuration errors:\n" + "\n".join(errors)) # Run validation only if Discord bot enabled if ENABLE_DISCORD_BOT: validate_config() # ============================================================ # PAGINATION SETTINGS # ============================================================ ITEMS_PER_PAGE = get_int_env('ITEMS_PER_PAGE', 50) MAX_ITEMS_PER_PAGE = get_int_env('MAX_ITEMS_PER_PAGE', 200) # ============================================================ # EXPORT SETTINGS # ============================================================ EXPORT_FORMATS = os.getenv('EXPORT_FORMATS', 'json,csv').split(',') EXPORT_MAX_ITEMS = get_int_env('EXPORT_MAX_ITEMS', 1000) # ============================================================ # LOGGING CONFIGURATION # ============================================================ LOG_LEVEL = os.getenv('LOG_LEVEL', 'DEBUG' if not IS_DOCKER else 'INFO') LOG_FORMAT = os.getenv('LOG_FORMAT', '%(asctime)s - %(name)s - %(levelname)s - %(message)s') LOG_FILE = os.getenv('LOG_FILE', '') LOG_MAX_BYTES = get_int_env('LOG_MAX_BYTES', 10485760) LOG_BACKUP_COUNT = get_int_env('LOG_BACKUP_COUNT', 5) # ============================================================ # SEVERITY CONFIGURATION # ============================================================ SEVERITY_LEVELS = { 'CRITICAL': { 'color': 'danger', 'icon': 'fa-skull-crossbones', 'min_cvss': 9.0, 'badge_class': 'badge-critical' }, 'HIGH': { 'color': 'warning', 'icon': 'fa-exclamation-triangle', 'min_cvss': 7.0, 'badge_class': 'badge-high' }, 'MEDIUM': { 'color': 'info', 'icon': 'fa-exclamation-circle', 'min_cvss': 4.0, 'badge_class': 'badge-medium' }, 'LOW': { 'color': 'secondary', 'icon': 'fa-info-circle', 'min_cvss': 0.0, 'badge_class': 'badge-low' } } # ============================================================ # CDN CONFIGURATION # ============================================================ BOOTSTRAP_CSS_CDN = os.getenv('BOOTSTRAP_CSS_CDN', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css') BOOTSTRAP_JS_CDN = os.getenv('BOOTSTRAP_JS_CDN', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js') FONTAWESOME_CDN = os.getenv('FONTAWESOME_CDN', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css') CHARTJS_CDN = os.getenv('CHARTJS_CDN', 'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js') # ============================================================ # FEATURE FLAGS # ============================================================ ENABLE_AUTO_UPDATE = get_bool_env('ENABLE_AUTO_UPDATE', True) ENABLE_CHARTS = get_bool_env('ENABLE_CHARTS', True) ENABLE_SEARCH = get_bool_env('ENABLE_SEARCH', True) ENABLE_EXPORT = get_bool_env('ENABLE_EXPORT', True) ENABLE_DARK_MODE = get_bool_env('ENABLE_DARK_MODE', True) # ============================================================ # VALIDATION # ============================================================ def validate_config(): errors = [] if DISCORD_BOT_ENABLED: if not DISCORD_BOT_TOKEN: errors.append("DISCORD_BOT_ENABLED=True but DISCORD_BOT_TOKEN is empty") if not DISCORD_CHANNEL_ID: errors.append("DISCORD_BOT_ENABLED=True but DISCORD_CHANNEL_ID is empty") if CACHE_HOURS < 1: errors.append("CACHE_HOURS must be at least 1") if UPDATE_INTERVAL_HOURS < 1: errors.append("UPDATE_INTERVAL_HOURS must be at least 1") if WORKERS < 1: errors.append("WORKERS must be at least 1") if errors: raise ValueError("Configuration errors:\n" + "\n".join(errors)) # Run validation validate_config()