unify config

This commit is contained in:
overcuriousity 2025-09-14 16:17:26 +02:00
parent 3511f18f9a
commit 689e8c00d4
7 changed files with 100 additions and 178 deletions

34
.env.example Normal file
View File

@ -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

5
app.py
View File

@ -16,8 +16,9 @@ from config import config
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = 'dnsrecon-dev-key-change-in-production' # Use centralized configuration for Flask settings
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2) # 2 hour session lifetime 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(): def get_user_scanner():
""" """

118
config.py
View File

@ -5,111 +5,93 @@ Handles API key storage, rate limiting, and default settings.
import os import os
from typing import Dict, Optional from typing import Dict, Optional
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
class Config: class Config:
"""Configuration manager for DNSRecon application.""" """Configuration manager for DNSRecon application."""
def __init__(self): def __init__(self):
"""Initialize configuration with default values.""" """Initialize configuration with default values."""
self.api_keys: Dict[str, Optional[str]] = { self.api_keys: Dict[str, Optional[str]] = {}
'shodan': None
}
# Default settings # --- General Settings ---
self.default_recursion_depth = 2 self.default_recursion_depth = 2
self.default_timeout = 10 self.default_timeout = 10
self.max_concurrent_requests = 5 self.max_concurrent_requests = 5
self.large_entity_threshold = 100 self.large_entity_threshold = 100
self.max_retries_per_target = 3 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 = { self.rate_limits = {
'crtsh': 60, # Free service, be respectful 'crtsh': 60,
'shodan': 60, # API dependent 'shodan': 60,
'dns': 100 # Local DNS queries 'dns': 100
} }
# Provider settings # --- Provider Settings ---
self.enabled_providers = { self.enabled_providers = {
'crtsh': True, # Always enabled (free) 'crtsh': True,
'dns': True, # Always enabled (free) 'dns': True,
'shodan': False # Requires API key 'shodan': False
} }
# Logging configuration # --- Logging ---
self.log_level = 'INFO' self.log_level = 'INFO'
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 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_host = '127.0.0.1'
self.flask_port = 5000 self.flask_port = 5000
self.flask_debug = True 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: # Load environment variables to override defaults
""" self.load_from_env()
Set API key for a provider.
Args: def load_from_env(self):
provider: Provider name (shodan, etc) """Load configuration from environment variables."""
api_key: API key string self.set_api_key('shodan', os.getenv('SHODAN_API_KEY'))
Returns: # Override settings from environment
bool: True if key was set successfully 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))
if provider in self.api_keys: self.max_concurrent_requests = int(os.getenv('MAX_CONCURRENT_REQUESTS', self.max_concurrent_requests))
self.api_keys[provider] = api_key self.large_entity_threshold = int(os.getenv('LARGE_ENTITY_THRESHOLD', self.large_entity_threshold))
self.enabled_providers[provider] = True if api_key else False self.max_retries_per_target = int(os.getenv('MAX_RETRIES_PER_TARGET', self.max_retries_per_target))
return True self.cache_expiry_hours = int(os.getenv('CACHE_EXPIRY_HOURS', self.cache_expiry_hours))
return False
# 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]: def get_api_key(self, provider: str) -> Optional[str]:
""" """Get API key for a provider."""
Get API key for a provider.
Args:
provider: Provider name
Returns:
API key or None if not set
"""
return self.api_keys.get(provider) return self.api_keys.get(provider)
def is_provider_enabled(self, provider: str) -> bool: def is_provider_enabled(self, provider: str) -> bool:
""" """Check if a provider is enabled."""
Check if a provider is enabled.
Args:
provider: Provider name
Returns:
bool: True if provider is enabled
"""
return self.enabled_providers.get(provider, False) return self.enabled_providers.get(provider, False)
def get_rate_limit(self, provider: str) -> int: def get_rate_limit(self, provider: str) -> int:
""" """Get rate limit for a provider."""
Get rate limit for a provider.
Args:
provider: Provider name
Returns:
Rate limit in requests per minute
"""
return self.rate_limits.get(provider, 60) 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 # Global configuration instance
config = Config() config = Config()

View File

@ -3,11 +3,9 @@ Per-session configuration management for DNSRecon.
Provides isolated configuration instances for each user session. Provides isolated configuration instances for each user session.
""" """
import os from config import Config
from typing import Dict, Optional
class SessionConfig(Config):
class SessionConfig:
""" """
Session-specific configuration that inherits from global config Session-specific configuration that inherits from global config
but maintains isolated API keys and provider settings. but maintains isolated API keys and provider settings.
@ -15,106 +13,8 @@ class SessionConfig:
def __init__(self): def __init__(self):
"""Initialize session config with global defaults.""" """Initialize session config with global defaults."""
# Copy all attributes from global config super().__init__()
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
def create_session_config() -> 'SessionConfig':
def create_session_config() -> SessionConfig:
"""Create a new session configuration instance.""" """Create a new session configuration instance."""
session_config = SessionConfig() return SessionConfig()
session_config.load_from_env()
return session_config

View File

@ -8,6 +8,7 @@ import pickle
from typing import Dict, Optional, Any, List from typing import Dict, Optional, Any, List
from core.scanner import Scanner from core.scanner import Scanner
from config import config
# WARNING: Using pickle can be a security risk if the data source is not trusted. # 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, # 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. 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. 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.redis_client = redis.StrictRedis(db=0, decode_responses=False)
self.session_timeout = session_timeout_minutes * 60 # Convert to seconds self.session_timeout = session_timeout_minutes * 60 # Convert to seconds
self.lock = threading.Lock() # Lock for local operations, Redis handles atomic ops self.lock = threading.Lock() # Lock for local operations, Redis handles atomic ops

View File

@ -82,7 +82,7 @@ class BaseProvider(ABC):
# Caching configuration (per session) # Caching configuration (per session)
self.cache_dir = f'.cache/{id(self.config)}' # Unique cache per session config 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): if not os.path.exists(self.cache_dir):
os.makedirs(self.cache_dir) os.makedirs(self.cache_dir)

View File

@ -6,4 +6,5 @@ Werkzeug>=2.3.7
urllib3>=2.0.0 urllib3>=2.0.0
dnspython>=2.4.2 dnspython>=2.4.2
gunicorn gunicorn
redis redis
python-dotenv