mirror of
https://github.com/napnap75/multiarch-docker-images.git
synced 2025-12-15 03:04:19 +01:00
Added the geolocation filter and improved the config file format
This commit is contained in:
@@ -1,27 +1,24 @@
|
|||||||
{
|
{
|
||||||
"log-fetchers": [
|
"log-fetchers": {
|
||||||
{
|
"loki-fileserver": {
|
||||||
"name": "loki-fileserver",
|
|
||||||
"type": "loki",
|
"type": "loki",
|
||||||
"config": {
|
"config": {
|
||||||
"url": "http://fileserver.home:3100"
|
"url-from-env": "{LOKI_URL}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"alert-managers":[
|
"alert-managers": {
|
||||||
{
|
"gotify-paris": {
|
||||||
"name": "gotify-paris",
|
|
||||||
"type": "gotify",
|
"type": "gotify",
|
||||||
"config": {
|
"config": {
|
||||||
"url": "https://paris.nappez.com/gotify/message",
|
"url-from-env": "{GOTIFY_URL}",
|
||||||
"token-from-env": "{GOTIFY_TOKEN}"
|
"token-from-env": "{GOTIFY_TOKEN}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"log-alerts": [
|
"alerting-rules": {
|
||||||
{
|
"ssh-outside": {
|
||||||
"name": "SSH outside connection",
|
"check-interval": 30,
|
||||||
"check-interval": 60,
|
|
||||||
"log-fetcher": {
|
"log-fetcher": {
|
||||||
"name": "loki-fileserver",
|
"name": "loki-fileserver",
|
||||||
"filters": {
|
"filters": {
|
||||||
@@ -37,16 +34,21 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"match": "Accepted (?P<method>\\w+) for (?P<username>\\w+) from (?P<ip>[^\\s]+)"
|
"match": "Accepted (?P<method>\\w+) for (?P<username>\\w+) from (?P<ip>[^\\s]+)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "geolocation",
|
||||||
|
"config": {
|
||||||
|
"source-field": "ip"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"alert-manager": {
|
"alert-manager": {
|
||||||
"name": "gotify-paris",
|
"name": "gotify-paris",
|
||||||
"title": "Outside SSH login",
|
"title": "Outside SSH login",
|
||||||
"message": "New SSH login for {username} on {instance} from ip {ip} (method: {method})"
|
"message": "New SSH login for {username} on {instance} from ip {ip} (country: {country}, provider: {isp}, method: {method})"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"ssh-local": {
|
||||||
"name": "SSH local connection",
|
|
||||||
"check-interval": 30,
|
"check-interval": 30,
|
||||||
"log-fetcher": {
|
"log-fetcher": {
|
||||||
"name": "loki-fileserver",
|
"name": "loki-fileserver",
|
||||||
@@ -71,5 +73,5 @@
|
|||||||
"message": "New SSH login for {username} on {instance} from ip {ip} (method: {method})"
|
"message": "New SSH login for {username} on {instance} from ip {ip} (method: {method})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
@@ -2,80 +2,43 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Log Alert configuration schema",
|
"title": "Log Alert configuration schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["log-fetchers", "alert-managers", "log-alerts"],
|
"required": ["log-fetchers", "alert-managers", "alerting-rules"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"log-fetchers": {
|
"log-fetchers": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "type", "config"],
|
"required": ["type", "config"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
|
||||||
"type": { "type": "string", "enum": ["loki"] },
|
"type": { "type": "string", "enum": ["loki"] },
|
||||||
"config": {
|
"config": { "type": "object" }
|
||||||
"type": "object",
|
}
|
||||||
"properties": {
|
|
||||||
"url": { "type": "string", "format": "uri" }
|
|
||||||
},
|
|
||||||
"required": ["url"],
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alert-managers": {
|
"alert-managers": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "type", "config"],
|
"required": ["type", "config"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
|
||||||
"type": { "type": "string", "enum": ["gotify"] },
|
"type": { "type": "string", "enum": ["gotify"] },
|
||||||
"config": {
|
"config": { "type": "object" }
|
||||||
"type": "object",
|
}
|
||||||
"properties": {
|
|
||||||
"url": { "type": "string", "format": "uri" },
|
|
||||||
"token": { "type": "string" },
|
|
||||||
"token-from-env": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["url"],
|
|
||||||
"anyOf": [
|
|
||||||
{ "required": ["token"] },
|
|
||||||
{ "required": ["token-from-env"] }
|
|
||||||
],
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"log-alerts": {
|
"alerting-rules": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name", "log-fetcher", "filters", "alert-manager"],
|
"required": ["check-interval", "log-fetcher", "filters", "alert-manager"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
|
||||||
"check-interval": { "type": "number", "minimum": 0 },
|
"check-interval": { "type": "number", "minimum": 0 },
|
||||||
"log-fetcher": {
|
"log-fetcher": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
"name": { "type": "string" }
|
||||||
"filters": {
|
}
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"labels": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": { "type": "string" }
|
|
||||||
},
|
|
||||||
"text": { "type": "string" }
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -83,33 +46,22 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["type", "config"],
|
"required": ["type", "config"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string", "enum": ["regexp"] },
|
"type": { "type": "string", "enum": ["regexp", "geolocation"] },
|
||||||
"config": {
|
"config": {
|
||||||
"type": "object",
|
"type": "object"
|
||||||
"required": ["match"],
|
|
||||||
"properties": {
|
|
||||||
"match": { "type": "string" }
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alert-manager": {
|
"alert-manager": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
"name": { "type": "string" }
|
||||||
"title": { "type": "string" },
|
}
|
||||||
"message": { "type": "string" }
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,31 @@ class RegexpFilter(Filter):
|
|||||||
logger.debug(f"Regex did not match for pattern '{self.match}' in log: {log.get('log')}")
|
logger.debug(f"Regex did not match for pattern '{self.match}' in log: {log.get('log')}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Geolocation Filter
|
||||||
|
class GeolocationFilter(Filter):
|
||||||
|
"""Concrete implementation for Geolocation filter."""
|
||||||
|
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.source_field = config["source-field"]
|
||||||
|
|
||||||
|
def filter(self, log: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
ip_address = log.get("labels", {}).get(self.source_field)
|
||||||
|
if not ip_address:
|
||||||
|
logger.warning("No IP address found in log labels for geolocation")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
response = requests.get(f"http://ip-api.com/json/{ip_address}").json()
|
||||||
|
if response["status"] == "success":
|
||||||
|
logger.debug(f"Found info {response} for IP {ip_address}")
|
||||||
|
del response["status"]
|
||||||
|
del response["query"]
|
||||||
|
log.setdefault("labels", {}).update(response)
|
||||||
|
else:
|
||||||
|
logger.warning("No info found for IP {ip_address}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"Error fetching geolocation for IP {ip_address}: {e}")
|
||||||
|
return log
|
||||||
|
|
||||||
# Gotify Alert Manager
|
# Gotify Alert Manager
|
||||||
class GotifyAlertManager(AlertManager):
|
class GotifyAlertManager(AlertManager):
|
||||||
"""Concrete implementation for Gotify alert manager."""
|
"""Concrete implementation for Gotify alert manager."""
|
||||||
@@ -129,7 +154,6 @@ class AlertRule:
|
|||||||
"""Represents an alert rule with filters and alert template."""
|
"""Represents an alert rule with filters and alert template."""
|
||||||
|
|
||||||
def __init__(self, log_fetchers: LogFetcher, alert_managers: AlertManager, config: Dict[str, Any]):
|
def __init__(self, log_fetchers: LogFetcher, alert_managers: AlertManager, config: Dict[str, Any]):
|
||||||
self.name = config["name"]
|
|
||||||
self.log_fetcher = log_fetchers[config["log-fetcher"]["name"]]
|
self.log_fetcher = log_fetchers[config["log-fetcher"]["name"]]
|
||||||
self.fetcher_filters = config["log-fetcher"].get("filters", {})
|
self.fetcher_filters = config["log-fetcher"].get("filters", {})
|
||||||
self.check_interval = config.get("check-interval", 60)
|
self.check_interval = config.get("check-interval", 60)
|
||||||
@@ -137,6 +161,8 @@ class AlertRule:
|
|||||||
for filter in config.get("filters", []):
|
for filter in config.get("filters", []):
|
||||||
if filter["type"] == "regexp":
|
if filter["type"] == "regexp":
|
||||||
self.filters.append(RegexpFilter(filter["config"]))
|
self.filters.append(RegexpFilter(filter["config"]))
|
||||||
|
elif filter["type"] == "geolocation":
|
||||||
|
self.filters.append(GeolocationFilter(filter["config"]))
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported filter type: {filter['type']}")
|
raise ValueError(f"Unsupported filter type: {filter['type']}")
|
||||||
self.alert_manager = alert_managers[config["alert-manager"]["name"]]
|
self.alert_manager = alert_managers[config["alert-manager"]["name"]]
|
||||||
@@ -146,7 +172,6 @@ class AlertRule:
|
|||||||
self.next_run = time.time()
|
self.next_run = time.time()
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
logger.debug(f"Processing rule: {self.name}")
|
|
||||||
logs = self.log_fetcher.fetch_logs(self.fetcher_filters, self.last_run, self.next_run)
|
logs = self.log_fetcher.fetch_logs(self.fetcher_filters, self.last_run, self.next_run)
|
||||||
for log_entry in logs:
|
for log_entry in logs:
|
||||||
logger.debug(f"Checking log: {log_entry['log']}")
|
logger.debug(f"Checking log: {log_entry['log']}")
|
||||||
@@ -170,12 +195,14 @@ class LogAlertApp:
|
|||||||
self.config = self._load_config(config_path)
|
self.config = self._load_config(config_path)
|
||||||
logger.debug(f"Configuration loaded: {self.config}")
|
logger.debug(f"Configuration loaded: {self.config}")
|
||||||
self.log_fetchers = {}
|
self.log_fetchers = {}
|
||||||
for fetcher in self.config["log-fetchers"]:
|
for key, fetcher in self.config["log-fetchers"].items():
|
||||||
self.log_fetchers[fetcher["name"]] = self._init_log_fetcher(fetcher)
|
self.log_fetchers[key] = self._init_log_fetcher(fetcher)
|
||||||
self.alert_managers = {}
|
self.alert_managers = {}
|
||||||
for manager in self.config["alert-managers"]:
|
for key, manager in self.config["alert-managers"].items():
|
||||||
self.alert_managers[manager["name"]] = self._init_alert_manager(manager)
|
self.alert_managers[key] = self._init_alert_manager(manager)
|
||||||
self.alert_rules = [AlertRule(self.log_fetchers, self.alert_managers, rule) for rule in self.config["log-alerts"]]
|
self.alert_rules = {}
|
||||||
|
for key, rule in self.config["alerting-rules"].items():
|
||||||
|
self.alert_rules[key] = AlertRule(self.log_fetchers, self.alert_managers, rule)
|
||||||
|
|
||||||
def _load_config(self, config_path: str) -> Dict[str, Any]:
|
def _load_config(self, config_path: str) -> Dict[str, Any]:
|
||||||
"""Load the configuration from a JSON file and validate it with JSON Schema."""
|
"""Load the configuration from a JSON file and validate it with JSON Schema."""
|
||||||
@@ -243,8 +270,9 @@ class LogAlertApp:
|
|||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Fetch logs, check for matches, and send alerts."""
|
"""Fetch logs, check for matches, and send alerts."""
|
||||||
while True:
|
while True:
|
||||||
for rule in self.alert_rules:
|
for name, rule in self.alert_rules.items():
|
||||||
if time.time() >= rule.next_run:
|
if time.time() >= rule.next_run:
|
||||||
|
logger.debug(f"Processing rule: {name}")
|
||||||
rule.run()
|
rule.run()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user