github.com/google/martian/v3@v3.3.3/mobile/proxy.go (about)

     1  // Copyright 2017 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mobile
    16  
    17  import (
    18  	"crypto/tls"
    19  	"crypto/x509"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"net"
    24  	"net/http"
    25  	"os"
    26  	"path"
    27  	"time"
    28  
    29  	"github.com/google/martian/v3"
    30  	"github.com/google/martian/v3/api"
    31  	"github.com/google/martian/v3/cors"
    32  	"github.com/google/martian/v3/cybervillains"
    33  	"github.com/google/martian/v3/fifo"
    34  	"github.com/google/martian/v3/har"
    35  	"github.com/google/martian/v3/httpspec"
    36  	mlog "github.com/google/martian/v3/log"
    37  	"github.com/google/martian/v3/marbl"
    38  	"github.com/google/martian/v3/martianhttp"
    39  	"github.com/google/martian/v3/mitm"
    40  	"github.com/google/martian/v3/servemux"
    41  	"github.com/google/martian/v3/trafficshape"
    42  	"github.com/google/martian/v3/verify"
    43  
    44  	// side-effect importing to register with JSON API
    45  	_ "github.com/google/martian/v3/body"
    46  	_ "github.com/google/martian/v3/cookie"
    47  	_ "github.com/google/martian/v3/failure"
    48  	_ "github.com/google/martian/v3/header"
    49  	_ "github.com/google/martian/v3/martianurl"
    50  	_ "github.com/google/martian/v3/method"
    51  	_ "github.com/google/martian/v3/pingback"
    52  	_ "github.com/google/martian/v3/port"
    53  	_ "github.com/google/martian/v3/priority"
    54  	_ "github.com/google/martian/v3/querystring"
    55  	_ "github.com/google/martian/v3/skip"
    56  	_ "github.com/google/martian/v3/stash"
    57  	_ "github.com/google/martian/v3/static"
    58  	_ "github.com/google/martian/v3/status"
    59  )
    60  
    61  // Martian is a wrapper for the initialized Martian proxy
    62  type Martian struct {
    63  	proxy          *martian.Proxy
    64  	listener       net.Listener
    65  	apiListener    net.Listener
    66  	mux            *http.ServeMux
    67  	started        bool
    68  	HARLogging     bool
    69  	TrafficPort    int
    70  	TrafficShaping bool
    71  	APIPort        int
    72  	APIOverTLS     bool
    73  	BindLocalhost  bool
    74  	Cert           string
    75  	Key            string
    76  	AllowCORS      bool
    77  	RoundTripper   *http.Transport
    78  }
    79  
    80  // EnableCybervillains configures Martian to use the Cybervillians certificate.
    81  func (m *Martian) EnableCybervillains() {
    82  	m.Cert = cybervillains.Cert
    83  	m.Key = cybervillains.Key
    84  }
    85  
    86  // NewProxy creates a new Martian struct for configuring and starting a martian.
    87  func NewProxy() *Martian {
    88  	return &Martian{}
    89  }
    90  
    91  // Start starts the proxy given the configured values of the Martian struct.
    92  func (m *Martian) Start() {
    93  	var err error
    94  	m.listener, err = net.Listen("tcp", m.bindAddress(m.TrafficPort))
    95  	if err != nil {
    96  		log.Fatal(err)
    97  	}
    98  
    99  	mlog.Debugf("mobile: started listener on: %v", m.listener.Addr())
   100  	m.proxy = martian.NewProxy()
   101  	m.mux = http.NewServeMux()
   102  
   103  	if m.Cert != "" && m.Key != "" {
   104  		tlsc, err := tls.X509KeyPair([]byte(m.Cert), []byte(m.Key))
   105  		if err != nil {
   106  			log.Fatal(err)
   107  		}
   108  
   109  		mlog.Debugf("mobile: loaded cert and key")
   110  
   111  		x509c, err := x509.ParseCertificate(tlsc.Certificate[0])
   112  		if err != nil {
   113  			log.Fatal(err)
   114  		}
   115  
   116  		mlog.Debugf("mobile: parsed cert")
   117  
   118  		mc, err := mitm.NewConfig(x509c, tlsc.PrivateKey)
   119  		if err != nil {
   120  			log.Fatal(err)
   121  		}
   122  
   123  		mc.SetValidity(12 * time.Hour)
   124  		mc.SetOrganization("Martian Proxy")
   125  
   126  		m.proxy.SetMITM(mc)
   127  
   128  		if m.RoundTripper != nil {
   129  			m.proxy.SetRoundTripper(m.RoundTripper)
   130  		}
   131  		m.handle("/authority.cer", martianhttp.NewAuthorityHandler(x509c))
   132  	}
   133  
   134  	// Enable Traffic shaping if requested
   135  	if m.TrafficShaping {
   136  		tsl := trafficshape.NewListener(m.listener)
   137  		tsh := trafficshape.NewHandler(tsl)
   138  		m.handle("/shape-traffic", tsh)
   139  		m.listener = tsl
   140  	}
   141  
   142  	// Forward traffic that pattern matches in m.mux before applying
   143  	// httpspec modifiers (via modifier, specifically)
   144  	topg := fifo.NewGroup()
   145  	apif := servemux.NewFilter(m.mux)
   146  	apif.SetRequestModifier(api.NewForwarder("", m.APIPort))
   147  	topg.AddRequestModifier(apif)
   148  
   149  	stack, fg := httpspec.NewStack("martian.mobile")
   150  	topg.AddRequestModifier(stack)
   151  	topg.AddResponseModifier(stack)
   152  
   153  	m.proxy.SetRequestModifier(topg)
   154  	m.proxy.SetResponseModifier(topg)
   155  
   156  	if m.HARLogging {
   157  		// add HAR logger for unmodified logs.
   158  		uhl := har.NewLogger()
   159  		uhmuxf := servemux.NewFilter(m.mux)
   160  		uhmuxf.RequestWhenFalse(uhl)
   161  		uhmuxf.ResponseWhenFalse(uhl)
   162  		fg.AddRequestModifier(uhmuxf)
   163  		fg.AddResponseModifier(uhmuxf)
   164  
   165  		// add HAR logger
   166  		hl := har.NewLogger()
   167  		hmuxf := servemux.NewFilter(m.mux)
   168  		hmuxf.RequestWhenFalse(hl)
   169  		hmuxf.ResponseWhenFalse(hl)
   170  		stack.AddRequestModifier(hmuxf)
   171  		stack.AddResponseModifier(hmuxf)
   172  
   173  		// Retrieve Unmodified HAR logs
   174  		m.handle("/logs/original", har.NewExportHandler(uhl))
   175  		m.handle("/logs/original/reset", har.NewResetHandler(uhl))
   176  
   177  		// Retrieve HAR logs
   178  		m.handle("/logs", har.NewExportHandler(hl))
   179  		m.handle("/logs/reset", har.NewResetHandler(hl))
   180  	}
   181  
   182  	lsh := marbl.NewHandler()
   183  	// retrieve binary marbl logs
   184  	m.handle("/binlogs", lsh)
   185  
   186  	lsm := marbl.NewModifier(lsh)
   187  	muxf := servemux.NewFilter(m.mux)
   188  	muxf.RequestWhenFalse(lsm)
   189  	muxf.ResponseWhenFalse(lsm)
   190  	stack.AddRequestModifier(muxf)
   191  	stack.AddResponseModifier(muxf)
   192  
   193  	mod := martianhttp.NewModifier()
   194  	fg.AddRequestModifier(mod)
   195  	fg.AddResponseModifier(mod)
   196  
   197  	// Proxy specific handlers.
   198  	// These handlers take precendence over proxy traffic and will not be intercepted.
   199  
   200  	// Update modifiers.
   201  	m.handle("/configure", mod)
   202  
   203  	// Verify assertions.
   204  	vh := verify.NewHandler()
   205  	vh.SetRequestVerifier(mod)
   206  	vh.SetResponseVerifier(mod)
   207  
   208  	m.handle("/verify", vh)
   209  
   210  	// Reset verifications.
   211  	rh := verify.NewResetHandler()
   212  	rh.SetRequestVerifier(mod)
   213  	rh.SetResponseVerifier(mod)
   214  	m.handle("/verify/reset", rh)
   215  
   216  	mlog.Infof("mobile: starting Martian proxy on listener")
   217  	go m.proxy.Serve(m.listener)
   218  
   219  	// start the API server
   220  	apiAddr := m.bindAddress(m.APIPort)
   221  	m.apiListener, err = net.Listen("tcp", apiAddr)
   222  	if err != nil {
   223  		log.Fatal(err)
   224  	}
   225  	if m.APIOverTLS {
   226  		if m.Cert == "" || m.Key == "" {
   227  			log.Fatal("mobile: APIOverTLS cannot be true without valid cert and key")
   228  		}
   229  
   230  		cerfile, err := ioutil.TempFile("", "martian-api.cert")
   231  		if err != nil {
   232  			log.Fatal(err)
   233  		}
   234  
   235  		keyfile, err := ioutil.TempFile("", "martian-api.key")
   236  		if err != nil {
   237  			log.Fatal(err)
   238  		}
   239  
   240  		if _, err := cerfile.Write([]byte(m.Cert)); err != nil {
   241  			log.Fatal(err)
   242  		}
   243  
   244  		if _, err := keyfile.Write([]byte(m.Key)); err != nil {
   245  			log.Fatal(err)
   246  		}
   247  
   248  		go func() {
   249  			http.ServeTLS(m.apiListener, m.mux, cerfile.Name(), keyfile.Name())
   250  			defer os.Remove(cerfile.Name())
   251  			defer os.Remove(keyfile.Name())
   252  		}()
   253  
   254  		mlog.Infof("mobile: proxy API started on %s over TLS", apiAddr)
   255  	} else {
   256  		go http.Serve(m.apiListener, m.mux)
   257  		mlog.Infof("mobile: proxy API started on %s", apiAddr)
   258  	}
   259  
   260  	m.started = true
   261  }
   262  
   263  // IsStarted returns true if the proxy has finished starting.
   264  func (m *Martian) IsStarted() bool {
   265  	return m.started
   266  }
   267  
   268  // Shutdown tells the Proxy to close. This function returns immediately, though
   269  // there may still be connection threads hanging around until they time out
   270  // depending on how the OS manages them.
   271  func (m *Martian) Shutdown() {
   272  	mlog.Infof("mobile: shutting down proxy")
   273  	m.listener.Close()
   274  	m.apiListener.Close()
   275  	m.proxy.Close()
   276  	m.started = false
   277  	mlog.Infof("mobile: proxy shut down")
   278  }
   279  
   280  // SetLogLevel sets the Martian log level (Silent = 0, Error, Info, Debug), controlling which Martian
   281  // log calls are displayed in the console
   282  func SetLogLevel(l int) {
   283  	mlog.SetLevel(l)
   284  }
   285  
   286  func (m *Martian) handle(pattern string, handler http.Handler) {
   287  	if m.AllowCORS {
   288  		handler = cors.NewHandler(handler)
   289  	}
   290  	m.mux.Handle(pattern, handler)
   291  	mlog.Infof("mobile: handler registered for %s", pattern)
   292  
   293  	lhp := path.Join(fmt.Sprintf("localhost:%d", m.APIPort), pattern)
   294  	m.mux.Handle(lhp, handler)
   295  	mlog.Infof("mobile: handler registered for %s", lhp)
   296  }
   297  
   298  func (m *Martian) bindAddress(port int) string {
   299  	if m.BindLocalhost {
   300  		return fmt.Sprintf("[::1]:%d", port)
   301  	}
   302  	return fmt.Sprintf(":%d", port)
   303  }