Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

230 rindas
6.6 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. // +build go1.8
  15. // The proxy package provides a record/replay HTTP proxy. It is designed to support
  16. // both an in-memory API (cloud.google.com/go/httpreplay) and a standalone server
  17. // (cloud.google.com/go/httpreplay/cmd/httpr).
  18. package proxy
  19. // See github.com/google/martian/cmd/proxy/main.go for the origin of much of this.
  20. import (
  21. "crypto/tls"
  22. "crypto/x509"
  23. "encoding/json"
  24. "fmt"
  25. "io/ioutil"
  26. "net"
  27. "net/http"
  28. "net/url"
  29. "strings"
  30. "time"
  31. "github.com/google/martian"
  32. "github.com/google/martian/fifo"
  33. "github.com/google/martian/har"
  34. "github.com/google/martian/httpspec"
  35. "github.com/google/martian/martianlog"
  36. "github.com/google/martian/mitm"
  37. )
  38. // A Proxy is an HTTP proxy that supports recording or replaying requests.
  39. type Proxy struct {
  40. // The certificate that the proxy uses to participate in TLS.
  41. CACert *x509.Certificate
  42. // The URL of the proxy.
  43. URL *url.URL
  44. // Initial state of the client.
  45. Initial []byte
  46. mproxy *martian.Proxy
  47. filename string // for log
  48. logger *har.Logger // for recording only
  49. }
  50. // ForRecording returns a Proxy configured to record.
  51. func ForRecording(filename string, port int) (*Proxy, error) {
  52. p, err := newProxy(filename)
  53. if err != nil {
  54. return nil, err
  55. }
  56. // Configure the transport for the proxy's outgoing traffic. We MUST use
  57. // DialContext and not Dial. In Go 1.10, Setting Dial (but not DialContext)
  58. // disables HTTP2, and that gives different behavior than http.DefaultTransport.
  59. // (For example, GET
  60. // https://storage.googleapis.com/storage-library-test-bucket/gzipped-text.txt
  61. // with an "Accept-Encoding: gzip" header returns a Content-Length header with
  62. // HTTP2, but not HTTP1.)
  63. // We must also hide the type http.Transport from martian, because it looks for
  64. // http.Transport and sets the Dial field!
  65. p.mproxy.SetRoundTripper((*hideTransport)(&http.Transport{
  66. DialContext: (&net.Dialer{
  67. Timeout: 30 * time.Second,
  68. KeepAlive: 30 * time.Second,
  69. }).DialContext,
  70. TLSHandshakeTimeout: 10 * time.Second,
  71. ExpectContinueTimeout: time.Second,
  72. }))
  73. // Construct a group that performs the standard proxy stack of request/response
  74. // modifications.
  75. stack, _ := httpspec.NewStack("httpr") // second arg is an internal group that we don't need
  76. p.mproxy.SetRequestModifier(stack)
  77. p.mproxy.SetResponseModifier(stack)
  78. // Make a group for logging requests and responses.
  79. logGroup := fifo.NewGroup()
  80. skipAuth := skipLoggingByHost("accounts.google.com")
  81. logGroup.AddRequestModifier(skipAuth)
  82. logGroup.AddResponseModifier(skipAuth)
  83. p.logger = har.NewLogger()
  84. logGroup.AddRequestModifier(martian.RequestModifierFunc(
  85. func(req *http.Request) error { return withRedactedHeaders(req, p.logger) }))
  86. logGroup.AddResponseModifier(p.logger)
  87. stack.AddRequestModifier(logGroup)
  88. stack.AddResponseModifier(logGroup)
  89. // Ordinary debug logging.
  90. logger := martianlog.NewLogger()
  91. logger.SetDecode(true)
  92. stack.AddRequestModifier(logger)
  93. stack.AddResponseModifier(logger)
  94. if err := p.start(port); err != nil {
  95. return nil, err
  96. }
  97. return p, nil
  98. }
  99. type hideTransport http.Transport
  100. func (t *hideTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  101. return (*http.Transport)(t).RoundTrip(req)
  102. }
  103. func newProxy(filename string) (*Proxy, error) {
  104. mproxy := martian.NewProxy()
  105. // Set up a man-in-the-middle configuration with a CA certificate so the proxy can
  106. // participate in TLS.
  107. x509c, priv, err := mitm.NewAuthority("cloud.google.com/go/httpreplay", "HTTPReplay Authority", time.Hour)
  108. if err != nil {
  109. return nil, err
  110. }
  111. mc, err := mitm.NewConfig(x509c, priv)
  112. if err != nil {
  113. return nil, err
  114. }
  115. mc.SetValidity(time.Hour)
  116. mc.SetOrganization("cloud.google.com/go/httpreplay")
  117. mc.SkipTLSVerify(false)
  118. if err != nil {
  119. return nil, err
  120. }
  121. mproxy.SetMITM(mc)
  122. return &Proxy{
  123. mproxy: mproxy,
  124. CACert: x509c,
  125. filename: filename,
  126. }, nil
  127. }
  128. func (p *Proxy) start(port int) error {
  129. l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
  130. if err != nil {
  131. return err
  132. }
  133. p.URL = &url.URL{Scheme: "http", Host: l.Addr().String()}
  134. go p.mproxy.Serve(l)
  135. return nil
  136. }
  137. // Transport returns an http.Transport for clients who want to talk to the proxy.
  138. func (p *Proxy) Transport() *http.Transport {
  139. caCertPool := x509.NewCertPool()
  140. caCertPool.AddCert(p.CACert)
  141. return &http.Transport{
  142. TLSClientConfig: &tls.Config{RootCAs: caCertPool},
  143. Proxy: func(*http.Request) (*url.URL, error) { return p.URL, nil },
  144. }
  145. }
  146. // Close closes the proxy. If the proxy is recording, it also writes the log.
  147. func (p *Proxy) Close() error {
  148. p.mproxy.Close()
  149. if p.logger != nil {
  150. return p.writeLog()
  151. }
  152. return nil
  153. }
  154. type httprFile struct {
  155. Initial []byte
  156. HAR *har.HAR
  157. }
  158. func (p *Proxy) writeLog() error {
  159. f := httprFile{
  160. Initial: p.Initial,
  161. HAR: p.logger.ExportAndReset(),
  162. }
  163. bytes, err := json.MarshalIndent(f, "", " ")
  164. if err != nil {
  165. return err
  166. }
  167. return ioutil.WriteFile(p.filename, bytes, 0600) // only accessible by owner
  168. }
  169. // Headers that may contain sensitive data (auth tokens, keys).
  170. var sensitiveHeaders = []string{
  171. "Authorization",
  172. "X-Goog-Encryption-Key", // used by Cloud Storage for customer-supplied encryption
  173. "X-Goog-Copy-Source-Encryption-Key", // ditto
  174. }
  175. // withRedactedHeaders removes sensitive header contents before calling mod.
  176. func withRedactedHeaders(req *http.Request, mod martian.RequestModifier) error {
  177. // We have to change the headers, then log, then restore them.
  178. replaced := map[string]string{}
  179. for _, h := range sensitiveHeaders {
  180. if v := req.Header.Get(h); v != "" {
  181. replaced[h] = v
  182. req.Header.Set(h, "REDACTED")
  183. }
  184. }
  185. err := mod.ModifyRequest(req)
  186. for h, v := range replaced {
  187. req.Header.Set(h, v)
  188. }
  189. return err
  190. }
  191. // skipLoggingByHost disables logging for traffic to a particular host.
  192. type skipLoggingByHost string
  193. func (s skipLoggingByHost) ModifyRequest(req *http.Request) error {
  194. if strings.HasPrefix(req.Host, string(s)) {
  195. martian.NewContext(req).SkipLogging()
  196. }
  197. return nil
  198. }
  199. func (s skipLoggingByHost) ModifyResponse(res *http.Response) error {
  200. return s.ModifyRequest(res.Request)
  201. }