Gomitmproxy
Simple golang mitm proxy implementation
Install / Use
/learn @AdguardTeam/GomitmproxyREADME
gomitmproxy
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.
Features
- HTTP proxy
- HTTP over TLS (HTTPS) proxy
- Proxy authorization
- TLS termination
How to use gomitmproxy
Simple HTTP proxy
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()
}
Modifying requests and responses
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
},
})
Proxy authorization
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",
})
HTTP over TLS (HTTPS) proxy
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,
})
TLS interception
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"
Custom certs storage
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{}},
)
Notable alternatives
- martian - an awesome debugging proxy with TLS interception support.
- goproxy - also supports TLS interception and requests.
TODO
- [X] Basic HTTP proxy without MITM
- [ ] Proxy
- [X] Expose APIs for the library users
- [X] How-to doc
- [X] Travis configuration
- [X] Proxy-Authorization
- [X] WebSockets support (see this)
- [X]
certsCache-- allow custom implementations - [X] Support HTTP CONNECT over TLS
- [X] Test plain HTTP requests inside HTTP CONNECT
- [X] Test memory leaks
- [X] Editing response body in a callback
- [X] Handle unknown content-encoding values
- [X] Handle CONNECT to APIHost properly (without trying to actually connect anywhere)
- [X] Allow hijacking connections (!)
- [X] Multiple listeners
- [ ] Unit tests
- [ ] Check & fix TODOs
- [ ] Allow specifying net.Dialer
- [ ] Specify timeouts for http.Transport
- [ ] MITM
- [X] Basic MITM
- [X] MITM exceptions
- [X] Handle invalid server certificates properly (not just reset connections)
- [X] Pass the most important tests on badssl.com/dashboard
- [X] Handle certificate authentication
- [ ] Allow configuring minimum supported TLS version
- [ ] OCSP check (see example)
- [ ] (?) HPKP (see example)
- [ ] (?) CT logs (see example)
- [ ] (?) CRLSets (see example)
Related Skills
node-connect
337.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
xurl
337.4kA CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.
frontend-design
83.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
337.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
