transport_proxy_auth.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. * Copyright (c) 2015, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package upstreamproxy
  20. import (
  21. "bytes"
  22. "fmt"
  23. "io/ioutil"
  24. "net/http"
  25. )
  26. // ProxyAuthTransport provides support for proxy authentication when doing plain HTTP
  27. // by tapping into HTTP conversation and adding authentication headers to the requests
  28. // when requested by server
  29. //
  30. // Limitation: in violation of https://golang.org/pkg/net/http/#RoundTripper,
  31. // ProxyAuthTransport is _not_ safe for concurrent RoundTrip calls. This is acceptable
  32. // for its use in Psiphon to provide upstream proxy support for meek, which makes only
  33. // serial RoundTrip calls. Concurrent RoundTrip calls will result in data race conditions
  34. // and undefined behavior during an authentication handshake.
  35. type ProxyAuthTransport struct {
  36. *http.Transport
  37. username string
  38. password string
  39. authenticator HttpAuthenticator
  40. customHeaders http.Header
  41. clonedBodyBuffer bytes.Buffer
  42. }
  43. func NewProxyAuthTransport(
  44. rawTransport *http.Transport,
  45. customHeaders http.Header) (*ProxyAuthTransport, error) {
  46. if rawTransport.Proxy == nil {
  47. return nil, fmt.Errorf("rawTransport must have Proxy")
  48. }
  49. tr := &ProxyAuthTransport{
  50. Transport: rawTransport,
  51. customHeaders: customHeaders,
  52. }
  53. proxyUrl, err := rawTransport.Proxy(nil)
  54. if err != nil {
  55. return nil, err
  56. }
  57. if proxyUrl.Scheme != "http" {
  58. return nil, fmt.Errorf("%s unsupported", proxyUrl.Scheme)
  59. }
  60. if proxyUrl.User != nil {
  61. tr.username = proxyUrl.User.Username()
  62. tr.password, _ = proxyUrl.User.Password()
  63. }
  64. // strip username and password from the proxyURL because
  65. // we do not want the wrapped transport to handle authentication
  66. proxyUrl.User = nil
  67. rawTransport.Proxy = http.ProxyURL(proxyUrl)
  68. return tr, nil
  69. }
  70. func (tr *ProxyAuthTransport) RoundTrip(request *http.Request) (*http.Response, error) {
  71. if request.URL.Scheme != "http" {
  72. return nil, fmt.Errorf("%s unsupported", request.URL.Scheme)
  73. }
  74. // Notes:
  75. //
  76. // - The 407 authentication loop assumes no concurrent calls of RoundTrip
  77. // and additionally assumes that serial RoundTrip calls will always
  78. // resuse any existing HTTP persistent conn. The entire authentication
  79. // handshake must occur on the same HTTP persistent conn.
  80. //
  81. // - Requests are cloned for the lifetime of the ProxyAuthTransport,
  82. // since we don't know when the next initial RoundTrip may need to enter
  83. // the 407 authentication loop, which requires the initial request to be
  84. // cloned and replayable. Even if we hook into the Close call for any
  85. // existing HTTP persistent conn, it could be that it closes only after
  86. // RoundTrip is called.
  87. //
  88. // - Cloning reuses a buffer (clonedBodyBuffer) to store the request body
  89. // to avoid excessive allocations.
  90. var cachedRequestBody []byte
  91. if request.Body != nil {
  92. tr.clonedBodyBuffer.Reset()
  93. tr.clonedBodyBuffer.ReadFrom(request.Body)
  94. request.Body.Close()
  95. cachedRequestBody = tr.clonedBodyBuffer.Bytes()
  96. }
  97. clonedRequest := cloneRequest(
  98. request, tr.customHeaders, cachedRequestBody)
  99. if tr.authenticator != nil {
  100. // For some authentication schemes (e.g., non-connection-based), once
  101. // an initial 407 has been handled, add necessary and sufficient
  102. // authentication headers to every request.
  103. err := tr.authenticator.PreAuthenticate(clonedRequest)
  104. if err != nil {
  105. return nil, err
  106. }
  107. }
  108. response, err := tr.Transport.RoundTrip(clonedRequest)
  109. if err != nil {
  110. return response, proxyError(err)
  111. }
  112. if response.StatusCode == 407 {
  113. authenticator, err := NewHttpAuthenticator(
  114. response, tr.username, tr.password)
  115. if err != nil {
  116. response.Body.Close()
  117. return nil, err
  118. }
  119. for {
  120. clonedRequest = cloneRequest(
  121. request, tr.customHeaders, cachedRequestBody)
  122. err = authenticator.Authenticate(clonedRequest, response)
  123. response.Body.Close()
  124. if err != nil {
  125. return nil, err
  126. }
  127. response, err = tr.Transport.RoundTrip(clonedRequest)
  128. if err != nil {
  129. return nil, proxyError(err)
  130. }
  131. if response.StatusCode != 407 {
  132. // Save the authenticator result to use for PreAuthenticate.
  133. tr.authenticator = authenticator
  134. break
  135. }
  136. }
  137. }
  138. return response, nil
  139. }
  140. // Based on https://github.com/golang/oauth2/blob/master/transport.go
  141. // Copyright 2014 The Go Authors. All rights reserved.
  142. func cloneRequest(r *http.Request, ch http.Header, body []byte) *http.Request {
  143. // shallow copy of the struct
  144. r2 := new(http.Request)
  145. *r2 = *r
  146. // deep copy of the Header
  147. r2.Header = make(http.Header)
  148. for k, s := range r.Header {
  149. r2.Header[k] = s
  150. }
  151. //Add custom headers to the cloned request
  152. for k, s := range ch {
  153. // handle special Host header case
  154. if k == "Host" {
  155. if len(s) > 0 {
  156. // hack around special case when http proxy is used:
  157. // https://golang.org/src/net/http/request.go#L474
  158. // using URL.Opaque, see URL.RequestURI() https://golang.org/src/net/url/url.go#L915
  159. if r2.URL.Opaque == "" {
  160. r2.URL.Opaque = r2.URL.Scheme + "://" + r2.Host + r2.URL.RequestURI()
  161. }
  162. r2.Host = s[0]
  163. }
  164. } else {
  165. r2.Header[k] = s
  166. }
  167. }
  168. if body != nil {
  169. r2.Body = ioutil.NopCloser(bytes.NewReader(body))
  170. }
  171. // A replayed request inherits the original request's deadline (and interruptability).
  172. r2 = r2.WithContext(r.Context())
  173. return r2
  174. }