Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

230 Zeilen
6.7 KiB

  1. // Copyright 2018 Google LLC
  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 proxy provides a record/replay HTTP proxy. It is designed to support
  15. // both an in-memory API (cloud.google.com/go/httpreplay) and a standalone server
  16. // (cloud.google.com/go/httpreplay/cmd/httpr).
  17. package proxy
  18. // See github.com/google/martian/cmd/proxy/main.go for the origin of much of this.
  19. import (
  20. "crypto/tls"
  21. "crypto/x509"
  22. "encoding/json"
  23. "fmt"
  24. "io/ioutil"
  25. "net"
  26. "net/http"
  27. "net/url"
  28. "strings"
  29. "time"
  30. "github.com/google/martian"
  31. "github.com/google/martian/fifo"
  32. "github.com/google/martian/httpspec"
  33. "github.com/google/martian/martianlog"
  34. "github.com/google/martian/mitm"
  35. )
  36. // A Proxy is an HTTP proxy that supports recording or replaying requests.
  37. type Proxy struct {
  38. // The certificate that the proxy uses to participate in TLS.
  39. CACert *x509.Certificate
  40. // The URL of the proxy.
  41. URL *url.URL
  42. // Initial state of the client.
  43. Initial []byte
  44. mproxy *martian.Proxy
  45. filename string // for log
  46. logger *Logger // for recording only
  47. ignoreHeaders map[string]bool // headers the user has asked to ignore
  48. }
  49. // ForRecording returns a Proxy configured to record.
  50. func ForRecording(filename string, port int) (*Proxy, error) {
  51. p, err := newProxy(filename)
  52. if err != nil {
  53. return nil, err
  54. }
  55. // Construct a group that performs the standard proxy stack of request/response
  56. // modifications.
  57. stack, _ := httpspec.NewStack("httpr") // second arg is an internal group that we don't need
  58. p.mproxy.SetRequestModifier(stack)
  59. p.mproxy.SetResponseModifier(stack)
  60. // Make a group for logging requests and responses.
  61. logGroup := fifo.NewGroup()
  62. skipAuth := skipLoggingByHost("accounts.google.com")
  63. logGroup.AddRequestModifier(skipAuth)
  64. logGroup.AddResponseModifier(skipAuth)
  65. p.logger = newLogger()
  66. logGroup.AddRequestModifier(p.logger)
  67. logGroup.AddResponseModifier(p.logger)
  68. stack.AddRequestModifier(logGroup)
  69. stack.AddResponseModifier(logGroup)
  70. // Ordinary debug logging.
  71. logger := martianlog.NewLogger()
  72. logger.SetDecode(true)
  73. stack.AddRequestModifier(logger)
  74. stack.AddResponseModifier(logger)
  75. if err := p.start(port); err != nil {
  76. return nil, err
  77. }
  78. return p, nil
  79. }
  80. type hideTransport http.Transport
  81. func (t *hideTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  82. return (*http.Transport)(t).RoundTrip(req)
  83. }
  84. func newProxy(filename string) (*Proxy, error) {
  85. mproxy := martian.NewProxy()
  86. // Set up a man-in-the-middle configuration with a CA certificate so the proxy can
  87. // participate in TLS.
  88. x509c, priv, err := mitm.NewAuthority("cloud.google.com/go/httpreplay", "HTTPReplay Authority", time.Hour)
  89. if err != nil {
  90. return nil, err
  91. }
  92. mc, err := mitm.NewConfig(x509c, priv)
  93. if err != nil {
  94. return nil, err
  95. }
  96. mc.SetValidity(time.Hour)
  97. mc.SetOrganization("cloud.google.com/go/httpreplay")
  98. mc.SkipTLSVerify(false)
  99. if err != nil {
  100. return nil, err
  101. }
  102. mproxy.SetMITM(mc)
  103. return &Proxy{
  104. mproxy: mproxy,
  105. CACert: x509c,
  106. filename: filename,
  107. ignoreHeaders: map[string]bool{},
  108. }, nil
  109. }
  110. func (p *Proxy) start(port int) error {
  111. l, err := net.Listen("tcp4", fmt.Sprintf(":%d", port))
  112. if err != nil {
  113. return err
  114. }
  115. p.URL = &url.URL{Scheme: "http", Host: l.Addr().String()}
  116. go p.mproxy.Serve(l)
  117. return nil
  118. }
  119. // Transport returns an http.Transport for clients who want to talk to the proxy.
  120. func (p *Proxy) Transport() *http.Transport {
  121. caCertPool := x509.NewCertPool()
  122. caCertPool.AddCert(p.CACert)
  123. return &http.Transport{
  124. TLSClientConfig: &tls.Config{RootCAs: caCertPool},
  125. Proxy: func(*http.Request) (*url.URL, error) { return p.URL, nil },
  126. }
  127. }
  128. // RemoveRequestHeaders will remove request headers matching patterns from the log,
  129. // and skip matching them. Pattern is taken literally except for *, which matches any
  130. // sequence of characters.
  131. //
  132. // This only needs to be called during recording; the patterns will be saved to the
  133. // log for replay.
  134. func (p *Proxy) RemoveRequestHeaders(patterns []string) {
  135. for _, pat := range patterns {
  136. p.logger.log.Converter.registerRemoveRequestHeaders(pat)
  137. }
  138. }
  139. // ClearHeaders will replace matching headers with CLEARED.
  140. //
  141. // This only needs to be called during recording; the patterns will be saved to the
  142. // log for replay.
  143. func (p *Proxy) ClearHeaders(patterns []string) {
  144. for _, pat := range patterns {
  145. p.logger.log.Converter.registerClearHeaders(pat)
  146. }
  147. }
  148. // RemoveQueryParams will remove query parameters matching patterns from the request
  149. // URL before logging, and skip matching them. Pattern is taken literally except for
  150. // *, which matches any sequence of characters.
  151. //
  152. // This only needs to be called during recording; the patterns will be saved to the
  153. // log for replay.
  154. func (p *Proxy) RemoveQueryParams(patterns []string) {
  155. for _, pat := range patterns {
  156. p.logger.log.Converter.registerRemoveParams(pat)
  157. }
  158. }
  159. // ClearQueryParams will replace matching query params in the request URL with CLEARED.
  160. //
  161. // This only needs to be called during recording; the patterns will be saved to the
  162. // log for replay.
  163. func (p *Proxy) ClearQueryParams(patterns []string) {
  164. for _, pat := range patterns {
  165. p.logger.log.Converter.registerClearParams(pat)
  166. }
  167. }
  168. // IgnoreHeader will cause h to be ignored during matching on replay.
  169. // Deprecated: use RemoveRequestHeaders instead.
  170. func (p *Proxy) IgnoreHeader(h string) {
  171. p.ignoreHeaders[http.CanonicalHeaderKey(h)] = true
  172. }
  173. // Close closes the proxy. If the proxy is recording, it also writes the log.
  174. func (p *Proxy) Close() error {
  175. p.mproxy.Close()
  176. if p.logger != nil {
  177. return p.writeLog()
  178. }
  179. return nil
  180. }
  181. func (p *Proxy) writeLog() error {
  182. lg := p.logger.Extract()
  183. lg.Initial = p.Initial
  184. bytes, err := json.MarshalIndent(lg, "", " ")
  185. if err != nil {
  186. return err
  187. }
  188. return ioutil.WriteFile(p.filename, bytes, 0600) // only accessible by owner
  189. }
  190. // skipLoggingByHost disables logging for traffic to a particular host.
  191. type skipLoggingByHost string
  192. func (s skipLoggingByHost) ModifyRequest(req *http.Request) error {
  193. if strings.HasPrefix(req.Host, string(s)) {
  194. martian.NewContext(req).SkipLogging()
  195. }
  196. return nil
  197. }
  198. func (s skipLoggingByHost) ModifyResponse(res *http.Response) error {
  199. return s.ModifyRequest(res.Request)
  200. }