Files
cve_monitor/config.py
Mateusz Gruszczyński bc1b4279de first commit
2026-02-13 12:42:53 +01:00

479 lines
17 KiB
Python

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()