
goproxy v1.9.1
Une bibliothèque de proxy HTTP/HTTPS personnalisable pour Go prenant en charge le transfert standard, le tunneling CONNECT, l'interception TLS MITM et la modification programmatique des requêtes/réponses.
GoProxy
GoProxy est une bibliothèque permettant de créer un serveur proxy HTTP/HTTPS personnalisé en utilisant
Go (alias Golang), avec plusieurs paramètres configurables disponibles.
L'objectif de ce projet est d'offrir un serveur proxy optimisé, utilisable avec
un volume de trafic raisonnable, tout en étant personnalisable et programmable.
Le proxy lui-même est simplement un gestionnaire net/http, vous pouvez donc ajouter plusieurs
middlewares (récupération de panique, journalisation, compression, etc.) par-dessus. Il peut être
facilement intégré à toute autre bibliothèque réseau HTTP.
Pour utiliser goproxy, il faut configurer son navigateur (ou tout autre client)
pour utiliser goproxy comme proxy HTTP.
Voici comment faire dans Chrome
et dans Firefox.
Si vous décidez de commencer avec l'exemple base, l'URL à utiliser comme
proxy est localhost:8080, qui est celle par défaut dans notre exemple.
Vous devez également approuver
le certificat CA du proxy, afin d'éviter tout problème de certificat chez les clients.
Fonctionnalités
- Exécuter certaines actions uniquement sur des
hôtes spécifiques, avec une simple comparaison d'égalité ou une évaluation par expression régulière - Manipuler les
requêtesetréponsesavant de les envoyer au navigateur - Utiliser un
http.Transport personnalisépour effectuer les requêtes vers le serveur cible - Vous pouvez spécifier un
cache de certificats MITM, pour les réutiliser ensuite pour d'autres requêtes vers le même hôte, économisant ainsi du CPU. Non activé par défaut, mais vous devriez l'utiliser en production ! - Rediriger le trafic HTTP normal vers un
gestionnaire personnalisé, lorsque la cible est unchemin relatif(ex./ping) - Vous pouvez choisir le journaliseur à utiliser, en implémentant l'interface
Logger - Vous pouvez
désactiverlacanonicalisationdes en-têtes de requête HTTP, en définissantPreventCanonicalizationsur true
Modes de proxy
- Proxy HTTP classique
- HTTPS via CONNECT
- Serveur proxy HTTPS MITM (« Man in the Middle »), dans lequel le serveur génère des certificats TLS pour analyser les données de requête/réponse et effectuer des actions sur celles-ci
- Connexion proxy « détournée », où le gestionnaire configuré peut accéder aux données brutes de net.Conn
Sponsors
Votre entreprise utilise-t-elle GoProxy ? Aidez-nous à maintenir le projet en bonne santé ! Soutenir GoProxy nous permet de consacrer plus de temps aux corrections de bugs et aux nouvelles fonctionnalités. En contrepartie, si vous choisissez le plan Gold Supporter ou Enterprise, nous afficherons fièrement le logo de votre entreprise ici.
Mainteneurs
- Elazar Leibovich : Créateur du projet, Ingénieur logiciel
- Erik Pellizzon : Mainteneur, Freelance (ouvert aux collaborations !)
Si vous avez besoin d'intégrer GoProxy dans votre projet, ou si vous avez besoin de fonctionnalités personnalisées à maintenir dans votre fork, vous pouvez contacter Erik (le mainteneur actuel) par e-mail, et vous pourrez discuter ensemble de la manière dont il peut vous aider en tant que consultant indépendant rémunéré.
Contributions
Si vous rencontrez un problème, avez une suggestion, ou si vous trouvez un bug, n'hésitez pas à nous
contacter en ouvrant une issue GitHub.
Il s'agit d'un projet open source géré par des bénévoles, et nous serons ravis
de discuter de tout ce qui peut l'améliorer.
Assurez-vous de tout expliquer, y compris la raison derrière l'issue
et ce que vous souhaitez changer, afin de faciliter la compréhension du problème.
Vous pouvez également ouvrir directement une Pull Request, s'il s'agit d'une petite modification de code, mais
vous devez tout expliquer dans la description.
Si vous ouvrez une pull request nommée refactoring avec 5 000 lignes modifiées,
nous ne la fusionnerons pas... :D
Le code de ce projet est publié sous licence BSD 3-Clause,
ce qui le rend également utile pour des utilisations commerciales.
Soumettez votre étude de cas
Vous avez donc introduit et intégré GoProxy dans l'un de vos projets personnels ou dans un projet au sein de l'entreprise pour laquelle vous travaillez.
Nous serions ravis de découvrir de nouvelles solutions créatives réalisées avec cette bibliothèque,
alors n'hésitez pas à contacter le mainteneur listé ci-dessus par e-mail, pour expliquer
pourquoi vous avez trouvé ce projet utile pour vos besoins.
Si vous avez signé un accord de non-divulgation avec l'entreprise, vous
pouvez lui proposer d'écrire un article de blog sur son site web officiel à propos de
ce sujet, afin que cette information soit rendue publique à leur discrétion, et vous pourrez
partager le lien de l'article de blog avec nous :)
L'objectif des études de cas est de partager avec la communauté pourquoi tous les
contributeurs de ce projet améliorent le monde grâce à leur aide et
ce que les gens construisent en utilisant ce projet.
Linter
Le codebase utilise une vérification automatique de lint sur le code de vos Pull Requests. Avant de l'ouvrir, vous devriez vérifier que vos modifications le respectent, en exécutant le linter sur votre machine locale, afin d'éviter toute surprise.
Pour installer le linter :
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Cela créera un exécutable dans votre dossier $GOPATH/bin
($GOPATH est une variable d'environnement, généralement
sa valeur équivaut à ~/go, vérifiez sa valeur sur votre machine si vous
n'en êtes pas sûr).
Assurez-vous d'inclure le dossier bin dans le chemin de votre shell, afin de pouvoir
utiliser directement la commande golangci-lint run.
Un aperçu de GoProxy
Pour avoir un aperçu de goproxy, voici un proxy HTTP/HTTPS basique
qui se contente de transférer les données vers la destination :
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
Gestionnaire de requêtes
Cette ligne ajoutera l'en-tête X-GoProxy: yxorPoG-X à toutes les requêtes envoyées via le proxy,
avant de les transmettre à la destination :
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
Lorsque l'entrée de OnRequest() est vide, la fonction spécifiée dans DoFunc
traitera toutes les requêtes entrantes vers le proxy. Dans ce cas, elle ajoutera
un en-tête à la requête et la retournera à l'appelant.
Le proxy enverra la requête modifiée à la destination.
Vous pouvez également utiliser Do au lieu de DoFunc, si vous implémentez l'interface
spécifiée dans votre type.
⚠️ Notez que nous avons retourné une valeur nil comme réponse. Si la réponse retournée n'est pas nil, goproxy ignorera la requête et enverra la réponse spécifiée au client.
Gestionnaire de requêtes conditionnel
Refuser les connexions à www.reddit.com entre 8 et 17 heures dans le fuseau horaire local du serveur :
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
func(req *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {
resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!")
return req, resp
}
return req, nil
})
DstHostIs retourne une ReqCondition, qui est une fonction recevant un *http.Request
et retournant un booléen vérifiant si la requête satisfait la condition (et qui sera traitée).
DstHostIs("www.reddit.com") retournera une ReqCondition qui renvoie true
lorsque la requête est destinée à "www.reddit.com".
La vérification d'égalité de l'hôte est insensible à la casse, pour refléter le comportement des
résolveurs DNS, donc même si l'utilisateur saisit "www.rEdDit.com", la comparaison
satisfaira la condition.
Lorsque l'heure est entre 8h00 et 17h59, nous retournons directement
une réponse dans DoFunc(), de sorte que la destination distante ne recevra pas la
requête et que le client recevra la réponse « Don't waste your time! ».
Commençons
import "github.com/elazarl/goproxy"
Il y a quelques exemples d'utilisation du proxy dans le dossier examples, qui
couvrent les cas les plus courants. Jetez-y un œil et bonne chance !
Manipulation des requêtes et réponses
Il existe 3 types différents de gestionnaires pour manipuler le comportement du proxy, comme suit :
// handler called after receiving HTTP CONNECT from the client, and
// before proxy establishes connection with the destination host
httpsHandlers []HttpsHandler
// handler called before proxy sends HTTP request to destination host
reqHandlers []ReqHandler
// handler called after proxy receives HTTP Response from destination host,
// and before proxy forwards the Response to the client
respHandlers []RespHandler
Selon ce que vous souhaitez manipuler, voici les façons d'ajouter des gestionnaires à chacune des listes précédentes :
// Add handlers to httpsHandlers
proxy.OnRequest(some ReqConditions).HandleConnect(YourHandlerFunc())
// Add handlers to reqHandlers
proxy.OnRequest(some ReqConditions).Do(YourReqHandlerFunc())
// Add handlers to respHandlers
proxy.OnResponse(some RespConditions).Do(YourRespHandlerFunc())
Exemple :
// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase.
// Reddit URL check is case-insensitive because of (?i), so the block will work also if the user types something like rEdDit.com.
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("(?i)reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject)
// Be careful about this example! It shows you a common error that you
// need to avoid.
// This will NOT reject the HTTPS request with URL ending with .gif because,
// if the scheme is HTTPS, the proxy will receive only URL.Hostname
// and URL.Port during the HTTP CONNECT phase.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject)
// To fix the previous example, here there is the correct way to manipulate
// an HTTP request using URL.Path (target path) as a condition.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())
Gestion des erreurs
Erreur générique
Si une erreur survient lors du traitement d'une requête via le proxy, par défaut
le proxy retourne une erreur HTTP 500 (Internal Server Error) avec le message d'erreur comme contenu du corps.
Si vous souhaitez remplacer ce comportement, vous pouvez définir votre propre
RespHandler qui modifie la réponse d'erreur.
Parmi les paramètres du contexte, ctx.Error contient l'erreur survenue,
le cas échéant, ou la valeur nil, si aucune erreur n'est survenue.
Vous pouvez la gérer comme vous le souhaitez, y compris en retournant un JSON personnalisé comme corps. Exemple de gestionnaire d'erreurs :
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
var dnsError *net.DNSError
if errors.As(ctx.Error, &dnsError) {
// Do not leak our DNS server's address
dnsError.Server = "<server-redacted>"
return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusBadGateway, dnsError.Error())
}
return resp
})
Erreur de connexion
Si une erreur survient lors de l'envoi de données au serveur distant cible (ou au
client du proxy), le proxy.ConnectionErrHandler est appelé pour gérer
l'erreur, s'il est présent, sinon un gestionnaire par défaut sera utilisé.
L'erreur est passée en paramètre de fonction et non dans le contexte du proxy,
vous n'avez donc pas à vérifier le champ ctx.Error dans ce gestionnaire.
Dans ce gestionnaire, vous avez accès à la connexion brute avec le client
du proxy (en tant qu'io.Writer), vous pouvez donc envoyer toutes les données HTTP sur celle-ci,
si nécessaire, contenant les données d'erreur.
Il n'y a aucune garantie que la connexion n'ait pas déjà été fermée, donc
le Write() pourrait retourner une erreur.
La connexion sera automatiquement fermée par la bibliothèque du proxy après l'appel du
gestionnaire d'erreurs, vous n'avez donc pas à vous en soucier.
État du projet
Ce projet a été créé il y a 10 ans, et a atteint un stade de
maturité. Il peut être utilisé en toute sécurité en production, et de nombreux projets
le font déjà.
Si un changement cassant survient à l'avenir, une nouvelle version du
module Go sera publiée (ex. v2).