gomitmproxy

package module
v0.0.0-...-c243a73 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2023 License: GPL-3.0 Imports: 19 Imported by: 0

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 := 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

  • 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)

Documentation

Overview

Package gomitmproxy implements a configurable mitm proxy wring purely in go.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// ListenAddr is the TCP address the proxy should listen to.
	ListenAddr *net.TCPAddr

	// TLSConfig is a *tls.Config to use for the HTTP over TLS proxy. If not set
	// the proxy will work as a simple plain HTTP proxy.
	TLSConfig *tls.Config

	// Username is the username to be used in the "Proxy-Authorization" header.
	Username string

	// Password is the password to be used in the "Proxy-Authorization" header.
	Password string

	// MITMConfig defines the MITM configuration of the proxy. If it is not set
	// MITM won't be enabled for this proxy instance.
	MITMConfig *mitm.Config

	// MITMExceptions is a list of hostnames for which MITM will be disabled.
	MITMExceptions []string

	// APIHost is a name of the gomitmproxy API hostname. If it is not set, the
	// API won't be exposed via HTTP.
	//
	// Here are the methods exposed:
	//   1. apihost/cert.crt - serves the authority cert if MITMConfig is
	//   configured.
	APIHost string

	// OnConnect is called when the proxy tries to open a new net.Conn. This
	// function allows hijacking the remote connection and replacing it with a
	// different one.
	//
	// 1. When the proxy handles the HTTP CONNECT.
	//    IMPORTANT: In this case we don't actually use the remote connections.
	//    It is only used to check if the remote endpoint is available.
	// 2. When the proxy bypasses data from the client to the remote endpoint.
	//    For instance, it could happen when there's a WebSocket connection.
	OnConnect OnConnectFunc

	// OnRequest is called when the request has been just received, but has not
	// been sent to the remote server.
	//
	// At this stage, it is possible to do the following things:
	//   1. Modify or even replace the request.
	//   2. Supply an HTTP response to be written to the client.
	//
	// Return nil instead of *http.Request or *http.Response to keep the
	// original request / response.
	//
	// Note that even if you supply your own HTTP response here, the OnResponse
	// handler will be called anyway!
	OnRequest OnRequestFunc

	// OnResponse is called when the response has been just received, but has
	// not been sent to the local client. At this stage you can either keep the
	// original response, or you can replace it with a new one.
	OnResponse OnResponseFunc

	// OnError is called if there's an issue with retrieving the response from
	// the remote server.
	OnError OnErrorFunc

	DialContext func(ctx context.Context, network, address string) (net.Conn, error)
}

Config is the configuration of the Proxy.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context contains all the necessary information about the connection that is currently being processed by the proxy.

func (*Context) GetProp

func (c *Context) GetProp(key string) (v interface{}, ok bool)

GetProp gets context property (previously saved using SetProp).

func (*Context) ID

func (c *Context) ID() (id string)

ID is the context's unique ID.

func (*Context) IsMITM

func (c *Context) IsMITM() (ok bool)

IsMITM returns true if this context is for a MITMed connection.

func (*Context) SetDeadline

func (c *Context) SetDeadline(t time.Time) (err error)

SetDeadline sets the read and write deadlines associated with the connection.

The difference is that our contexts can be nested, so we search for the topmost parent context recursively and call SetDeadline for its connection only as this is the real underlying network connection.

func (*Context) SetProp

func (c *Context) SetProp(key string, val interface{})

SetProp sets the context's property.

type OnConnectFunc

type OnConnectFunc func(session *Session, proto string, addr string) (conn net.Conn)

OnConnectFunc is a declaration of the Config.OnConnect handler.

type OnErrorFunc

type OnErrorFunc func(session *Session, err error)

OnErrorFunc is a declaration of the Config.OnError handler.

type OnRequestFunc

type OnRequestFunc func(session *Session) (req *http.Request, resp *http.Response)

OnRequestFunc is a declaration of the Config.OnRequest handler.

type OnResponseFunc

type OnResponseFunc func(session *Session) (resp *http.Response)

OnResponseFunc is a declaration of the Config.OnResponse handler.

type Proxy

type Proxy struct {

	// Config is the proxy's configuration.
	// TODO(ameshkov): make it a field.
	Config
	// contains filtered or unexported fields
}

Proxy is a structure with the proxy server configuration and current state.

func NewProxy

func NewProxy(config Config) *Proxy

NewProxy creates a new instance of the Proxy.

func (*Proxy) Addr

func (p *Proxy) Addr() (addr net.Addr)

Addr returns the address this proxy listens to.

func (*Proxy) Close

func (p *Proxy) Close()

Close sets the proxy to the closing state so it stops receiving new connections, finishes processing any inflight requests, and closes existing connections without reading anymore requests from them.

TODO(ameshkov): make it return an error.

func (*Proxy) Closing

func (p *Proxy) Closing() (ok bool)

Closing returns true if the proxy is in the closing state.

func (*Proxy) Serve

func (p *Proxy) Serve(l net.Listener)

Serve starts reading and processing requests from the specified listener. Please note, that it will close the listener in the end.

func (*Proxy) Start

func (p *Proxy) Start() (err error)

Start starts the proxy server in a separate goroutine.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session contains all the necessary information about the request-response pair that is currently being processed.

func (*Session) Ctx

func (s *Session) Ctx() (ctx *Context)

Ctx returns this session's context.

func (*Session) GetProp

func (s *Session) GetProp(key string) (v interface{}, ok bool)

GetProp gets session property (previously saved using SetProp).

func (*Session) ID

func (s *Session) ID() (id string)

ID returns a unique session identifier.

func (*Session) RemoteAddr

func (s *Session) RemoteAddr() (addr string)

RemoteAddr returns this session's remote address.

func (*Session) Request

func (s *Session) Request() (req *http.Request)

Request returns the HTTP request of this session.

func (*Session) Response

func (s *Session) Response() (resp *http.Response)

Response returns the HTTP response of this session.

func (*Session) SetProp

func (s *Session) SetProp(key string, val interface{})

SetProp sets a session's property.

Directories

Path Synopsis
examples
Package mitm implements methods for working with certificates and TLS configurations that are used for MITMing connections.
Package mitm implements methods for working with certificates and TLS configurations that are used for MITMing connections.
Package proxyutil contains different utility methods that will be helpful to gomitmproxy users.
Package proxyutil contains different utility methods that will be helpful to gomitmproxy users.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL