diff --git a/README.md b/README.md index b84d955..06157bd 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ To customize the deception server installation several **environment variables** | `DASHBOARD_SECRET_PATH` | Custom dashboard path | Auto-generated | | `PROBABILITY_ERROR_CODES` | Error response probability (0-100%) | `0` | | `SERVER_HEADER` | HTTP Server header for deception | `Apache/2.2.22 (Ubuntu)` | +| `TIMEZONE` | IANA timezone for logs and dashboard (e.g., `America/New_York`, `Europe/Rome`) | System timezone | ## robots.txt The actual (juicy) robots.txt configuration is the following diff --git a/deployment.yaml b/deployment.yaml deleted file mode 100644 index 4bf5189..0000000 --- a/deployment.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: krawl-server - namespace: krawl - labels: - app: krawl-server -spec: - replicas: 1 - selector: - matchLabels: - app: krawl-server - template: - metadata: - labels: - app: krawl-server - spec: - containers: - - name: krawl - image: ghcr.io/blessedrebus/krawl:latest - imagePullPolicy: Always - ports: - - containerPort: 5000 - name: http - protocol: TCP - envFrom: - - configMapRef: - name: krawl-config - volumeMounts: - - name: wordlists - mountPath: /app/wordlists.json - subPath: wordlists.json - readOnly: true - resources: - requests: - memory: "64Mi" - cpu: "100m" - limits: - memory: "256Mi" - cpu: "500m" - volumes: - - name: wordlists - configMap: - name: krawl-wordlists diff --git a/docker-compose.yaml b/docker-compose.yaml index 1612864..600034d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,6 +25,8 @@ services: # - CANARY_TOKEN_URL=http://canarytokens.com/api/users/YOUR_TOKEN/passwords.txt # Optional: Set custom dashboard path (auto-generated if not set) # - DASHBOARD_SECRET_PATH=/my-secret-dashboard + # Optional: Set timezone for logs and dashboard (e.g., America/New_York, Europe/Rome) + # - TIMEZONE=UTC restart: unless-stopped healthcheck: test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:5000')"] diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml index c50ab75..c08aaa5 100644 --- a/helm/templates/configmap.yaml +++ b/helm/templates/configmap.yaml @@ -16,3 +16,6 @@ data: PROBABILITY_ERROR_CODES: {{ .Values.config.probabilityErrorCodes | quote }} SERVER_HEADER: {{ .Values.config.serverHeader | quote }} CANARY_TOKEN_URL: {{ .Values.config.canaryTokenUrl | quote }} + {{- if .Values.config.timezone }} + TIMEZONE: {{ .Values.config.timezone | quote }} + {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index a095632..ac51756 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -75,6 +75,7 @@ config: probabilityErrorCodes: 0 serverHeader: "Apache/2.2.22 (Ubuntu)" # canaryTokenUrl: set-your-canary-token-url-here +# timezone: "UTC" # IANA timezone (e.g., "America/New_York", "Europe/Rome"). If not set, system timezone is used. networkPolicy: enabled: true diff --git a/kubernetes/manifests/configmap.yaml b/kubernetes/manifests/configmap.yaml index 431b9a3..073005f 100644 --- a/kubernetes/manifests/configmap.yaml +++ b/kubernetes/manifests/configmap.yaml @@ -14,4 +14,5 @@ data: CANARY_TOKEN_TRIES: "10" PROBABILITY_ERROR_CODES: "0" SERVER_HEADER: "Apache/2.2.22 (Ubuntu)" -# CANARY_TOKEN_URL: set-your-canary-token-url-here \ No newline at end of file +# CANARY_TOKEN_URL: set-your-canary-token-url-here +# TIMEZONE: "UTC" # IANA timezone (e.g., "America/New_York", "Europe/Rome") \ No newline at end of file diff --git a/src/config.py b/src/config.py index 7c6714c..741f01f 100644 --- a/src/config.py +++ b/src/config.py @@ -3,6 +3,8 @@ import os from dataclasses import dataclass from typing import Optional, Tuple +from zoneinfo import ZoneInfo +import time @dataclass @@ -22,6 +24,40 @@ class Config: api_server_path: str = "/api/v2/users" probability_error_codes: int = 0 # Percentage (0-100) server_header: str = "Apache/2.2.22 (Ubuntu)" + timezone: str = None # IANA timezone (e.g., 'America/New_York', 'Europe/Rome') + + @staticmethod + # Try to fetch timezone before if not set + def get_system_timezone() -> str: + """Get the system's default timezone""" + try: + if os.path.islink('/etc/localtime'): + tz_path = os.readlink('/etc/localtime') + if 'zoneinfo/' in tz_path: + return tz_path.split('zoneinfo/')[-1] + + local_tz = time.tzname[time.daylight] + if local_tz and local_tz != 'UTC': + return local_tz + except Exception: + pass + + # Default fallback to UTC + return 'UTC' + + def get_timezone(self) -> ZoneInfo: + """Get configured timezone as ZoneInfo object""" + if self.timezone: + try: + return ZoneInfo(self.timezone) + except Exception: + pass + + system_tz = self.get_system_timezone() + try: + return ZoneInfo(system_tz) + except Exception: + return ZoneInfo('UTC') @classmethod def from_env(cls) -> 'Config': @@ -45,6 +81,7 @@ class Config: api_server_url=os.getenv('API_SERVER_URL'), api_server_port=int(os.getenv('API_SERVER_PORT', 8080)), api_server_path=os.getenv('API_SERVER_PATH', '/api/v2/users'), - probability_error_codes=int(os.getenv('PROBABILITY_ERROR_CODES', 5)), - server_header=os.getenv('SERVER_HEADER', 'Apache/2.2.22 (Ubuntu)') + probability_error_codes=int(os.getenv('PROBABILITY_ERROR_CODES', 0)), + server_header=os.getenv('SERVER_HEADER', 'Apache/2.2.22 (Ubuntu)'), + timezone=os.getenv('TIMEZONE') # If not set, will use system timezone ) diff --git a/src/logger.py b/src/logger.py index 9f09236..992cad8 100644 --- a/src/logger.py +++ b/src/logger.py @@ -8,6 +8,23 @@ Provides two loggers: app (application) and access (HTTP access logs). import logging import os from logging.handlers import RotatingFileHandler +from typing import Optional +from zoneinfo import ZoneInfo +from datetime import datetime + + +class TimezoneFormatter(logging.Formatter): + """Custom formatter that respects configured timezone""" + def __init__(self, fmt=None, datefmt=None, timezone: Optional[ZoneInfo] = None): + super().__init__(fmt, datefmt) + self.timezone = timezone or ZoneInfo('UTC') + + def formatTime(self, record, datefmt=None): + """Override formatTime to use configured timezone""" + dt = datetime.fromtimestamp(record.created, tz=self.timezone) + if datefmt: + return dt.strftime(datefmt) + return dt.isoformat() class LoggerManager: @@ -20,23 +37,27 @@ class LoggerManager: cls._instance._initialized = False return cls._instance - def initialize(self, log_dir: str = "logs") -> None: + def initialize(self, log_dir: str = "logs", timezone: Optional[ZoneInfo] = None) -> None: """ Initialize the logging system with rotating file handlers. Args: log_dir: Directory for log files (created if not exists) + timezone: ZoneInfo timezone for log timestamps (defaults to UTC) """ if self._initialized: return + self.timezone = timezone or ZoneInfo('UTC') + # Create log directory if it doesn't exist os.makedirs(log_dir, exist_ok=True) # Common format for all loggers - log_format = logging.Formatter( + log_format = TimezoneFormatter( "[%(asctime)s] %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S" + datefmt="%Y-%m-%d %H:%M:%S", + timezone=self.timezone ) # Rotation settings: 1MB max, 5 backups @@ -83,7 +104,7 @@ class LoggerManager: self._credential_logger.handlers.clear() # Credential logger uses a simple format: timestamp|ip|username|password|path - credential_format = logging.Formatter("%(message)s") + credential_format = TimezoneFormatter("%(message)s", timezone=self.timezone) credential_file_handler = RotatingFileHandler( os.path.join(log_dir, "credentials.log"), @@ -136,6 +157,6 @@ def get_credential_logger() -> logging.Logger: return _logger_manager.credentials -def initialize_logging(log_dir: str = "logs") -> None: +def initialize_logging(log_dir: str = "logs", timezone: Optional[ZoneInfo] = None) -> None: """Initialize the logging system.""" - _logger_manager.initialize(log_dir) + _logger_manager.initialize(log_dir, timezone) diff --git a/src/server.py b/src/server.py index fd8f7d2..fcb794e 100644 --- a/src/server.py +++ b/src/server.py @@ -33,6 +33,8 @@ def print_usage(): print(' PROBABILITY_ERROR_CODES - Probability (0-100) to return HTTP error codes (default: 0)') print(' CHAR_SPACE - Characters for random links') print(' SERVER_HEADER - HTTP Server header for deception (default: Apache/2.2.22 (Ubuntu))') + print(' TIMEZONE - IANA timezone for logs/dashboard (e.g., America/New_York, Europe/Rome)') + print(' If not set, system timezone will be used') def main(): @@ -41,15 +43,19 @@ def main(): print_usage() exit(0) - # Initialize logging - initialize_logging() + config = Config.from_env() + + # Get timezone configuration + tz = config.get_timezone() + + # Initialize logging with timezone + initialize_logging(timezone=tz) app_logger = get_app_logger() access_logger = get_access_logger() credential_logger = get_credential_logger() - config = Config.from_env() - - tracker = AccessTracker() + # Initialize tracker with timezone + tracker = AccessTracker(timezone=tz) Handler.config = config Handler.tracker = tracker @@ -71,6 +77,7 @@ def main(): try: app_logger.info(f'Starting deception server on port {config.port}...') + app_logger.info(f'Timezone configured: {tz.key}') app_logger.info(f'Dashboard available at: {config.dashboard_secret_path}') if config.canary_token_url: app_logger.info(f'Canary token will appear after {config.canary_token_tries} tries') diff --git a/src/templates/dashboard_template.py b/src/templates/dashboard_template.py index a267278..9fc4111 100644 --- a/src/templates/dashboard_template.py +++ b/src/templates/dashboard_template.py @@ -5,6 +5,18 @@ Dashboard template for viewing honeypot statistics. Customize this template to change the dashboard appearance. """ +from datetime import datetime + + +def format_timestamp(iso_timestamp: str) -> str: + """Format ISO timestamp for display (YYYY-MM-DD HH:MM:SS)""" + try: + dt = datetime.fromisoformat(iso_timestamp) + return dt.strftime("%Y-%m-%d %H:%M:%S") + except Exception: + # Fallback for old format + return iso_timestamp.split("T")[1][:8] if "T" in iso_timestamp else iso_timestamp + def generate_dashboard(stats: dict) -> str: """Generate dashboard HTML with access statistics""" @@ -29,7 +41,7 @@ def generate_dashboard(stats: dict) -> str: # Generate suspicious accesses rows suspicious_rows = '\n'.join([ - f'