chore: update
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Crée le dashboard "VPN - Activite WireGuard" dans OpenSearch Dashboards (Wazuh).
|
||||
|
||||
Usage:
|
||||
docker cp create-dashboard-vpn.py wazuh_dashboard:/tmp/
|
||||
docker exec wazuh_dashboard python3 /tmp/create-dashboard-vpn.py
|
||||
|
||||
Données utilisées :
|
||||
- rules 100801-100803 → handshakes WireGuard
|
||||
- rules 100810-100812 → timeouts / déconnexions
|
||||
- rule 100815 → peer roaming
|
||||
- fields: data.srcip (IP peer), data.id (peer number), data.extra_data (retry)
|
||||
- agent.name: vpn-prod
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import ssl
|
||||
import base64
|
||||
import sys
|
||||
import os
|
||||
|
||||
# ── Configuration ──────────────────────────────────────────────────────────────
|
||||
DASHBOARD_HOST = "https://localhost:5601"
|
||||
DASHBOARD_USER = "kibanaserver"
|
||||
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
|
||||
INDEX_PATTERN = "wazuh-alerts-*"
|
||||
|
||||
# ── Queries ────────────────────────────────────────────────────────────────────
|
||||
# Note: pour un peer de la liste de confiance (etc/lists/wireguard-trusted-ips),
|
||||
# analysisd indexe l'alerte sous le rule.id de la regle enfant 1082x (voir
|
||||
# wireguard-rules.xml), pas celui du parent 1008xx. Il faut donc inclure les
|
||||
# deux jeux d'IDs partout, sinon les peers de confiance disparaissent des stats.
|
||||
Q_ALL = ("rule.id: 100801 OR rule.id: 100802 OR rule.id: 100803 OR rule.id: 100810 "
|
||||
"OR rule.id: 100811 OR rule.id: 100812 OR rule.id: 100815 OR rule.id: 100816 "
|
||||
"OR rule.id: 100821 OR rule.id: 100822 OR rule.id: 100823 OR rule.id: 100825 OR rule.id: 100826")
|
||||
Q_HANDSHAKES = ("rule.id: 100801 OR rule.id: 100802 OR rule.id: 100803 "
|
||||
"OR rule.id: 100821 OR rule.id: 100822 OR rule.id: 100823")
|
||||
Q_WARNINGS = "rule.id: 100810 OR rule.id: 100811 OR rule.id: 100812"
|
||||
# Peers actifs : cardinalite sur data.srcip, tous rule.id de handshake confondus
|
||||
# (100802/100822 = reponse envoyee, le cas dominant sur un serveur qui recoit les
|
||||
# connexions ; 100803/100823 = reponse recue, cote initiateur).
|
||||
Q_COMPLETE = Q_HANDSHAKES
|
||||
|
||||
# ── Client HTTP ────────────────────────────────────────────────────────────────
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
|
||||
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
|
||||
_headers = {
|
||||
"Content-Type": "application/json",
|
||||
"osd-xsrf": "true",
|
||||
"Authorization": f"Basic {_auth}",
|
||||
}
|
||||
|
||||
def api(method, path, body=None):
|
||||
data = json.dumps(body).encode() if body else None
|
||||
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
|
||||
return json.loads(r.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
return {"error": e.code, "msg": e.read().decode()[:300]}
|
||||
|
||||
def search_src(query):
|
||||
return json.dumps({
|
||||
"index": INDEX_PATTERN,
|
||||
"filter": [],
|
||||
"query": {"language": "kuery", "query": query},
|
||||
})
|
||||
|
||||
# ── Visualisations ─────────────────────────────────────────────────────────────
|
||||
VISUALIZATIONS = [
|
||||
# ── 1. Métrique : total handshakes (24h) ──────────────────────────────────
|
||||
{
|
||||
"id": "vpn-total-handshakes",
|
||||
"title": "[VPN] Handshakes (24h)",
|
||||
"type": "metric",
|
||||
"query": Q_HANDSHAKES,
|
||||
"visState": {
|
||||
"type": "metric",
|
||||
"params": {
|
||||
"metric": {
|
||||
"percentageMode": False, "useRanges": False,
|
||||
"colorSchema": "Green to Red",
|
||||
"metricColorMode": "None",
|
||||
"colorsRange": [{"from": 0, "to": 9999999}],
|
||||
"labels": {"show": True},
|
||||
"invertColors": False,
|
||||
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
|
||||
"subText": "handshakes VPN", "fontSize": 60},
|
||||
}
|
||||
},
|
||||
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
|
||||
},
|
||||
},
|
||||
# ── 2. Métrique : alertes déconnexion ─────────────────────────────────────
|
||||
{
|
||||
"id": "vpn-disconnects",
|
||||
"title": "[VPN] Alertes Deconnexion",
|
||||
"type": "metric",
|
||||
"query": Q_WARNINGS,
|
||||
"visState": {
|
||||
"type": "metric",
|
||||
"params": {
|
||||
"metric": {
|
||||
"percentageMode": False, "useRanges": False,
|
||||
"colorSchema": "Green to Red",
|
||||
"metricColorMode": "Labels",
|
||||
"colorsRange": [{"from": 0, "to": 1}, {"from": 1, "to": 10}, {"from": 10, "to": 99999}],
|
||||
"labels": {"show": True},
|
||||
"invertColors": False,
|
||||
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
|
||||
"subText": "timeouts / deconnexions", "fontSize": 60},
|
||||
}
|
||||
},
|
||||
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
|
||||
},
|
||||
},
|
||||
# ── 3. Métrique : peers actifs distincts ──────────────────────────────────
|
||||
{
|
||||
"id": "vpn-active-peers",
|
||||
"title": "[VPN] Peers Actifs (distincts)",
|
||||
"type": "metric",
|
||||
"query": Q_COMPLETE,
|
||||
"visState": {
|
||||
"type": "metric",
|
||||
"params": {
|
||||
"metric": {
|
||||
"percentageMode": False, "useRanges": False,
|
||||
"colorSchema": "Blues",
|
||||
"metricColorMode": "None",
|
||||
"colorsRange": [{"from": 0, "to": 99999}],
|
||||
"labels": {"show": True},
|
||||
"invertColors": False,
|
||||
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
|
||||
"subText": "IPs peers uniques", "fontSize": 60},
|
||||
}
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "cardinality", "schema": "metric",
|
||||
"params": {"field": "data.srcip"}},
|
||||
],
|
||||
},
|
||||
},
|
||||
# ── 4. Timeline : activité WireGuard ──────────────────────────────────────
|
||||
{
|
||||
"id": "vpn-timeline",
|
||||
"title": "[VPN] Timeline Activite WireGuard",
|
||||
"type": "histogram",
|
||||
"query": Q_ALL,
|
||||
"visState": {
|
||||
"type": "histogram",
|
||||
"params": {
|
||||
"type": "histogram",
|
||||
"grid": {"categoryLines": False},
|
||||
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom",
|
||||
"show": True, "style": {}, "scale": {"type": "linear"},
|
||||
"labels": {"show": True, "truncate": 100}, "title": {}}],
|
||||
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
|
||||
"position": "left", "show": True, "style": {},
|
||||
"scale": {"type": "linear", "mode": "normal"},
|
||||
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
|
||||
"title": {"text": "Evenements"}}],
|
||||
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked",
|
||||
"data": {"label": "Evenements", "id": "1"},
|
||||
"valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True,
|
||||
"showCircles": True}],
|
||||
"addTooltip": True, "addLegend": True, "legendPosition": "right",
|
||||
"times": [], "addTimeMarker": False,
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
|
||||
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment",
|
||||
"params": {"field": "@timestamp", "interval": "auto", "min_doc_count": 1, "extended_bounds": {}}},
|
||||
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
|
||||
"params": {"field": "rule.description", "size": 5, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": True, "otherBucketLabel": "Autres"}},
|
||||
],
|
||||
},
|
||||
},
|
||||
# ── 5. Bar horizontal : événements par peer (IP) ──────────────────────────
|
||||
{
|
||||
"id": "vpn-events-per-peer",
|
||||
"title": "[VPN] Evenements par Peer",
|
||||
"type": "horizontal_bar",
|
||||
"query": Q_ALL,
|
||||
"visState": {
|
||||
"type": "horizontal_bar",
|
||||
"params": {
|
||||
"type": "horizontal_bar",
|
||||
"grid": {"categoryLines": False},
|
||||
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "left",
|
||||
"show": True, "style": {},
|
||||
"labels": {"show": True, "rotate": 0, "filter": True, "truncate": 200},
|
||||
"title": {}}],
|
||||
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
|
||||
"position": "bottom", "show": True, "style": {},
|
||||
"scale": {"type": "linear", "mode": "normal"},
|
||||
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
|
||||
"title": {"text": "Evenements"}}],
|
||||
"seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "stacked",
|
||||
"data": {"label": "Evenements", "id": "1"},
|
||||
"valueAxis": "ValueAxis-1"}],
|
||||
"addTooltip": True, "addLegend": True, "legendPosition": "right",
|
||||
"times": [], "addTimeMarker": False,
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
|
||||
{"id": "2", "enabled": True, "type": "terms", "schema": "segment",
|
||||
"params": {"field": "data.srcip", "size": 10, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": False, "missingBucket": False}},
|
||||
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
|
||||
"params": {"field": "rule.description", "size": 4, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": True, "otherBucketLabel": "Autres"}},
|
||||
],
|
||||
},
|
||||
},
|
||||
# ── 6. Pie : types d'événements ───────────────────────────────────────────
|
||||
{
|
||||
"id": "vpn-event-types",
|
||||
"title": "[VPN] Types d Evenements",
|
||||
"type": "pie",
|
||||
"query": Q_ALL,
|
||||
"visState": {
|
||||
"type": "pie",
|
||||
"params": {
|
||||
"type": "pie", "addTooltip": True, "addLegend": True,
|
||||
"legendPosition": "right", "isDonut": True,
|
||||
"labels": {"show": True, "values": True, "last_level": True, "truncate": 100},
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
|
||||
{"id": "2", "enabled": True, "type": "terms", "schema": "segment",
|
||||
"params": {"field": "rule.description", "size": 8, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}},
|
||||
],
|
||||
},
|
||||
},
|
||||
# ── 7. Timeline : timeouts et déconnexions ────────────────────────────────
|
||||
{
|
||||
"id": "vpn-timeout-timeline",
|
||||
"title": "[VPN] Timeouts et Deconnexions",
|
||||
"type": "histogram",
|
||||
"query": Q_WARNINGS,
|
||||
"visState": {
|
||||
"type": "histogram",
|
||||
"params": {
|
||||
"type": "histogram",
|
||||
"grid": {"categoryLines": False},
|
||||
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom",
|
||||
"show": True, "style": {},
|
||||
"labels": {"show": True, "truncate": 100}, "title": {}}],
|
||||
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
|
||||
"position": "left", "show": True, "style": {},
|
||||
"scale": {"type": "linear", "mode": "normal"},
|
||||
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
|
||||
"title": {"text": "Alertes"}}],
|
||||
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked",
|
||||
"data": {"label": "Alertes", "id": "1"},
|
||||
"valueAxis": "ValueAxis-1"}],
|
||||
"addTooltip": True, "addLegend": True, "legendPosition": "right",
|
||||
"times": [], "addTimeMarker": False,
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
|
||||
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment",
|
||||
"params": {"field": "@timestamp", "interval": "auto", "min_doc_count": 1, "extended_bounds": {}}},
|
||||
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
|
||||
"params": {"field": "data.srcip", "size": 6, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": False}},
|
||||
],
|
||||
},
|
||||
},
|
||||
# ── 8. Table : log des événements VPN ─────────────────────────────────────
|
||||
{
|
||||
"id": "vpn-events-table",
|
||||
"title": "[VPN] Journal Evenements VPN",
|
||||
"type": "table",
|
||||
"query": Q_ALL,
|
||||
"visState": {
|
||||
"type": "table",
|
||||
"params": {
|
||||
"perPage": 20, "showPartialRows": False, "showMetricsAtAllLevels": False,
|
||||
"sort": {"columnIndex": None, "direction": None},
|
||||
"showTotal": False, "totalFunc": "sum",
|
||||
},
|
||||
"aggs": [
|
||||
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
|
||||
{"id": "2", "enabled": True, "type": "terms", "schema": "bucket",
|
||||
"params": {"field": "data.srcip", "size": 20, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": False, "missingBucket": True, "missingBucketLabel": "inconnu"}},
|
||||
{"id": "3", "enabled": True, "type": "terms", "schema": "bucket",
|
||||
"params": {"field": "rule.description", "size": 5, "order": "desc", "orderBy": "1",
|
||||
"otherBucket": False, "missingBucket": False}},
|
||||
],
|
||||
},
|
||||
"uiStateJSON": json.dumps({"vis": {"params": {"sort": {"columnIndex": 0, "direction": "desc"}}}}),
|
||||
},
|
||||
]
|
||||
|
||||
# ── Layout dashboard (grille 48 colonnes) ──────────────────────────────────────
|
||||
# Ligne 0 : 3 métriques
|
||||
# Ligne 6 : timeline activité (pleine largeur)
|
||||
# Ligne 18: events par peer (gauche) + types d'événements (droite)
|
||||
# Ligne 34: timeline timeouts (gauche) + table journal (droite)
|
||||
PANELS = [
|
||||
{"panelIndex": "1", "gridData": {"x": 0, "y": 0, "w": 16, "h": 6, "i": "1"}, "version": "2.19.5", "type": "visualization", "id": "vpn-total-handshakes", "embeddableConfig": {}},
|
||||
{"panelIndex": "2", "gridData": {"x": 16, "y": 0, "w": 16, "h": 6, "i": "2"}, "version": "2.19.5", "type": "visualization", "id": "vpn-disconnects", "embeddableConfig": {}},
|
||||
{"panelIndex": "3", "gridData": {"x": 32, "y": 0, "w": 16, "h": 6, "i": "3"}, "version": "2.19.5", "type": "visualization", "id": "vpn-active-peers", "embeddableConfig": {}},
|
||||
{"panelIndex": "4", "gridData": {"x": 0, "y": 6, "w": 48, "h": 12, "i": "4"}, "version": "2.19.5", "type": "visualization", "id": "vpn-timeline", "embeddableConfig": {}},
|
||||
{"panelIndex": "5", "gridData": {"x": 0, "y": 18, "w": 30, "h": 16, "i": "5"}, "version": "2.19.5", "type": "visualization", "id": "vpn-events-per-peer", "embeddableConfig": {}},
|
||||
{"panelIndex": "6", "gridData": {"x": 30, "y": 18, "w": 18, "h": 16, "i": "6"}, "version": "2.19.5", "type": "visualization", "id": "vpn-event-types", "embeddableConfig": {}},
|
||||
{"panelIndex": "7", "gridData": {"x": 0, "y": 34, "w": 24, "h": 14, "i": "7"}, "version": "2.19.5", "type": "visualization", "id": "vpn-timeout-timeline", "embeddableConfig": {}},
|
||||
{"panelIndex": "8", "gridData": {"x": 24, "y": 34, "w": 24, "h": 14, "i": "8"}, "version": "2.19.5", "type": "visualization", "id": "vpn-events-table", "embeddableConfig": {}},
|
||||
]
|
||||
|
||||
# ── Main ───────────────────────────────────────────────────────────────────────
|
||||
def main():
|
||||
print("=== Creation dashboard: VPN - Activite WireGuard ===\n")
|
||||
|
||||
for viz in VISUALIZATIONS:
|
||||
vis_state = dict(viz["visState"])
|
||||
vis_state["title"] = viz["title"]
|
||||
body = {
|
||||
"attributes": {
|
||||
"title": viz["title"],
|
||||
"visState": json.dumps(vis_state),
|
||||
"uiStateJSON": viz.get("uiStateJSON", "{}"),
|
||||
"description": "",
|
||||
"kibanaSavedObjectMeta": {"searchSourceJSON": search_src(viz["query"])},
|
||||
}
|
||||
}
|
||||
r = api("POST", f"/api/saved_objects/visualization/{viz['id']}?overwrite=true", body)
|
||||
if "id" in r:
|
||||
print(f" OK {viz['title']}")
|
||||
else:
|
||||
print(f" ERR {viz['title']}: {r}")
|
||||
sys.exit(1)
|
||||
|
||||
dashboard_body = {
|
||||
"attributes": {
|
||||
"title": "VPN - Activite WireGuard",
|
||||
"description": "Handshakes peers, deconnexions, timeouts, activite par IP — agent vpn-prod",
|
||||
"panelsJSON": json.dumps(PANELS),
|
||||
"optionsJSON": json.dumps({"useMargins": True, "hidePanelTitles": False}),
|
||||
"timeRestore": True,
|
||||
"timeFrom": "now-24h",
|
||||
"timeTo": "now",
|
||||
"kibanaSavedObjectMeta": {
|
||||
"searchSourceJSON": json.dumps({"query": {"language": "kuery", "query": ""}, "filter": []})
|
||||
},
|
||||
}
|
||||
}
|
||||
r = api("POST", "/api/saved_objects/dashboard/vpn-wireguard-dashboard?overwrite=true", dashboard_body)
|
||||
if "id" in r:
|
||||
print(f"\n OK Dashboard: {r['id']}")
|
||||
print(f" URL: https://<monitoring-ip>/#/app/dashboards#/view/{r['id']}")
|
||||
else:
|
||||
print(f"\n ERR Dashboard: {r}")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n=== Termine ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user