Files
Andrew Pease 1981c72e19 Phase 2: Add configuration management module
- Create Config class to load and validate .env file
- Validate passwords are changed from default 'changeme'
- Provide type-safe access to all configuration values
- Support detection rule flags (Linux/Windows/macOS)
- Helpful error messages for missing or invalid config
2025-11-20 08:42:04 -06:00

185 lines
5.9 KiB
Python

"""
Configuration management for Elastic Container
Loads and validates .env file settings
"""
import os
import sys
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
import click
class Config:
"""
Manages configuration from .env file
Validates required settings and provides easy access to all config values
"""
def __init__(self, env_file: str = ".env"):
"""
Initialize configuration by loading .env file
Args:
env_file: Path to .env file (default: .env)
"""
self.env_file = Path(env_file)
# Check if .env file exists
if not self.env_file.exists():
click.secho(f"Error: {env_file} file not found!", fg="red", bold=True)
click.secho(f"Please create a {env_file} file with required configuration.", fg="yellow")
click.secho("\nRequired variables:", fg="yellow")
click.secho(" - ELASTIC_PASSWORD", fg="yellow")
click.secho(" - KIBANA_PASSWORD", fg="yellow")
click.secho(" - STACK_VERSION", fg="yellow")
sys.exit(1)
# Load environment variables from .env file
load_dotenv(self.env_file)
# Validate required variables exist
self._validate_required_variables()
def _validate_required_variables(self):
"""Ensure all required environment variables are set"""
required_vars = [
"ELASTIC_PASSWORD",
"KIBANA_PASSWORD",
"STACK_VERSION"
]
missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars:
click.secho("Error: Missing required environment variables in .env file:", fg="red", bold=True)
for var in missing_vars:
click.secho(f" - {var}", fg="red")
sys.exit(1)
def validate_passwords(self):
"""
Validate that default passwords have been changed
Exits with error if passwords are still set to 'changeme'
"""
if "changeme" in [self.elastic_password.lower(), self.kibana_password.lower()]:
click.secho("\n" + "=" * 70, fg="red")
click.secho("ERROR: Default passwords detected!", fg="red", bold=True)
click.secho("=" * 70, fg="red")
click.echo()
click.secho("Sorry, looks like you haven't updated the passphrase from the default.", fg="yellow")
click.secho("Please update the 'changeme' passphrases in the .env file.", fg="yellow")
click.echo()
click.secho("Security Note:", fg="cyan", bold=True)
click.secho(" This project is intended for local security research only.", fg="cyan")
click.secho(" You must change default passwords before starting.", fg="cyan")
click.echo()
sys.exit(1)
click.secho("✓ Passphrase has been changed. Proceeding.", fg="green")
def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
"""
Get environment variable value
Args:
key: Environment variable name
default: Default value if not found
Returns:
Variable value or default
"""
return os.getenv(key, default)
# Elastic credentials
@property
def elastic_username(self) -> str:
"""Elasticsearch username (default: elastic)"""
return self.get("ELASTIC_USERNAME", "elastic")
@property
def elastic_password(self) -> str:
"""Elasticsearch password"""
return self.get("ELASTIC_PASSWORD", "")
@property
def kibana_password(self) -> str:
"""Kibana system user password"""
return self.get("KIBANA_PASSWORD", "")
# Stack configuration
@property
def stack_version(self) -> str:
"""Elastic Stack version"""
return self.get("STACK_VERSION", "8.14.0")
@property
def cluster_name(self) -> str:
"""Elasticsearch cluster name"""
return self.get("CLUSTER_NAME", "docker-cluster")
@property
def license(self) -> str:
"""Elasticsearch license type"""
return self.get("LICENSE", "basic")
# Ports
@property
def es_port(self) -> int:
"""Elasticsearch port"""
return int(self.get("ES_PORT", "9200"))
@property
def kibana_port(self) -> int:
"""Kibana port"""
return int(self.get("KIBANA_PORT", "5601"))
@property
def fleet_port(self) -> int:
"""Fleet Server port"""
return int(self.get("FLEET_PORT", "8220"))
# Resource limits
@property
def mem_limit(self) -> str:
"""Memory limit for containers"""
return self.get("MEM_LIMIT", "1073741824") # 1GB default
# Detection Rules
@property
def linux_detection_rules(self) -> bool:
"""Enable Linux detection rules"""
return self.get("LinuxDR", "0") == "1"
@property
def windows_detection_rules(self) -> bool:
"""Enable Windows detection rules"""
return self.get("WindowsDR", "0") == "1"
@property
def macos_detection_rules(self) -> bool:
"""Enable macOS detection rules"""
return self.get("MacOSDR", "0") == "1"
# Constructed URLs
@property
def local_kibana_url(self) -> str:
"""Local Kibana URL"""
return f"https://localhost:{self.kibana_port}"
@property
def local_elasticsearch_url(self) -> str:
"""Local Elasticsearch URL"""
return f"https://localhost:{self.es_port}"
def __repr__(self) -> str:
"""String representation of config (hides passwords)"""
return (
f"Config(stack_version={self.stack_version}, "
f"kibana_port={self.kibana_port}, "
f"es_port={self.es_port}, "
f"fleet_port={self.fleet_port})"
)