Schreiben des Grafana-Reverse-Proxys auf Go


Eigentlich wollte ich den Artikel "Proxy-Service on Go in 3 Zeilen" nennen, aber ich stehe darüber.


Tatsächlich kann die Hauptlogik in drei Zeilen passen. Für Ungeduldige und diejenigen, die das Wesentliche sehen wollen:


proxy := httputil.NewSingleHostReverseProxy(url) r.Header.Set(header, value) proxy.ServeHTTP(w, r) 

Unter der Katze ist eine detailliertere Geschichte für Neulinge in die Golang-Sprache und diejenigen, die in kürzester Zeit einen Reverse-Proxy erstellen müssen.


Lassen Sie uns herausfinden, warum ein Proxy-Service benötigt wird, wie er implementiert wird und was sich unter der Haube der Standardbibliothek befindet.


Reverse Proxy


Ein Reverse-Proxy ist ein Proxyservertyp, der eine Anforderung von einem Client empfängt, an einen oder mehrere Server umleitet und die Antwort zurückleitet.


Eine Besonderheit des Reverse-Proxys besteht darin, dass es der Einstiegspunkt für die Verbindung des Benutzers mit Servern ist, mit denen der Proxy selbst über eine Geschäftslogik verbunden ist. Hiermit wird festgelegt, an welche Server die Clientanforderung gesendet wird. Die Logik zum Aufbau eines Netzwerks hinter dem Proxy bleibt dem Benutzer verborgen.



Reverse Proxy


Zum Vergleich verbindet ein regulärer Proxy seine Clients mit jedem Server, den sie benötigen. In diesem Fall befindet sich der Proxy vor dem Benutzer und ist lediglich ein Vermittler bei der Ausführung der Anforderung.



Normaler Proxy (Forward Proxy)


Warum verwenden
Das Reverse-Proxy-Konzept kann in verschiedenen Situationen angewendet werden:
- Lastausgleich,
- A / B-Tests
- Ressourcen-Caching,
- Komprimierung der Anforderungsdaten,
- Verkehrsfilterung,
- Genehmigung.


Natürlich ist der Geltungsbereich nicht auf diese sechs Punkte beschränkt. Die Möglichkeit, die Anfrage sowohl vor als auch nach dem Proxying zu bearbeiten, gibt viel Raum für Kreativität. In diesem Artikel wird die Verwendung des Reverse-Proxys für die Autorisierung erläutert.


Herausforderung


Wir entwickeln ein Virtualisierungs-Control-Panel für VMmanager 6. Eines schönen Tages beschlossen wir, den Benutzern mehr Freiheit bei der Überwachung und Visualisierung dieser Cluster zu geben. Für diese Zwecke entschieden sie sich für Grafana .


Damit Grafana mit unseren Daten arbeiten konnte, musste die Autorisierung konfiguriert werden. Es ist nicht schwer, dies zu tun, wenn nicht für drei "Aber".


  1. Wir haben bereits einen einzigen Einstiegspunkt - einen Autorisierungsservice.
  2. Wir möchten keine Benutzer in Grafana starten und autorisieren.
  3. Wir möchten den Benutzern keinen direkten Zugriff auf Grafana gewähren.

Um die Bedingungen zu erfüllen, haben wir uns entschlossen, Grafana in das interne Netzwerk einzubinden und einen Reverse-Proxy zu schreiben. Er wird die Rechte im Berechtigungsservice prüfen und erst danach die Anfrage an Grafana weiterleiten.


Idee


Die Hauptidee ist, die Verantwortung für die Autorisierung in Grafana auf den Reverse-Proxy-Server zu übertragen ( offizielle Dokumentation ). Grafana akzeptiert jede Anfrage als autorisiert, wenn sie einen bestimmten Header enthält. Bevor wir diese Überschrift ersetzen, müssen wir sicherstellen, dass der aktuelle Benutzer das Recht hat, mit Grafana zu arbeiten.



Anrufkette "Grafana-Proxy oder Round-Trip"


Implementierung


Die Hauptfunktion ist ziemlich normal. Wir starten den http-Server, der Verbindungen auf dem 4000-Port akzeptiert und alle "/" -Adressen verarbeitet, mit denen die Verbindung hergestellt werden soll.


 func main() { http.HandleFunc("/", handlerProxy) if err := http.ListenAndServe(":4000", nil); err != nil { panic(err) } } 

Der Großteil der Arbeit findet im Request-Handler statt.


