479 lines
17 KiB
Python
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()
|