Code Coverage Go Report Card GolangCI Go Doc
This is a customizable HTTP proxy with TLS interception support. It was created as a part of AdGuard Home. However, it can be used for different purposes so we decided to make it a separate project.
- HTTP proxy
- HTTP over TLS (HTTPS) proxy
- Proxy authorization
- TLS termination
package main import ( "log" "net" "os" "os/signal" "syscall" "github.com/AdguardTeam/gomitmproxy" ) func main() { proxy := gomitmproxy.NewProxy(gomitmproxy.Config{ ListenAddr: &net.TCPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 8080, }, }) err := proxy.Start() if err != nil { log.Fatal(err) } signalChannel := make(chan os.Signal, 1) signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM) <-signalChannel // Clean up proxy.Close() }
You can modify requests and responses using OnRequest and OnResponse handlers.
The example below will block requests to example.net and add a short comment to
the end of every HTML response.
proxy := gomitmproxy.NewProxy(gomitmproxy.Config{ ListenAddr: &net.TCPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 8080, }, OnRequest: func(session *gomitmproxy.Session) (request *http.Request, response *http.Response) { req := session.Request() log.Printf("onRequest: %s %s", req.Method, req.URL.String()) if req.URL.Host == "example.net" { body := strings.NewReader("<html><body><h1>Replaced response</h1></body></html>") res := proxyutil.NewResponse(http.StatusOK, body, req) res.Header.Set("Content-Type", "text/html") // Use session props to pass the information about request being blocked session.SetProp("blocked", true) return nil, res } return nil, nil }, OnResponse: func(session *gomitmproxy.Session) *http.Response { log.Printf("onResponse: %s", session.Request().URL.String()) if _, ok := session.GetProp("blocked"); ok { log.Printf("onResponse: was blocked") } res := session.Response() req := session.Request() if strings.Index(res.Header.Get("Content-Type"), "text/html") != 0 { // Do nothing with non-HTML responses return nil } b, err := proxyutil.ReadDecompressedBody(res) // Close the original body _ = res.Body.Close() if err != nil { return proxyutil.NewErrorResponse(req, err) } // Use latin1 before modifying the body // Using this 1-byte encoding will let us preserve all original characters // regardless of what exactly is the encoding body, err := proxyutil.DecodeLatin1(bytes.NewReader(b)) if err != nil { return proxyutil.NewErrorResponse(session.Request(), err) } // Modifying the original body modifiedBody, err := proxyutil.EncodeLatin1(body + "<!-- EDITED -->") if err != nil { return proxyutil.NewErrorResponse(session.Request(), err) } res.Body = ioutil.NopCloser(bytes.NewReader(modifiedBody)) res.Header.Del("Content-Encoding") res.ContentLength = int64(len(modifiedBody)) return res }, })
If you want to protect your proxy with Basic authentication, set Username and Password
fields in the proxy configuration.
proxy := gomitmproxy.NewProxy(gomitmproxy.Config{ ListenAddr: &net.TCPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 8080, }, Username: "user", Password: "pass", })
If you want to protect yourself from eavesdropping on your traffic to proxy, you can configure
it to work over a TLS tunnel. This is really simple to do, just set a *tls.Config instance
in your proxy configuration.
tlsConfig := &tls.Config{ Certificates: []tls.Certificate{*proxyCert}, } proxy := gomitmproxy.NewProxy(gomitmproxy.Config{ ListenAddr: addr, TLSConfig: tlsConfig, })
If you want to do TLS termination, you first need to prepare a self-signed certificate
that will be used as a certificates authority. Use the following openssl commands to do this.
openssl genrsa -out demo.key 2048 openssl req -new -x509 -key demo.key -out demo.crt -days 3650 -addext subjectAltName=DNS:<hostname>,IP:<ip>
Now you can use it to initialize MITMConfig:
tlsCert, err := tls.LoadX509KeyPair("demo.crt", "demo.key") if err != nil { log.Fatal(err) } privateKey := tlsCert.PrivateKey.(*rsa.PrivateKey) x509c, err := x509.ParseCertificate(tlsCert.Certificate[0]) if err != nil { log.Fatal(err) } mitmConfig, err := mitm.NewConfig(x509c, privateKey, nil) if err != nil { log.Fatal(err) } mitmConfig.SetValidity(time.Hour * 24 * 7) // generate certs valid for 7 days mitmConfig.SetOrganization("gomitmproxy") // cert organization
Please note that you can set MITMExceptions to a list of hostnames,
which will be excluded from TLS interception.
proxy := gomitmproxy.NewProxy(gomitmproxy.Config{ ListenAddr: &net.TCPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 3333, }, MITMConfig: mitmConfig, MITMExceptions: []string{"example.com"}, })
If you configure the APIHost, you'll be able to download the CA certificate
from http://[APIHost]/cert.crt when the proxy is configured.
// Navigate to http://gomitmproxy/cert.crt to download the CA certificate proxy.APIHost = "gomitmproxy"
By default, gomitmproxy uses an in-memory map-based storage for the certificates,
generated while doing TLS interception. It is often necessary to use a different kind
of certificates storage. If this is your case, you can supply your own implementation
of the CertsStorage interface.
// CustomCertsStorage - an example of a custom cert storage type CustomCertsStorage struct { certsCache map[string]*tls.Certificate // cache with the generated certificates } // Get gets the certificate from the storage func (c *CustomCertsStorage) Get(key string) (*tls.Certificate, bool) { v, ok := c.certsCache[key] return v, ok } // Set saves the certificate to the storage func (c *CustomCertsStorage) Set(key string, cert *tls.Certificate) { c.certsCache[key] = cert }
Then pass it to the NewConfig function.
mitmConfig, err := mitm.NewConfig(x509c, privateKey, &CustomCertsStorage{ certsCache: map[string]*tls.Certificate{}}, )
- martian - an awesome debugging proxy with TLS interception support.
- goproxy - also supports TLS interception and requests.
- Basic HTTP proxy without MITM
- Proxy
- Expose APIs for the library users
- How-to doc
- Travis configuration
- Proxy-Authorization
- WebSockets support (see this)
-
certsCache-- allow custom implementations - Support HTTP CONNECT over TLS
- Test plain HTTP requests inside HTTP CONNECT
- Test memory leaks
- Editing response body in a callback
- Handle unknown content-encoding values
- Handle CONNECT to APIHost properly (without trying to actually connect anywhere)
- Allow hijacking connections (!)
- Multiple listeners
- Unit tests
- Check & fix TODOs
- Allow specifying net.Dialer
- Specify timeouts for http.Transport
- MITM
- Basic MITM
- MITM exceptions
- Handle invalid server certificates properly (not just reset connections)
- Pass the most important tests on badssl.com/dashboard
- Handle certificate authentication
- Allow configuring minimum supported TLS version
- OCSP check (see example)
- (?) HPKP (see example)
- (?) CT logs (see example)
- (?) CRLSets (see example)