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 }