[vollständiger Code unter Schnitt]
 func handlerProxy(w http.ResponseWriter, r *http.Request) { fmt.Println(r.URL.Host) if strings.HasPrefix(r.URL.String(), "/api") { //     } url, err := url.Parse(fmt.Sprintf("http://%s/", grafanaHost)) if err != nil { SendJSONError(w, err.Error()) return } proxy := httputil.NewSingleHostReverseProxy(url) fmt.Println(r.URL.Host) r.Header.Set(grafanaHeader, grafanaUser) proxy.ServeHTTP(w, r) } 

Gehen wir die Parameter durch. Die Hauptvariablen im Beispiel habe ich in Konstanten geschrieben:


 grafanaUser = "admin" //,      grafanaHost = "grafana:3000" //  grafana grafanaHeader = "X-GRAFANA-AUTH" //Header,      

Dies ist beispielsweise ausreichend, in der Praxis müssen Sie diese Werte möglicherweise voreinstellen. Sie können ihnen Proxys als Befehlszeilenparameter übergeben und sie dann mit flag oder erweiterten Paketen analysieren. Die Container-Umgebung verwendet häufig auch Umgebungsvariablen, um Dienste zu konfigurieren. Os.Getenv hilft Ihnen dabei.


Als nächstes folgt die Berechtigungsprüfung:


 if strings.HasPrefix(r.URL.String(), "/api") { err := CheckRights(r.Header) if err != nil { SendJSONError(w, err.Error()) return } } 

Wenn die Anfrage an grafana.host/api geht, prüfen wir die Nutzungsrechte des aktuellen Nutzers für Grafana. Die Überprüfung ist erforderlich, damit bei jeder GET-Anforderung eines JS-Skripts oder PNG-Symbols der Autorisierungspunkt nicht gestört wird. Wir werden statische Inhalte ohne zusätzliche Überprüfungen weiterleiten. Dazu übergeben wir die Map mit den Headern, die die Benutzersitzung enthalten, an den Autorisierungsservice. Dies kann eine reguläre GET-Anforderung sein. Das Autorisierungsdienstgerät spielt hier keine Rolle. Wenn die Autorisierungsdaten nicht passen, schließen Sie die Verbindung und geben Sie einen Fehler zurück.


Nach den Prüfungen bilden wir das Objekt des Basispfades:


 url, err := url.Parse(fmt.Sprintf("http://%s/", grafanaHost)) 

Mit dem Standardpaket httputil , das das http-Paket erweitert, bilden wir das ReverseProxy-Objekt.


 proxy := httputil.NewSingleHostReverseProxy(url) 

ReverseProxy ist ein Anforderungshandler, der eine eingehende Anforderung akzeptiert, an Grafana sendet und die Antwort an den Client zurückgibt.


Alle Anfragen werden an die Adresse "Basispfad + angeforderte URL" weitergeleitet. Wenn der Benutzer zum Adressproxy gekommen ist: 4000 / api / something, wird seine Anforderung in grafana: 3000 / api / something umgewandelt, wobei grafana: 3000 der an NewSingleHostReverseProxy übergebene Basispfad ist und / api / something die eingehende Anforderung ist.


Fügen Sie den Autorisierungsheader für Grafana hinzu und rufen Sie die ServeHTTP-Methode auf, die den Großteil der Anforderungsverarbeitung übernimmt.


 r.Header.Set(grafanaHeader, grafanaUser) proxy.ServeHTTP(w, r) 

Unter der Haube leistet ServeHTTP eine Menge Arbeit. Beispielsweise verarbeitet es den X-Forwarded-For-Header oder die 101-Server-Antwort auf eine Protokolländerung. Die Hauptarbeit der Methode besteht darin, eine Anforderung an eine zusammengesetzte Adresse zu senden und die empfangene Antwort an ResponseWriter zu übertragen.



Ergebnis


Der gesamte Code ist auf Github verfügbar .


Überprüfen Sie


Emulieren Sie unser System mit Docker. Lassen Sie uns zwei Container erstellen - Proxy und Grafana in einem Netzwerk. Wir werden keinen Autorisierungspunkt erstellen, wir glauben, dass er immer positiv beantwortet wird. Der Grafana-Container ist offline nicht verfügbar, der Proxy-Container ist an einem bestimmten Port verfügbar.


Erstellen Sie ein Netzwerk:


 docker network create --driver=bridge --subnet=192.168.0.0/16 gnet 

Erhöhen Sie den Grafana-Container mit dem konfigurierten Autorisierungsmodus über den Header:


 docker run -d --name=grafana --network=gnet -e "GF_AUTH_PROXY_ENABLED=true" -e "GF_AUTH_PROXY_HEADER_NAME=X-GRAFANA-AUTH" grafana/grafana 

Bitte beachten Sie, dass dies eine Demo und keine endgültige Konfiguration ist. Sie müssen mindestens ein Administratorkennwort festlegen und die automatische Benutzerregistrierung verhindern.


Erhöhen Sie den Reverse-Proxy:


 docker run -d --name proxy -p 4000:4000 --network=gnet grafana_proxy:latest 

Wechseln Sie im Browser zu localhost: 4000.


Toll, wir haben eine autorisierte Grafana vor uns.


Dockerfile zum Erstellen eines Containers mit einem Proxy und detailliertere Anweisungen zum Heben von Containern sind auf Github .

Source: https://habr.com/ru/post/de480446/


All Articles