deploy/selfheal-proxy.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""vektor-selfheal-proxy — injecte X-Vektor-Selfheal vers vektor-api.
Pourquoi : Grafana v12 (nouveau provisioner) n'envoie les valeurs de headers
de contact point QUE depuis secureSettings chiffrés, qu'il ne chiffre pas
lui-même au provisioning. Plutôt que stocker le secret dans Grafana, ce proxy
local (127.0.0.1:8010 -> 127.0.0.1:8011) signe chaque POST avant retransmission.
Le secret vit uniquement dans /etc/vektor/selfheal-secret (600) sur le VPS.
"""
import http.server
import os
import urllib.request
import urllib.error
SECRET_FILE = "/etc/vektor/selfheal-secret"
UPSTREAM = "http://127.0.0.1:8011"
LISTEN_PORT = 8010
HEADER = "X-Vektor-Selfheal"
def read_secret() -> str:
try:
with open(SECRET_FILE) as f:
return f.read().strip()
except OSError:
return ""
SECRET = read_secret()
class Handler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def _proxy(self, method: str) -> None:
length = int(self.headers.get("Content-Length") or 0)
body = self.rfile.read(length) if length else None
req = urllib.request.Request(UPSTREAM + self.path, data=body, method=method)
for key, value in self.headers.items():
if key.lower() in ("host", "content-length", "connection", HEADER.lower()):
continue
req.add_header(key, value)
if SECRET:
req.add_header(HEADER, SECRET)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
out_body = resp.read()
self.send_response(resp.status)
out_headers = {k: v for k, v in resp.getheaders() if k.lower() not in ("transfer-encoding", "connection")}
for k, v in out_headers.items():
self.send_header(k, v)
self.send_header("Content-Length", str(len(out_body)))
self.end_headers()
self.wfile.write(out_body)
except urllib.error.HTTPError as e:
out_body = e.read()
self.send_response(e.code)
self.send_header("Content-Length", str(len(out_body)))
self.end_headers()
self.wfile.write(out_body)
except Exception:
self.send_response(502)
self.send_header("Content-Length", "0")
self.end_headers()
def do_POST(self): # noqa: N802
self._proxy("POST")
def do_GET(self): # noqa: N802
self._proxy("GET")
def log_message(self, fmt, *args): # silence access log systemd
pass
if __name__ == "__main__":
if not SECRET:
raise SystemExit(f"secret manquant : {SECRET_FILE}")
server = http.server.ThreadingHTTPServer(("127.0.0.1", LISTEN_PORT), Handler)
server.serve_forever()