MCPcopy Index your code
hub / github.com/AdguardTeam/gomitmproxy

github.com/AdguardTeam/gomitmproxy @v0.2.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.2.1 ↗ · + Follow
83 symbols 258 edges 11 files 77 documented · 93%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Code Coverage Go Report Card GolangCI Go Doc

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 := gomitmproxy.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 + "")
        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

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)

Extension points exported contracts — how you extend this code

CertsStorage (Interface)
CertsStorage is an interface for generated tls certificates storage [2 implementers]
mitm/mitm.go

Core symbols most depended-on inside this repo

ID
called by 37
context.go
Close
called by 17
proxy.go
writeResponse
called by 8
proxy.go
Get
called by 7
mitm/mitm.go
NewResponse
called by 7
proxyutil/util.go
NewErrorResponse
called by 7
proxyutil/util.go
Set
called by 6
mitm/mitm.go
Request
called by 5
context.go

Shape

Method 52
Function 21
Struct 9
Interface 1

Languages

Go100%

Modules by API surface

proxy.go22 symbols
context.go16 symbols
proxyutil/util.go14 symbols
mitm/mitm.go14 symbols
examples/mitm/main.go7 symbols
helper.go3 symbols
auth.go3 symbols
mitm/mitm_test.go1 symbols
hopbyhop.go1 symbols
examples/simple/main.go1 symbols
config.go1 symbols

For agents

$ claude mcp add gomitmproxy \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact