mirror of
https://github.com/marcredhat/SIEM-toolkit-patched
synced 2026-06-08 20:37:12 +00:00
2e55e21a77
- Sidebar: ⚙ Settings link pinned to bottom of nav - Settings page: view all config keys (secrets masked), edit and save directly to .env - Show/hide toggle for secret fields (tokens, keys) - First-time setup banner with cp .env.example .env instructions when .env is missing - Manual setup section with step-by-step terminal commands and where to find each credential - New .env.example template with comments for all required variables - Backend: GET/POST /api/settings/config router reads/writes mounted .env file - docker-compose: mounts .env into backend container at /app/.env for write access Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
import os
|
|
import re
|
|
from pathlib import Path
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter()
|
|
|
|
ENV_FILE = Path(os.environ.get("ENV_FILE_PATH", "/app/.env"))
|
|
|
|
# Fields we expose in the UI — order matters for display
|
|
FIELDS = [
|
|
{"key": "S1_BASE_URL", "label": "Console URL", "secret": False, "placeholder": "https://demo.sentinelone.net"},
|
|
{"key": "S1_API_TOKEN", "label": "Console API Token", "secret": True, "placeholder": "eyJ..."},
|
|
{"key": "SDL_XDR_URL", "label": "SDL XDR URL", "secret": False, "placeholder": "https://xdr.us1.sentinelone.net"},
|
|
{"key": "SDL_LOG_READ_KEY", "label": "SDL Log Read Key", "secret": True, "placeholder": "1DnK0Y4e..."},
|
|
{"key": "ANTHROPIC_API_KEY", "label": "Anthropic API Key", "secret": True, "placeholder": "sk-ant-..."},
|
|
]
|
|
|
|
FIELD_KEYS = {f["key"] for f in FIELDS}
|
|
|
|
|
|
def _read_env() -> dict[str, str]:
|
|
"""Read .env file into a dict."""
|
|
vals: dict[str, str] = {}
|
|
if ENV_FILE.exists():
|
|
for line in ENV_FILE.read_text().splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
k, _, v = line.partition("=")
|
|
vals[k.strip()] = v.strip()
|
|
return vals
|
|
|
|
|
|
def _write_env(updates: dict[str, str]) -> None:
|
|
"""Write updates into .env, preserving comments and unknown keys."""
|
|
existing_lines: list[str] = []
|
|
if ENV_FILE.exists():
|
|
existing_lines = ENV_FILE.read_text().splitlines()
|
|
|
|
written: set[str] = set()
|
|
new_lines: list[str] = []
|
|
|
|
for line in existing_lines:
|
|
stripped = line.strip()
|
|
if stripped and not stripped.startswith("#") and "=" in stripped:
|
|
k, _, _ = stripped.partition("=")
|
|
k = k.strip()
|
|
if k in updates:
|
|
new_lines.append(f"{k}={updates[k]}")
|
|
written.add(k)
|
|
continue
|
|
new_lines.append(line)
|
|
|
|
# Append any new keys not already in the file
|
|
for k, v in updates.items():
|
|
if k not in written:
|
|
new_lines.append(f"{k}={v}")
|
|
|
|
ENV_FILE.write_text("\n".join(new_lines) + "\n")
|
|
|
|
|
|
@router.get("/config")
|
|
async def get_config():
|
|
"""Return current config values. Secrets are masked."""
|
|
env_vals = _read_env()
|
|
result = []
|
|
for f in FIELDS:
|
|
key = f["key"]
|
|
# Prefer live env var, fall back to .env file value
|
|
raw = os.environ.get(key, env_vals.get(key, ""))
|
|
if f["secret"] and raw:
|
|
# Show first 6 + last 4 chars, mask middle
|
|
masked = raw[:6] + "•" * max(4, len(raw) - 10) + raw[-4:] if len(raw) > 10 else "••••••••"
|
|
else:
|
|
masked = raw
|
|
result.append({
|
|
"key": key,
|
|
"label": f["label"],
|
|
"secret": f["secret"],
|
|
"placeholder": f["placeholder"],
|
|
"value": masked,
|
|
"set": bool(raw),
|
|
})
|
|
env_file_exists = ENV_FILE.exists()
|
|
return {"fields": result, "env_file_exists": env_file_exists, "env_file_path": str(ENV_FILE)}
|
|
|
|
|
|
class ConfigUpdate(BaseModel):
|
|
updates: dict[str, str]
|
|
|
|
|
|
@router.post("/config")
|
|
async def save_config(body: ConfigUpdate):
|
|
"""Save config values to .env file. Only known keys accepted."""
|
|
bad = [k for k in body.updates if k not in FIELD_KEYS]
|
|
if bad:
|
|
raise HTTPException(400, f"Unknown keys: {bad}")
|
|
if not ENV_FILE.parent.exists():
|
|
raise HTTPException(503, f"Cannot write to {ENV_FILE} — check Docker volume mount")
|
|
try:
|
|
_write_env(body.updates)
|
|
except Exception as e:
|
|
raise HTTPException(500, f"Failed to write .env: {e}")
|
|
return {"saved": list(body.updates.keys()), "restart_required": True}
|