You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

304 lines
7.9 KiB

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