- 特征检测(规则/签名)与行为检测(24h 统计)单进程运行 - 融合名单按首次发现时间倒序,展示位置/归属(GeoIP + ASN) - 钉钉融合推送(加签、24h 节流、去重) - 页面状态灯、CSV 导出、Docker 镜像构建文件
162 lines
4.5 KiB
Python
162 lines
4.5 KiB
Python
"""Configuration resolution.
|
|
|
|
Precedence (highest wins):
|
|
1. command line flags
|
|
2. environment variables (GRAYLOG_URL, GRAYLOG_TOKEN, ...)
|
|
3. JSON config file (--config, ./graylog-ops.json,
|
|
~/.config/graylog-ops/config.json)
|
|
|
|
File format::
|
|
|
|
{
|
|
"url": "http://edu-master:9000",
|
|
"token": "xxxxxxxx",
|
|
"insecure": false,
|
|
"timeout": 30
|
|
}
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
ENV_KEYS = {
|
|
"url": "GRAYLOG_URL",
|
|
"token": "GRAYLOG_TOKEN",
|
|
"user": "GRAYLOG_USER",
|
|
"password": "GRAYLOG_PASSWORD",
|
|
"insecure": "GRAYLOG_INSECURE",
|
|
"timeout": "GRAYLOG_TIMEOUT",
|
|
"retries": "GRAYLOG_RETRIES",
|
|
}
|
|
|
|
DEFAULT_CONFIG_PATHS = (
|
|
os.path.expanduser("~/.config/graylog-ops/config.json"),
|
|
"graylog-ops.json",
|
|
)
|
|
|
|
|
|
class ConfigError(Exception):
|
|
"""User facing configuration problem."""
|
|
|
|
|
|
def _truthy(value):
|
|
return str(value).strip().lower() in ("1", "true", "yes", "on", "y")
|
|
|
|
|
|
def _read_file(path):
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as handle:
|
|
data = json.load(handle)
|
|
except OSError as exc:
|
|
raise ConfigError("cannot read config %s: %s" % (path, exc))
|
|
except ValueError as exc:
|
|
raise ConfigError("config %s is not valid JSON: %s" % (path, exc))
|
|
if not isinstance(data, dict):
|
|
raise ConfigError("config %s must contain a JSON object" % path)
|
|
return data
|
|
|
|
|
|
class Config:
|
|
def __init__(self, url, token=None, user=None, password=None,
|
|
insecure=False, timeout=30.0, retries=2, source=None):
|
|
self.url = url
|
|
self.token = token
|
|
self.user = user
|
|
self.password = password
|
|
self.insecure = bool(insecure)
|
|
self.timeout = float(timeout)
|
|
self.retries = int(retries)
|
|
self.source = source
|
|
|
|
@property
|
|
def credentials(self):
|
|
"""(username, password) pair for HTTP Basic auth."""
|
|
if self.token:
|
|
# Graylog token auth: username is the token, password is "token".
|
|
return self.token, "token"
|
|
return self.user, self.password
|
|
|
|
@property
|
|
def auth_kind(self):
|
|
return "token" if self.token else "user/password"
|
|
|
|
|
|
def _pick(values, key, default=None):
|
|
value = values.get(key, default)
|
|
return value
|
|
|
|
|
|
def load_config(args):
|
|
"""Build a Config from CLI args, env vars and the config file."""
|
|
values = {}
|
|
chosen = None
|
|
|
|
explicit = getattr(args, "config", None)
|
|
if explicit:
|
|
if not os.path.exists(explicit):
|
|
raise ConfigError("config file not found: %s" % explicit)
|
|
values.update(_read_file(explicit))
|
|
chosen = explicit
|
|
else:
|
|
for candidate in DEFAULT_CONFIG_PATHS:
|
|
if os.path.exists(candidate):
|
|
values.update(_read_file(candidate))
|
|
chosen = candidate
|
|
break
|
|
|
|
for key, env_name in ENV_KEYS.items():
|
|
env_value = os.environ.get(env_name)
|
|
if env_value is not None and env_value.strip() != "":
|
|
values[key] = env_value
|
|
|
|
for key in ("url", "token", "user", "password", "timeout", "retries"):
|
|
flag_value = getattr(args, key, None)
|
|
if flag_value is not None and flag_value != "":
|
|
values[key] = flag_value
|
|
if getattr(args, "insecure", False):
|
|
values["insecure"] = True
|
|
|
|
url = values.get("url")
|
|
if not url:
|
|
raise ConfigError(
|
|
"no Graylog URL: pass --url, set GRAYLOG_URL, or add \"url\" to the config file"
|
|
)
|
|
url = str(url).strip()
|
|
if "://" not in url:
|
|
url = "http://" + url
|
|
|
|
token = (values.get("token") or "").strip() or None
|
|
user = (values.get("user") or "").strip() or None
|
|
password = values.get("password")
|
|
if not token and not (user and password):
|
|
raise ConfigError(
|
|
"no credentials: pass --token or --user/--password "
|
|
"(env GRAYLOG_TOKEN / GRAYLOG_USER + GRAYLOG_PASSWORD)"
|
|
)
|
|
|
|
timeout = values.get("timeout", 30)
|
|
retries = values.get("retries", 2)
|
|
try:
|
|
timeout = float(timeout)
|
|
except (TypeError, ValueError):
|
|
raise ConfigError("timeout must be a number, got %r" % (timeout,))
|
|
try:
|
|
retries = int(retries)
|
|
except (TypeError, ValueError):
|
|
raise ConfigError("retries must be an integer, got %r" % (retries,))
|
|
|
|
insecure = values.get("insecure", False)
|
|
if isinstance(insecure, str):
|
|
insecure = _truthy(insecure)
|
|
|
|
return Config(
|
|
url=url,
|
|
token=token,
|
|
user=user,
|
|
password=password,
|
|
insecure=bool(insecure),
|
|
timeout=timeout,
|
|
retries=retries,
|
|
source=chosen,
|
|
)
|