MITM proxy
This commit is contained in:
279
mitmproxy/mitmproxy.go
Normal file
279
mitmproxy/mitmproxy.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package mitmproxy
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/filters"
|
||||
"github.com/AdguardTeam/golibs/file"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/gomitmproxy/mitm"
|
||||
"github.com/AdguardTeam/urlfilter/proxy"
|
||||
)
|
||||
|
||||
// MITMProxy - MITM proxy structure
|
||||
type MITMProxy struct {
|
||||
proxy *proxy.Server
|
||||
conf Config
|
||||
confLock sync.Mutex
|
||||
}
|
||||
|
||||
// Config - module configuration
|
||||
type Config struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
ListenAddr string `yaml:"listen_address"`
|
||||
|
||||
UserName string `yaml:"auth_username"`
|
||||
Password string `yaml:"auth_password"`
|
||||
|
||||
// TLS:
|
||||
RegenCert bool `yaml:"regenerate_cert"` // Regenerate certificate on cert loading failure
|
||||
CertDir string `yaml:"-"` // Directory where Root certificate & pkey is stored
|
||||
certFileName string
|
||||
pkeyFileName string
|
||||
certData []byte
|
||||
pkeyData []byte
|
||||
|
||||
Filter filters.Filters `yaml:"-"`
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
ConfigModified func() `yaml:"-"`
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||
}
|
||||
|
||||
// New - create a new instance of the query log
|
||||
func New(conf Config) *MITMProxy {
|
||||
p := MITMProxy{}
|
||||
|
||||
p.conf = conf
|
||||
p.conf.certFileName = filepath.Join(p.conf.CertDir, "/http_proxy.crt")
|
||||
p.conf.pkeyFileName = filepath.Join(p.conf.CertDir, "/http_proxy.key")
|
||||
|
||||
err := p.create()
|
||||
if err != nil {
|
||||
log.Error("MITM: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.conf.HTTPRegister != nil {
|
||||
p.initWeb()
|
||||
}
|
||||
|
||||
p.conf.Filter.SetObserver(p.onFiltersChanged)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
// Close - close the object
|
||||
func (p *MITMProxy) Close() {
|
||||
if p.proxy != nil {
|
||||
p.proxy.Close()
|
||||
p.proxy = nil
|
||||
log.Debug("MITM: Closed proxy")
|
||||
}
|
||||
}
|
||||
|
||||
// WriteDiskConfig - write configuration on disk
|
||||
func (p *MITMProxy) WriteDiskConfig(c *Config) {
|
||||
p.confLock.Lock()
|
||||
*c = p.conf
|
||||
p.confLock.Unlock()
|
||||
}
|
||||
|
||||
// Start - start proxy server
|
||||
func (p *MITMProxy) Start() error {
|
||||
if !p.conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.proxy.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("MITM: Running...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart - restart proxy server after Close()
|
||||
func (p *MITMProxy) Restart() error {
|
||||
err := p.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Start()
|
||||
}
|
||||
|
||||
// Create a gomitmproxy object
|
||||
func (p *MITMProxy) create() error {
|
||||
if !p.conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := proxy.Config{}
|
||||
c.ProxyConfig.APIHost = "adguardhome.api"
|
||||
addr, port, err := net.SplitHostPort(p.conf.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("net.SplitHostPort: %s", err)
|
||||
}
|
||||
|
||||
c.CompressContentScript = true
|
||||
c.ProxyConfig.ListenAddr = &net.TCPAddr{}
|
||||
c.ProxyConfig.ListenAddr.IP = net.ParseIP(addr)
|
||||
if c.ProxyConfig.ListenAddr.IP == nil {
|
||||
return fmt.Errorf("invalid IP: %s", addr)
|
||||
}
|
||||
c.ProxyConfig.ListenAddr.Port, err = strconv.Atoi(port)
|
||||
if c.ProxyConfig.ListenAddr.Port < 0 || c.ProxyConfig.ListenAddr.Port > 0xffff || err != nil {
|
||||
return fmt.Errorf("invalid port number: %s", port)
|
||||
}
|
||||
|
||||
c.ProxyConfig.Username = p.conf.UserName
|
||||
c.ProxyConfig.Password = p.conf.Password
|
||||
|
||||
err = p.loadCert()
|
||||
if err != nil {
|
||||
if !p.conf.RegenCert {
|
||||
return err
|
||||
}
|
||||
log.Debug("%s", err)
|
||||
|
||||
// certificate or private key file doesn't exist - generate new
|
||||
err = p.createRootCert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.ProxyConfig.MITMConfig, err = p.prepareMITMConfig()
|
||||
if err != nil {
|
||||
if !p.conf.RegenCert {
|
||||
return err
|
||||
}
|
||||
|
||||
// certificate or private key is invalid - generate new
|
||||
err = p.createRootCert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ProxyConfig.MITMConfig, err = p.prepareMITMConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.FiltersPaths = make(map[int]string)
|
||||
filtrs := p.conf.Filter.List(0)
|
||||
i := 0
|
||||
for _, f := range filtrs {
|
||||
if !f.Enabled ||
|
||||
f.RuleCount == 0 { // not loaded
|
||||
continue
|
||||
}
|
||||
|
||||
c.FiltersPaths[i] = f.Path
|
||||
i++
|
||||
}
|
||||
|
||||
p.proxy, err = proxy.NewServer(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proxy.NewServer: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load cert and pkey from file
|
||||
func (p *MITMProxy) loadCert() error {
|
||||
var err error
|
||||
p.conf.certData, err = ioutil.ReadFile(p.conf.certFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.conf.pkeyData, err = ioutil.ReadFile(p.conf.pkeyFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create Root certificate and pkey and store it on disk
|
||||
func (p *MITMProxy) createRootCert() error {
|
||||
cert, key, err := mitm.NewAuthority("AdGuardHome Root", "AdGuard", 365*24*time.Hour)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.conf.certData = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||
p.conf.pkeyData = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
log.Debug("MITM: Created root certificate and key")
|
||||
|
||||
err = p.storeCert(p.conf.certData, p.conf.pkeyData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store cert & pkey on disk
|
||||
func (p *MITMProxy) storeCert(certData []byte, pkeyData []byte) error {
|
||||
err := file.SafeWrite(p.conf.certFileName, certData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.SafeWrite(p.conf.pkeyFileName, pkeyData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("MITM: stored root certificate and key: %s, %s", p.conf.certFileName, p.conf.pkeyFileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fill TLSConfig & MITMConfig objects
|
||||
func (p *MITMProxy) prepareMITMConfig() (*mitm.Config, error) {
|
||||
tlsCert, err := tls.X509KeyPair(p.conf.certData, p.conf.pkeyData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load root CA: %v", err)
|
||||
}
|
||||
privateKey := tlsCert.PrivateKey.(*rsa.PrivateKey)
|
||||
|
||||
x509c, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid certificate: %v", err)
|
||||
}
|
||||
|
||||
mitmConfig, err := mitm.NewConfig(x509c, privateKey, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create MITM config: %v", err)
|
||||
}
|
||||
|
||||
mitmConfig.SetValidity(time.Hour * 24 * 7) // generate certs valid for 7 days
|
||||
mitmConfig.SetOrganization("AdGuard") // cert organization
|
||||
return mitmConfig, nil
|
||||
}
|
||||
|
||||
func (p *MITMProxy) onFiltersChanged(flags uint) {
|
||||
switch flags {
|
||||
case filters.EventBeforeUpdate:
|
||||
p.Close()
|
||||
|
||||
case filters.EventAfterUpdate:
|
||||
err := p.Restart()
|
||||
if err != nil {
|
||||
log.Error("MITM: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user