|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- // 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)
- }
|