Skip to content

Commit 6b37b68

Browse files
authored
Merge pull request #399 from gyptazy/feature/387-add-syslog-scoping
feature: Add syslog scoping
2 parents d5affc1 + fae1aeb commit 6b37b68

7 files changed

Lines changed: 176 additions & 11 deletions

File tree

pegaprox/api/helpers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ def load_server_settings():
8080
# MK Apr 2026 — when true, /api/metrics needs no auth. Useful for setups
8181
# where a reverse proxy/mutual-TLS already gates scrapes. Default off.
8282
'metrics_public': False,
83+
# When enabled, the Syslog viewer only shows hostnames belonging to
84+
# the currently selected cluster instead of all collected syslog rows.
85+
'syslog_filter_by_selected_cluster': False,
8386
# Webhook alert channels (Slack, Discord, Teams, ntfy, generic)
8487
# Each: {id, name, type, url, enabled, ...type-specific fields}
8588
'alert_webhooks': [],

pegaprox/api/reports.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re
88
import sqlite3
99
import threading
10+
from urllib.parse import urlparse
1011
from datetime import datetime, timedelta
1112
from flask import Blueprint, jsonify, request
1213

@@ -16,7 +17,7 @@
1617

1718
from pegaprox.utils.auth import require_auth, load_users
1819
from pegaprox.utils.rbac import get_user_clusters
19-
from pegaprox.api.helpers import check_cluster_access
20+
from pegaprox.api.helpers import check_cluster_access, load_server_settings
2021
from pegaprox.background.metrics import load_metrics_history, start_metrics_collector
2122
from pegaprox.background.syslog_server import DB_FILE, SEVERITY_MAP
2223
from pegaprox.api.schedules import start_scheduler
@@ -57,6 +58,53 @@ def _syslog_like_clause(search_text):
5758
[like, like, like, like, like, like],
5859
)
5960

61+
62+
def _syslog_hostname_tokens(value):
63+
value = str(value or '').strip().lower()
64+
if not value:
65+
return set()
66+
if '://' in value:
67+
parsed = urlparse(value)
68+
value = parsed.hostname or value
69+
value = value.split('/')[0].split('@')[-1]
70+
if value.startswith('[') and ']' in value:
71+
value = value[1:value.index(']')]
72+
elif ':' in value and value.count(':') == 1:
73+
value = value.rsplit(':', 1)[0]
74+
value = value.strip('.')
75+
if not value:
76+
return set()
77+
tokens = {value}
78+
if '.' in value:
79+
tokens.add(value.split('.', 1)[0])
80+
return tokens
81+
82+
83+
def _syslog_cluster_hostnames(cluster_id):
84+
manager = cluster_managers.get(cluster_id)
85+
if not manager:
86+
return set()
87+
88+
hostnames = set()
89+
config = getattr(manager, 'config', None)
90+
for value in (
91+
getattr(manager, 'host', ''),
92+
getattr(config, 'host', '') if config else '',
93+
getattr(config, 'name', '') if config else '',
94+
):
95+
hostnames.update(_syslog_hostname_tokens(value))
96+
97+
try:
98+
node_status = manager.get_node_status() or {}
99+
for node_name in node_status.keys():
100+
hostnames.update(_syslog_hostname_tokens(node_name))
101+
except Exception as exc:
102+
logging.debug(f"[Syslog] Could not load nodes for cluster filter {cluster_id}: {exc}")
103+
for node_name in getattr(manager, 'ha_node_status', {}).keys():
104+
hostnames.update(_syslog_hostname_tokens(node_name))
105+
106+
return hostnames
107+
60108
@bp.route('/api/reports/summary', methods=['GET'])
61109
@require_auth()
62110
def get_reports_summary():
@@ -180,6 +228,7 @@ def get_integrated_syslog_events():
180228
hostname = (request.args.get('hostname') or '').strip()
181229
source_ip = (request.args.get('source_ip') or '').strip()
182230
facility = (request.args.get('facility') or '').strip()
231+
cluster_id = (request.args.get('cluster_id') or '').strip()
183232

184233
sort_map = {
185234
'id': 'logs.id',
@@ -267,6 +316,22 @@ def get_integrated_syslog_events():
267316
except ValueError:
268317
pass
269318

319+
if cluster_id and load_server_settings().get('syslog_filter_by_selected_cluster', False):
320+
ok, err = check_cluster_access(cluster_id)
321+
if not ok:
322+
return err
323+
cluster_hostnames = sorted(_syslog_cluster_hostnames(cluster_id))
324+
if cluster_hostnames:
325+
cluster_hostname_where = []
326+
for value in cluster_hostnames:
327+
cluster_hostname_where.append("LOWER(logs.hostname) = ?")
328+
params.append(value)
329+
cluster_hostname_where.append("LOWER(logs.hostname) LIKE ?")
330+
params.append(f"{value}.%")
331+
where.append(f"({' OR '.join(cluster_hostname_where)})")
332+
else:
333+
where.append("1 = 0")
334+
270335
where_sql = f"WHERE {' AND '.join(where)}" if where else ''
271336
joins_sql = f"{' '.join(joins)}" if joins else ''
272337
offset = (page - 1) * per_page

pegaprox/api/settings.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,12 @@ def update_server_settings():
11521152
settings['alert_update_available'] = bool(data['alert_update_available'])
11531153
if 'metrics_public' in data:
11541154
settings['metrics_public'] = bool(data['metrics_public'])
1155+
if 'syslog_filter_by_selected_cluster' in data:
1156+
old_val = settings.get('syslog_filter_by_selected_cluster', False)
1157+
settings['syslog_filter_by_selected_cluster'] = bool(data['syslog_filter_by_selected_cluster'])
1158+
if old_val != settings['syslog_filter_by_selected_cluster']:
1159+
log_audit(request.session.get('user', 'admin'), 'settings.syslog',
1160+
f"Syslog cluster hostname filter {'enabled' if settings['syslog_filter_by_selected_cluster'] else 'disabled'}")
11551161
if 'strict_session_ip' in data:
11561162
settings['strict_session_ip'] = bool(data['strict_session_ip'])
11571163

@@ -1381,6 +1387,12 @@ def update_server_settings():
13811387
settings['alert_cooldown'] = max(60, min(86400, int(request.form['alert_cooldown'])))
13821388
if 'alert_update_available' in request.form:
13831389
settings['alert_update_available'] = request.form['alert_update_available'] in ('true', '1', 'on')
1390+
if 'syslog_filter_by_selected_cluster' in request.form:
1391+
old_syslog_filter = settings.get('syslog_filter_by_selected_cluster', False)
1392+
settings['syslog_filter_by_selected_cluster'] = request.form['syslog_filter_by_selected_cluster'] in ('true', '1', 'on')
1393+
if old_syslog_filter != settings['syslog_filter_by_selected_cluster']:
1394+
log_audit(request.session.get('user', 'admin'), 'settings.syslog',
1395+
f"Syslog cluster hostname filter {'enabled' if settings['syslog_filter_by_selected_cluster'] else 'disabled'}")
13841396

13851397
# Handle certificate upload
13861398
if 'ssl_cert' in request.files:
@@ -4574,4 +4586,3 @@ def test_ldap():
45744586
return jsonify(results)
45754587

45764588

4577-

0 commit comments

Comments
 (0)