transfer.sh/vendor/github.com/google/martian/mobile/proxy.go

304 lines
7.9 KiB
Go
Raw Normal View History

2019-03-17 20:19:56 +01:00
// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mobile
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path"
"time"
"github.com/google/martian"
"github.com/google/martian/api"
"github.com/google/martian/cors"
"github.com/google/martian/cybervillains"
"github.com/google/martian/fifo"
"github.com/google/martian/har"
"github.com/google/martian/httpspec"
mlog "github.com/google/martian/log"
"github.com/google/martian/marbl"
"github.com/google/martian/martianhttp"
"github.com/google/martian/mitm"
"github.com/google/martian/servemux"
"github.com/google/martian/trafficshape"
"github.com/google/martian/verify"
// side-effect importing to register with JSON API
_ "github.com/google/martian/body"
_ "github.com/google/martian/cookie"
_ "github.com/google/martian/failure"
_ "github.com/google/martian/header"
_ "github.com/google/martian/martianurl"
_ "github.com/google/martian/method"
_ "github.com/google/martian/pingback"
_ "github.com/google/martian/port"
_ "github.com/google/martian/priority"
_ "github.com/google/martian/querystring"
_ "github.com/google/martian/skip"
_ "github.com/google/martian/stash"
_ "github.com/google/martian/static"
_ "github.com/google/martian/status"
)
// Martian is a wrapper for the initialized Martian proxy
type Martian struct {
proxy *martian.Proxy
listener net.Listener
apiListener net.Listener
mux *http.ServeMux
started bool
HARLogging bool
TrafficPort int
TrafficShaping bool
APIPort int
APIOverTLS bool
BindLocalhost bool
Cert string
Key string
AllowCORS bool
RoundTripper *http.Transport
}
// EnableCybervillains configures Martian to use the Cybervillians certificate.
func (m *Martian) EnableCybervillains() {
m.Cert = cybervillains.Cert
m.Key = cybervillains.Key
}
// NewProxy creates a new Martian struct for configuring and starting a martian.
func NewProxy() *Martian {
return &Martian{}
}
// Start starts the proxy given the configured values of the Martian struct.
func (m *Martian) Start() {
var err error
m.listener, err = net.Listen("tcp", m.bindAddress(m.TrafficPort))
if err != nil {
log.Fatal(err)
}
mlog.Debugf("mobile: started listener on: %v", m.listener.Addr())
m.proxy = martian.NewProxy()
m.mux = http.NewServeMux()
if m.Cert != "" && m.Key != "" {
tlsc, err := tls.X509KeyPair([]byte(m.Cert), []byte(m.Key))
if err != nil {
log.Fatal(err)
}
mlog.Debugf("mobile: loaded cert and key")
x509c, err := x509.ParseCertificate(tlsc.Certificate[0])
if err != nil {
log.Fatal(err)
}
mlog.Debugf("mobile: parsed cert")
mc, err := mitm.NewConfig(x509c, tlsc.PrivateKey)
if err != nil {
log.Fatal(err)
}
mc.SetValidity(12 * time.Hour)
mc.SetOrganization("Martian Proxy")
m.proxy.SetMITM(mc)
if m.RoundTripper != nil {
m.proxy.SetRoundTripper(m.RoundTripper)
}
m.handle("/authority.cer", martianhttp.NewAuthorityHandler(x509c))
}
// Enable Traffic shaping if requested
if m.TrafficShaping {
tsl := trafficshape.NewListener(m.listener)
tsh := trafficshape.NewHandler(tsl)
m.handle("/shape-traffic", tsh)
m.listener = tsl
}
// Forward traffic that pattern matches in m.mux before applying
// httpspec modifiers (via modifier, specifically)
topg := fifo.NewGroup()
apif := servemux.NewFilter(m.mux)
apif.SetRequestModifier(api.NewForwarder("", m.APIPort))
topg.AddRequestModifier(apif)
stack, fg := httpspec.NewStack("martian.mobile")
topg.AddRequestModifier(stack)
topg.AddResponseModifier(stack)
m.proxy.SetRequestModifier(topg)
m.proxy.SetResponseModifier(topg)
if m.HARLogging {
// add HAR logger for unmodified logs.
uhl := har.NewLogger()
uhmuxf := servemux.NewFilter(m.mux)
uhmuxf.RequestWhenFalse(uhl)
uhmuxf.ResponseWhenFalse(uhl)
fg.AddRequestModifier(uhmuxf)
fg.AddResponseModifier(uhmuxf)
// add HAR logger
hl := har.NewLogger()
hmuxf := servemux.NewFilter(m.mux)
hmuxf.RequestWhenFalse(hl)
hmuxf.ResponseWhenFalse(hl)
stack.AddRequestModifier(hmuxf)
stack.AddResponseModifier(hmuxf)
// Retrieve Unmodified HAR logs
m.handle("/logs/original", har.NewExportHandler(uhl))
m.handle("/logs/original/reset", har.NewResetHandler(uhl))
// Retrieve HAR logs
m.handle("/logs", har.NewExportHandler(hl))
m.handle("/logs/reset", har.NewResetHandler(hl))
}
lsh := marbl.NewHandler()
// retrieve binary marbl logs
m.handle("/binlogs", lsh)
lsm := marbl.NewModifier(lsh)
muxf := servemux.NewFilter(m.mux)
muxf.RequestWhenFalse(lsm)
muxf.ResponseWhenFalse(lsm)
stack.AddRequestModifier(muxf)
stack.AddResponseModifier(muxf)
mod := martianhttp.NewModifier()
fg.AddRequestModifier(mod)
fg.AddResponseModifier(mod)
// Proxy specific handlers.
// These handlers take precendence over proxy traffic and will not be intercepted.
// Update modifiers.
m.handle("/configure", mod)
// Verify assertions.
vh := verify.NewHandler()
vh.SetRequestVerifier(mod)
vh.SetResponseVerifier(mod)
m.handle("/verify", vh)
// Reset verifications.
rh := verify.NewResetHandler()
rh.SetRequestVerifier(mod)
rh.SetResponseVerifier(mod)
m.handle("/verify/reset", rh)
mlog.Infof("mobile: starting Martian proxy on listener")
go m.proxy.Serve(m.listener)
// start the API server
apiAddr := m.bindAddress(m.APIPort)
m.apiListener, err = net.Listen("tcp", apiAddr)
if err != nil {
log.Fatal(err)
}
if m.APIOverTLS {
if m.Cert == "" || m.Key == "" {
log.Fatal("mobile: APIOverTLS cannot be true without valid cert and key")
}
cerfile, err := ioutil.TempFile("", "martian-api.cert")
if err != nil {
log.Fatal(err)
}
keyfile, err := ioutil.TempFile("", "martian-api.key")
if err != nil {
log.Fatal(err)
}
if _, err := cerfile.Write([]byte(m.Cert)); err != nil {
log.Fatal(err)
}
if _, err := keyfile.Write([]byte(m.Key)); err != nil {
log.Fatal(err)
}
go func() {
http.ServeTLS(m.apiListener, m.mux, cerfile.Name(), keyfile.Name())
defer os.Remove(cerfile.Name())
defer os.Remove(keyfile.Name())
}()
mlog.Infof("mobile: proxy API started on %s over TLS", apiAddr)
} else {
go http.Serve(m.apiListener, m.mux)
mlog.Infof("mobile: proxy API started on %s", apiAddr)
}
m.started = true
}
// IsStarted returns true if the proxy has finished starting.
func (m *Martian) IsStarted() bool {
return m.started
}
// Shutdown tells the Proxy to close. This function returns immediately, though
// there may still be connection threads hanging around until they time out
// depending on how the OS manages them.
func (m *Martian) Shutdown() {
mlog.Infof("mobile: shutting down proxy")
m.listener.Close()
m.apiListener.Close()
m.proxy.Close()
m.started = false
mlog.Infof("mobile: proxy shut down")
}
// SetLogLevel sets the Martian log level (Silent = 0, Error, Info, Debug), controlling which Martian
// log calls are displayed in the console
func SetLogLevel(l int) {
mlog.SetLevel(l)
}
func (m *Martian) handle(pattern string, handler http.Handler) {
if m.AllowCORS {
handler = cors.NewHandler(handler)
}
m.mux.Handle(pattern, handler)
mlog.Infof("mobile: handler registered for %s", pattern)
lhp := path.Join(fmt.Sprintf("localhost:%d", m.APIPort), pattern)
m.mux.Handle(lhp, handler)
mlog.Infof("mobile: handler registered for %s", lhp)
}
func (m *Martian) bindAddress(port int) string {
if m.BindLocalhost {
return fmt.Sprintf("[::1]:%d", port)
}
return fmt.Sprintf(":%d", port)
}