From 689e8c00d4e805a09aef5b2c52850d12a7f4ea0c Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Sun, 14 Sep 2025 16:17:26 +0200 Subject: [PATCH] unify config --- .env.example | 34 +++++++++++ app.py | 5 +- config.py | 118 ++++++++++++++++--------------------- core/session_config.py | 110 ++-------------------------------- core/session_manager.py | 6 +- providers/base_provider.py | 2 +- requirements.txt | 3 +- 7 files changed, 100 insertions(+), 178 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e1b66c9 --- /dev/null +++ b/.env.example @@ -0,0 +1,34 @@ +# =============================================== +# DNSRecon Environment Variables +# =============================================== +# Copy this file to .env and fill in your values. + +# --- API Keys --- +# Add your Shodan API key for the Shodan provider to be enabled. +SHODAN_API_KEY= + +# --- Flask & Session Settings --- +# A strong, random secret key is crucial for session security. +FLASK_SECRET_KEY=your-very-secret-and-random-key-here +FLASK_HOST=127.0.0.1 +FLASK_PORT=5000 +FLASK_DEBUG=True +# How long a user's session in the browser lasts (in hours). +FLASK_PERMANENT_SESSION_LIFETIME_HOURS=2 +# How long inactive scanner data is stored in Redis (in minutes). +SESSION_TIMEOUT_MINUTES=60 + + +# --- Application Core Settings --- +# The default number of levels to recurse when scanning. +DEFAULT_RECURSION_DEPTH=2 +# Default timeout for provider API requests in seconds. +DEFAULT_TIMEOUT=30 +# The number of concurrent provider requests to make. +MAX_CONCURRENT_REQUESTS=5 +# The number of results from a provider that triggers the "large entity" grouping. +LARGE_ENTITY_THRESHOLD=100 +# The number of times to retry a target if a provider fails. +MAX_RETRIES_PER_TARGET=3 +# How long cached provider responses are stored (in hours). +CACHE_EXPIRY_HOURS=12 diff --git a/app.py b/app.py index 8553450..b8e9679 100644 --- a/app.py +++ b/app.py @@ -16,8 +16,9 @@ from config import config app = Flask(__name__) -app.config['SECRET_KEY'] = 'dnsrecon-dev-key-change-in-production' -app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2) # 2 hour session lifetime +# Use centralized configuration for Flask settings +app.config['SECRET_KEY'] = config.flask_secret_key +app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=config.flask_permanent_session_lifetime_hours) def get_user_scanner(): """ diff --git a/config.py b/config.py index 57011de..01cd30b 100644 --- a/config.py +++ b/config.py @@ -5,111 +5,93 @@ Handles API key storage, rate limiting, and default settings. import os from typing import Dict, Optional +from dotenv import load_dotenv +# Load environment variables from .env file +load_dotenv() class Config: """Configuration manager for DNSRecon application.""" def __init__(self): """Initialize configuration with default values.""" - self.api_keys: Dict[str, Optional[str]] = { - 'shodan': None - } + self.api_keys: Dict[str, Optional[str]] = {} - # Default settings + # --- General Settings --- self.default_recursion_depth = 2 self.default_timeout = 10 self.max_concurrent_requests = 5 self.large_entity_threshold = 100 self.max_retries_per_target = 3 + self.cache_expiry_hours = 12 - # Rate limiting settings (requests per minute) + # --- Rate Limiting (requests per minute) --- self.rate_limits = { - 'crtsh': 60, # Free service, be respectful - 'shodan': 60, # API dependent - 'dns': 100 # Local DNS queries + 'crtsh': 60, + 'shodan': 60, + 'dns': 100 } - # Provider settings + # --- Provider Settings --- self.enabled_providers = { - 'crtsh': True, # Always enabled (free) - 'dns': True, # Always enabled (free) - 'shodan': False # Requires API key + 'crtsh': True, + 'dns': True, + 'shodan': False } - # Logging configuration + # --- Logging --- self.log_level = 'INFO' self.log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' - # Flask configuration + # --- Flask & Session Settings --- self.flask_host = '127.0.0.1' self.flask_port = 5000 self.flask_debug = True + self.flask_secret_key = 'default-secret-key-change-me' + self.flask_permanent_session_lifetime_hours = 2 + self.session_timeout_minutes = 60 - def set_api_key(self, provider: str, api_key: str) -> bool: - """ - Set API key for a provider. + # Load environment variables to override defaults + self.load_from_env() - Args: - provider: Provider name (shodan, etc) - api_key: API key string - - Returns: - bool: True if key was set successfully - """ - if provider in self.api_keys: - self.api_keys[provider] = api_key - self.enabled_providers[provider] = True if api_key else False - return True - return False + def load_from_env(self): + """Load configuration from environment variables.""" + self.set_api_key('shodan', os.getenv('SHODAN_API_KEY')) + + # Override settings from environment + self.default_recursion_depth = int(os.getenv('DEFAULT_RECURSION_DEPTH', self.default_recursion_depth)) + self.default_timeout = int(os.getenv('DEFAULT_TIMEOUT', self.default_timeout)) + self.max_concurrent_requests = int(os.getenv('MAX_CONCURRENT_REQUESTS', self.max_concurrent_requests)) + self.large_entity_threshold = int(os.getenv('LARGE_ENTITY_THRESHOLD', self.large_entity_threshold)) + self.max_retries_per_target = int(os.getenv('MAX_RETRIES_PER_TARGET', self.max_retries_per_target)) + self.cache_expiry_hours = int(os.getenv('CACHE_EXPIRY_HOURS', self.cache_expiry_hours)) + + # Override Flask and session settings + self.flask_host = os.getenv('FLASK_HOST', self.flask_host) + self.flask_port = int(os.getenv('FLASK_PORT', self.flask_port)) + self.flask_debug = os.getenv('FLASK_DEBUG', str(self.flask_debug)).lower() == 'true' + self.flask_secret_key = os.getenv('FLASK_SECRET_KEY', self.flask_secret_key) + self.flask_permanent_session_lifetime_hours = int(os.getenv('FLASK_PERMANENT_SESSION_LIFETIME_HOURS', self.flask_permanent_session_lifetime_hours)) + self.session_timeout_minutes = int(os.getenv('SESSION_TIMEOUT_MINUTES', self.session_timeout_minutes)) + + def set_api_key(self, provider: str, api_key: Optional[str]) -> bool: + """Set API key for a provider.""" + self.api_keys[provider] = api_key + if api_key: + self.enabled_providers[provider] = True + return True def get_api_key(self, provider: str) -> Optional[str]: - """ - Get API key for a provider. - - Args: - provider: Provider name - - Returns: - API key or None if not set - """ + """Get API key for a provider.""" return self.api_keys.get(provider) def is_provider_enabled(self, provider: str) -> bool: - """ - Check if a provider is enabled. - - Args: - provider: Provider name - - Returns: - bool: True if provider is enabled - """ + """Check if a provider is enabled.""" return self.enabled_providers.get(provider, False) def get_rate_limit(self, provider: str) -> int: - """ - Get rate limit for a provider. - - Args: - provider: Provider name - - Returns: - Rate limit in requests per minute - """ + """Get rate limit for a provider.""" return self.rate_limits.get(provider, 60) - - def load_from_env(self): - """Load configuration from environment variables.""" - if os.getenv('SHODAN_API_KEY'): - self.set_api_key('shodan', os.getenv('SHODAN_API_KEY')) - - # Override default settings from environment - self.default_recursion_depth = int(os.getenv('DEFAULT_RECURSION_DEPTH', '2')) - self.flask_debug = os.getenv('FLASK_DEBUG', 'True').lower() == 'true' - self.default_timeout = 30 - self.max_concurrent_requests = 5 - # Global configuration instance config = Config() \ No newline at end of file diff --git a/core/session_config.py b/core/session_config.py index 3545b14..f8aba3c 100644 --- a/core/session_config.py +++ b/core/session_config.py @@ -3,11 +3,9 @@ Per-session configuration management for DNSRecon. Provides isolated configuration instances for each user session. """ -import os -from typing import Dict, Optional +from config import Config - -class SessionConfig: +class SessionConfig(Config): """ Session-specific configuration that inherits from global config but maintains isolated API keys and provider settings. @@ -15,106 +13,8 @@ class SessionConfig: def __init__(self): """Initialize session config with global defaults.""" - # Copy all attributes from global config - self.api_keys: Dict[str, Optional[str]] = { - 'shodan': None - } - - # Default settings (copied from global config) - self.default_recursion_depth = 2 - self.default_timeout = 30 - self.max_concurrent_requests = 5 - self.large_entity_threshold = 100 - - # Rate limiting settings (per session) - self.rate_limits = { - 'crtsh': 60, - 'shodan': 60, - 'dns': 100 - } - - # Provider settings (per session) - self.enabled_providers = { - 'crtsh': True, - 'dns': True, - 'shodan': False - } - - # Logging configuration - self.log_level = 'INFO' - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' - - # Flask configuration (shared) - self.flask_host = '127.0.0.1' - self.flask_port = 5000 - self.flask_debug = True - - def set_api_key(self, provider: str, api_key: str) -> bool: - """ - Set API key for a provider in this session. - - Args: - provider: Provider name (shodan, etc) - api_key: API key string - - Returns: - bool: True if key was set successfully - """ - if provider in self.api_keys: - self.api_keys[provider] = api_key - self.enabled_providers[provider] = True if api_key else False - return True - return False - - def get_api_key(self, provider: str) -> Optional[str]: - """ - Get API key for a provider in this session. - - Args: - provider: Provider name - - Returns: - API key or None if not set - """ - return self.api_keys.get(provider) - - def is_provider_enabled(self, provider: str) -> bool: - """ - Check if a provider is enabled in this session. - - Args: - provider: Provider name - - Returns: - bool: True if provider is enabled - """ - return self.enabled_providers.get(provider, False) - - def get_rate_limit(self, provider: str) -> int: - """ - Get rate limit for a provider in this session. - - Args: - provider: Provider name - - Returns: - Rate limit in requests per minute - """ - return self.rate_limits.get(provider, 60) - - def load_from_env(self): - """Load configuration from environment variables (only if not already set).""" - if os.getenv('SHODAN_API_KEY') and not self.api_keys['shodan']: - self.set_api_key('shodan', os.getenv('SHODAN_API_KEY')) - - # Override default settings from environment - self.default_recursion_depth = int(os.getenv('DEFAULT_RECURSION_DEPTH', '2')) - self.default_timeout = 30 - self.max_concurrent_requests = 5 + super().__init__() - -def create_session_config() -> SessionConfig: +def create_session_config() -> 'SessionConfig': """Create a new session configuration instance.""" - session_config = SessionConfig() - session_config.load_from_env() - return session_config \ No newline at end of file + return SessionConfig() \ No newline at end of file diff --git a/core/session_manager.py b/core/session_manager.py index 79fa9c7..7631db9 100644 --- a/core/session_manager.py +++ b/core/session_manager.py @@ -8,6 +8,7 @@ import pickle from typing import Dict, Optional, Any, List from core.scanner import Scanner +from config import config # WARNING: Using pickle can be a security risk if the data source is not trusted. # In this case, we are only serializing/deserializing our own trusted Scanner objects, @@ -18,10 +19,13 @@ class SessionManager: Manages multiple scanner instances for concurrent user sessions using Redis. """ - def __init__(self, session_timeout_minutes: int = 60): + def __init__(self, session_timeout_minutes: int = 0): """ Initialize session manager with a Redis backend. """ + if session_timeout_minutes is None: + session_timeout_minutes = config.session_timeout_minutes + self.redis_client = redis.StrictRedis(db=0, decode_responses=False) self.session_timeout = session_timeout_minutes * 60 # Convert to seconds self.lock = threading.Lock() # Lock for local operations, Redis handles atomic ops diff --git a/providers/base_provider.py b/providers/base_provider.py index c7bbd75..f8b66a7 100644 --- a/providers/base_provider.py +++ b/providers/base_provider.py @@ -82,7 +82,7 @@ class BaseProvider(ABC): # Caching configuration (per session) self.cache_dir = f'.cache/{id(self.config)}' # Unique cache per session config - self.cache_expiry = 12 * 3600 # 12 hours in seconds + self.cache_expiry = self.config.cache_expiry_hours * 3600 if not os.path.exists(self.cache_dir): os.makedirs(self.cache_dir) diff --git a/requirements.txt b/requirements.txt index be4385c..0e37daa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ Werkzeug>=2.3.7 urllib3>=2.0.0 dnspython>=2.4.2 gunicorn -redis \ No newline at end of file +redis +python-dotenv \ No newline at end of file