#!/usr/bin/env python3 """ Crée le dashboard "API Go - Performance & Trafic" dans OpenSearch Dashboards (Wazuh). Usage: docker cp create-dashboard-go-api.py wazuh_dashboard:/tmp/ docker exec wazuh_dashboard python3 /tmp/create-dashboard-go-api.py Données utilisées : - rule 100700 → toute ligne backend Go - rule 100701 → requête HTTP Gin (tous codes) - rule 100702 → erreur 4xx - rule 100703 → erreur 5xx - rule 100704 → panic / fatal - fields: data.id (code HTTP), data.extra_data (temps réponse), data.srcip (IP client), data.protocol (GET/POST...), data.url (endpoint), agent.name (prod-mln / pre-prod-mln) """ 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 ──────────────────────────────────────────────────────────────────── Q_ALL = "rule.id: 100700 OR rule.id: 100701 OR rule.id: 100702 OR rule.id: 100703 OR rule.id: 100704" Q_HTTP = "rule.id: 100701 OR rule.id: 100702 OR rule.id: 100703" Q_4XX = "rule.id: 100702" Q_5XX = "rule.id: 100703" Q_PANIC = "rule.id: 100704" # ── 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 requêtes HTTP ───────────────────────────────────── { "id": "go-api-total-requests", "title": "[API Go] Total Requetes HTTP", "type": "metric", "query": Q_HTTP, "visState": { "type": "metric", "params": { "metric": { "percentageMode": False, "useRanges": False, "colorSchema": "Blues", "metricColorMode": "None", "colorsRange": [{"from": 0, "to": 99999999}], "labels": {"show": True}, "invertColors": False, "style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "requetes loggees", "fontSize": 60}, } }, "aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}], }, }, # ── 2. Métrique : erreurs 4xx ───────────────────────────────────────────── { "id": "go-api-4xx", "title": "[API Go] Erreurs 4xx", "type": "metric", "query": Q_4XX, "visState": { "type": "metric", "params": { "metric": { "percentageMode": False, "useRanges": False, "colorSchema": "Yellow to Red", "metricColorMode": "Labels", "colorsRange": [{"from": 0, "to": 50}, {"from": 50, "to": 500}, {"from": 500, "to": 99999}], "labels": {"show": True}, "invertColors": False, "style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "erreurs client 4xx", "fontSize": 60}, } }, "aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}], }, }, # ── 3. Métrique : erreurs 5xx ───────────────────────────────────────────── { "id": "go-api-5xx", "title": "[API Go] Erreurs 5xx", "type": "metric", "query": Q_5XX, "visState": { "type": "metric", "params": { "metric": { "percentageMode": False, "useRanges": False, "colorSchema": "Green to Red", "metricColorMode": "Labels", "colorsRange": [{"from": 0, "to": 1}, {"from": 1, "to": 20}, {"from": 20, "to": 99999}], "labels": {"show": True}, "invertColors": False, "style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "erreurs serveur 5xx", "fontSize": 60}, } }, "aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}], }, }, # ── 4. Timeline : requêtes par heure (prod vs pre-prod) ─────────────────── { "id": "go-api-timeline", "title": "[API Go] Timeline Requetes par Serveur", "type": "histogram", "query": Q_HTTP, "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": "Requetes"}}], "seriesParams": [{"show": True, "type": "histogram", "mode": "stacked", "data": {"label": "Requetes", "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": "agent.name", "size": 3, "order": "desc", "orderBy": "1", "otherBucket": False}}, ], }, }, # ── 5. Pie : distribution codes HTTP ────────────────────────────────────── { "id": "go-api-http-codes", "title": "[API Go] Distribution Codes HTTP", "type": "pie", "query": Q_HTTP, "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": "data.id", "size": 10, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}}, ], }, }, # ── 6. Pie : méthodes HTTP ──────────────────────────────────────────────── { "id": "go-api-methods", "title": "[API Go] Methodes HTTP", "type": "pie", "query": Q_HTTP, "visState": { "type": "pie", "params": { "type": "pie", "addTooltip": True, "addLegend": True, "legendPosition": "right", "isDonut": False, "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": "data.protocol", "size": 8, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}}, ], }, }, # ── 7. Bar horizontal : top endpoints ──────────────────────────────────── { "id": "go-api-top-endpoints", "title": "[API Go] Top Endpoints", "type": "horizontal_bar", "query": Q_HTTP, "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": 300}, "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": "Requetes"}}], "seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "stacked", "data": {"label": "Requetes", "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.url", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}}, {"id": "3", "enabled": True, "type": "terms", "schema": "group", "params": {"field": "data.id", "size": 4, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres"}}, ], }, }, # ── 8. Bar horizontal : top endpoints en erreur ─────────────────────────── { "id": "go-api-error-endpoints", "title": "[API Go] Endpoints en Erreur (4xx/5xx)", "type": "horizontal_bar", "query": Q_4XX + " OR " + Q_5XX, "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": 300}, "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": "Erreurs"}}], "seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "stacked", "data": {"label": "Erreurs", "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.url", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}}, {"id": "3", "enabled": True, "type": "terms", "schema": "group", "params": {"field": "data.id", "size": 3, "order": "desc", "orderBy": "1", "otherBucket": False}}, ], }, }, # ── 9. Bar horizontal : top IPs clientes ────────────────────────────────── { "id": "go-api-top-clients", "title": "[API Go] Top IPs Clientes", "type": "horizontal_bar", "query": Q_HTTP, "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": "Requetes"}}], "seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "normal", "data": {"label": "Requetes", "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": 15, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}}, ], }, }, # ── 10. Table : erreurs critiques (panic / fatal) ────────────────────────── { "id": "go-api-panics", "title": "[API Go] Erreurs Critiques (panic/fatal)", "type": "table", "query": Q_PANIC, "visState": { "type": "table", "params": { "perPage": 15, "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": "agent.name", "size": 5, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}}, {"id": "3", "enabled": True, "type": "date_histogram", "schema": "bucket", "params": {"field": "@timestamp", "interval": "1d", "min_doc_count": 1, "extended_bounds": {}}}, ], }, "uiStateJSON": json.dumps({"vis": {"params": {"sort": {"columnIndex": 0, "direction": "desc"}}}}), }, ] # ── Layout dashboard (grille 48 colonnes) ────────────────────────────────────── # Ligne 0 : 3 métriques (total | 4xx | 5xx) # Ligne 6 : timeline requêtes (pleine largeur) # Ligne 18 : pie codes HTTP + pie méthodes # Ligne 34 : top endpoints (pleine largeur) # Ligne 50 : endpoints en erreur (gauche) + top clients (droite) # Ligne 66 : table panics (pleine largeur) PANELS = [ {"panelIndex": "1", "gridData": {"x": 0, "y": 0, "w": 16, "h": 6, "i": "1"}, "version": "2.19.5", "type": "visualization", "id": "go-api-total-requests", "embeddableConfig": {}}, {"panelIndex": "2", "gridData": {"x": 16, "y": 0, "w": 16, "h": 6, "i": "2"}, "version": "2.19.5", "type": "visualization", "id": "go-api-4xx", "embeddableConfig": {}}, {"panelIndex": "3", "gridData": {"x": 32, "y": 0, "w": 16, "h": 6, "i": "3"}, "version": "2.19.5", "type": "visualization", "id": "go-api-5xx", "embeddableConfig": {}}, {"panelIndex": "4", "gridData": {"x": 0, "y": 6, "w": 48, "h": 12, "i": "4"}, "version": "2.19.5", "type": "visualization", "id": "go-api-timeline", "embeddableConfig": {}}, {"panelIndex": "5", "gridData": {"x": 0, "y": 18, "w": 24, "h": 16, "i": "5"}, "version": "2.19.5", "type": "visualization", "id": "go-api-http-codes", "embeddableConfig": {}}, {"panelIndex": "6", "gridData": {"x": 24, "y": 18, "w": 24, "h": 16, "i": "6"}, "version": "2.19.5", "type": "visualization", "id": "go-api-methods", "embeddableConfig": {}}, {"panelIndex": "7", "gridData": {"x": 0, "y": 34, "w": 48, "h": 16, "i": "7"}, "version": "2.19.5", "type": "visualization", "id": "go-api-top-endpoints", "embeddableConfig": {}}, {"panelIndex": "8", "gridData": {"x": 0, "y": 50, "w": 28, "h": 16, "i": "8"}, "version": "2.19.5", "type": "visualization", "id": "go-api-error-endpoints", "embeddableConfig": {}}, {"panelIndex": "9", "gridData": {"x": 28, "y": 50, "w": 20, "h": 16, "i": "9"}, "version": "2.19.5", "type": "visualization", "id": "go-api-top-clients", "embeddableConfig": {}}, {"panelIndex": "10", "gridData": {"x": 0, "y": 66, "w": 48, "h": 12, "i": "10"}, "version": "2.19.5", "type": "visualization", "id": "go-api-panics", "embeddableConfig": {}}, ] # ── Main ─────────────────────────────────────────────────────────────────────── def main(): print("=== Creation dashboard: API Go - Performance & Trafic ===\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": "API Go - Performance et Trafic", "description": "Requetes HTTP Gin, codes de statut, endpoints, temps de reponse, erreurs 4xx/5xx, panics", "panelsJSON": json.dumps(PANELS), "optionsJSON": json.dumps({"useMargins": True, "hidePanelTitles": False}), "timeRestore": False, "kibanaSavedObjectMeta": { "searchSourceJSON": json.dumps({"query": {"language": "kuery", "query": ""}, "filter": []}) }, } } r = api("POST", "/api/saved_objects/dashboard/go-api-performance-dashboard?overwrite=true", dashboard_body) if "id" in r: print(f"\n OK Dashboard: {r['id']}") print(f" URL: https:///#/app/dashboards#/view/{r['id']}") else: print(f"\n ERR Dashboard: {r}") sys.exit(1) print("\n=== Termine ===") if __name__ == "__main__": main()