client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // Copyright 2016 Google LLC. 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 jsonclient provides a simple client for fetching and parsing
  15. // JSON CT structures from a log.
  16. package jsonclient
  17. import (
  18. "bytes"
  19. "context"
  20. "crypto"
  21. "encoding/json"
  22. "errors"
  23. "fmt"
  24. "io"
  25. "log"
  26. "math/rand"
  27. "net/http"
  28. "net/url"
  29. "strconv"
  30. "strings"
  31. "time"
  32. ct "github.com/google/certificate-transparency-go"
  33. "github.com/google/certificate-transparency-go/x509"
  34. "golang.org/x/net/context/ctxhttp"
  35. "k8s.io/klog/v2"
  36. )
  37. const maxJitter = 250 * time.Millisecond
  38. type backoffer interface {
  39. // set adjusts/increases the current backoff interval (typically on retryable failure);
  40. // if the optional parameter is provided, this will be used as the interval if it is greater
  41. // than the currently set interval. Returns the current wait period so that it can be
  42. // logged along with any error message.
  43. set(*time.Duration) time.Duration
  44. // decreaseMultiplier reduces the current backoff multiplier, typically on success.
  45. decreaseMultiplier()
  46. // until returns the time until which the client should wait before making a request,
  47. // it may be in the past in which case it should be ignored.
  48. until() time.Time
  49. }
  50. // JSONClient provides common functionality for interacting with a JSON server
  51. // that uses cryptographic signatures.
  52. type JSONClient struct {
  53. uri string // the base URI of the server. e.g. https://ct.googleapis/pilot
  54. httpClient *http.Client // used to interact with the server via HTTP
  55. Verifier *ct.SignatureVerifier // nil for no verification (e.g. no public key available)
  56. logger Logger // interface to use for logging warnings and errors
  57. backoff backoffer // object used to store and calculate backoff information
  58. userAgent string // If set, this is sent as the UserAgent header.
  59. }
  60. // Logger is a simple logging interface used to log internal errors and warnings
  61. type Logger interface {
  62. // Printf formats and logs a message
  63. Printf(string, ...interface{})
  64. }
  65. // Options are the options for creating a new JSONClient.
  66. type Options struct {
  67. // Interface to use for logging warnings and errors, if nil the
  68. // standard library log package will be used.
  69. Logger Logger
  70. // PEM format public key to use for signature verification.
  71. PublicKey string
  72. // DER format public key to use for signature verification.
  73. PublicKeyDER []byte
  74. // UserAgent, if set, will be sent as the User-Agent header with each request.
  75. UserAgent string
  76. }
  77. // ParsePublicKey parses and returns the public key contained in opts.
  78. // If both opts.PublicKey and opts.PublicKeyDER are set, PublicKeyDER is used.
  79. // If neither is set, nil will be returned.
  80. func (opts *Options) ParsePublicKey() (crypto.PublicKey, error) {
  81. if len(opts.PublicKeyDER) > 0 {
  82. return x509.ParsePKIXPublicKey(opts.PublicKeyDER)
  83. }
  84. if opts.PublicKey != "" {
  85. pubkey, _ /* keyhash */, rest, err := ct.PublicKeyFromPEM([]byte(opts.PublicKey))
  86. if err != nil {
  87. return nil, err
  88. }
  89. if len(rest) > 0 {
  90. return nil, errors.New("extra data found after PEM key decoded")
  91. }
  92. return pubkey, nil
  93. }
  94. return nil, nil
  95. }
  96. type basicLogger struct{}
  97. func (bl *basicLogger) Printf(msg string, args ...interface{}) {
  98. log.Printf(msg, args...)
  99. }
  100. // RspError represents an error that occurred when processing a response from a server,
  101. // and also includes key details from the http.Response that triggered the error.
  102. type RspError struct {
  103. Err error
  104. StatusCode int
  105. Body []byte
  106. }
  107. // Error formats the RspError instance, focusing on the error.
  108. func (e RspError) Error() string {
  109. return e.Err.Error()
  110. }
  111. // New constructs a new JSONClient instance, for the given base URI, using the
  112. // given http.Client object (if provided) and the Options object.
  113. // If opts does not specify a public key, signatures will not be verified.
  114. func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) {
  115. pubkey, err := opts.ParsePublicKey()
  116. if err != nil {
  117. return nil, fmt.Errorf("invalid public key: %v", err)
  118. }
  119. var verifier *ct.SignatureVerifier
  120. if pubkey != nil {
  121. var err error
  122. verifier, err = ct.NewSignatureVerifier(pubkey)
  123. if err != nil {
  124. return nil, err
  125. }
  126. }
  127. if hc == nil {
  128. hc = new(http.Client)
  129. }
  130. logger := opts.Logger
  131. if logger == nil {
  132. logger = &basicLogger{}
  133. }
  134. return &JSONClient{
  135. uri: strings.TrimRight(uri, "/"),
  136. httpClient: hc,
  137. Verifier: verifier,
  138. logger: logger,
  139. backoff: &backoff{},
  140. userAgent: opts.UserAgent,
  141. }, nil
  142. }
  143. // BaseURI returns the base URI that the JSONClient makes queries to.
  144. func (c *JSONClient) BaseURI() string {
  145. return c.uri
  146. }
  147. // GetAndParse makes a HTTP GET call to the given path, and attempts to parse
  148. // the response as a JSON representation of the rsp structure. Returns the
  149. // http.Response, the body of the response, and an error (which may be of
  150. // type RspError if the HTTP response was available).
  151. func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[string]string, rsp interface{}) (*http.Response, []byte, error) {
  152. if ctx == nil {
  153. return nil, nil, errors.New("context.Context required")
  154. }
  155. // Build a GET request with URL-encoded parameters.
  156. vals := url.Values{}
  157. for k, v := range params {
  158. vals.Add(k, v)
  159. }
  160. fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode())
  161. klog.V(2).Infof("GET %s", fullURI)
  162. httpReq, err := http.NewRequest(http.MethodGet, fullURI, nil)
  163. if err != nil {
  164. return nil, nil, err
  165. }
  166. if len(c.userAgent) != 0 {
  167. httpReq.Header.Set("User-Agent", c.userAgent)
  168. }
  169. httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
  170. if err != nil {
  171. return nil, nil, err
  172. }
  173. // Read everything now so http.Client can reuse the connection.
  174. body, err := io.ReadAll(httpRsp.Body)
  175. httpRsp.Body.Close()
  176. if err != nil {
  177. return nil, nil, RspError{Err: fmt.Errorf("failed to read response body: %v", err), StatusCode: httpRsp.StatusCode, Body: body}
  178. }
  179. if httpRsp.StatusCode != http.StatusOK {
  180. return nil, nil, RspError{Err: fmt.Errorf("got HTTP Status %q", httpRsp.Status), StatusCode: httpRsp.StatusCode, Body: body}
  181. }
  182. if err := json.NewDecoder(bytes.NewReader(body)).Decode(rsp); err != nil {
  183. return nil, nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  184. }
  185. return httpRsp, body, nil
  186. }
  187. // PostAndParse makes a HTTP POST call to the given path, including the request
  188. // parameters, and attempts to parse the response as a JSON representation of
  189. // the rsp structure. Returns the http.Response, the body of the response, and
  190. // an error (which may be of type RspError if the HTTP response was available).
  191. func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
  192. if ctx == nil {
  193. return nil, nil, errors.New("context.Context required")
  194. }
  195. // Build a POST request with JSON body.
  196. postBody, err := json.Marshal(req)
  197. if err != nil {
  198. return nil, nil, err
  199. }
  200. fullURI := fmt.Sprintf("%s%s", c.uri, path)
  201. klog.V(2).Infof("POST %s", fullURI)
  202. httpReq, err := http.NewRequest(http.MethodPost, fullURI, bytes.NewReader(postBody))
  203. if err != nil {
  204. return nil, nil, err
  205. }
  206. if len(c.userAgent) != 0 {
  207. httpReq.Header.Set("User-Agent", c.userAgent)
  208. }
  209. httpReq.Header.Set("Content-Type", "application/json")
  210. httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
  211. // Read all of the body, if there is one, so that the http.Client can do Keep-Alive.
  212. var body []byte
  213. if httpRsp != nil {
  214. body, err = io.ReadAll(httpRsp.Body)
  215. httpRsp.Body.Close()
  216. }
  217. if err != nil {
  218. if httpRsp != nil {
  219. return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err}
  220. }
  221. return nil, nil, err
  222. }
  223. if httpRsp.StatusCode == http.StatusOK {
  224. if err = json.Unmarshal(body, &rsp); err != nil {
  225. return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err}
  226. }
  227. }
  228. return httpRsp, body, nil
  229. }
  230. // waitForBackoff blocks until the defined backoff interval or context has expired, if the returned
  231. // not before time is in the past it returns immediately.
  232. func (c *JSONClient) waitForBackoff(ctx context.Context) error {
  233. dur := time.Until(c.backoff.until().Add(time.Millisecond * time.Duration(rand.Intn(int(maxJitter.Seconds()*1000)))))
  234. if dur < 0 {
  235. dur = 0
  236. }
  237. backoffTimer := time.NewTimer(dur)
  238. select {
  239. case <-ctx.Done():
  240. return ctx.Err()
  241. case <-backoffTimer.C:
  242. }
  243. return nil
  244. }
  245. // PostAndParseWithRetry makes a HTTP POST call, but retries (with backoff) on
  246. // retryable errors; the caller should set a deadline on the provided context
  247. // to prevent infinite retries. Return values are as for PostAndParse.
  248. func (c *JSONClient) PostAndParseWithRetry(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
  249. if ctx == nil {
  250. return nil, nil, errors.New("context.Context required")
  251. }
  252. for {
  253. httpRsp, body, err := c.PostAndParse(ctx, path, req, rsp)
  254. if err != nil {
  255. // Don't retry context errors.
  256. if err == context.Canceled || err == context.DeadlineExceeded {
  257. return nil, nil, err
  258. }
  259. wait := c.backoff.set(nil)
  260. c.logger.Printf("Request to %s failed, backing-off %s: %s", c.uri, wait, err)
  261. } else {
  262. switch {
  263. case httpRsp.StatusCode == http.StatusOK:
  264. return httpRsp, body, nil
  265. case httpRsp.StatusCode == http.StatusRequestTimeout:
  266. // Request timeout, retry immediately
  267. c.logger.Printf("Request to %s timed out, retrying immediately", c.uri)
  268. case httpRsp.StatusCode == http.StatusServiceUnavailable:
  269. fallthrough
  270. case httpRsp.StatusCode == http.StatusTooManyRequests:
  271. var backoff *time.Duration
  272. // Retry-After may be either a number of seconds as a int or a RFC 1123
  273. // date string (RFC 7231 Section 7.1.3)
  274. if retryAfter := httpRsp.Header.Get("Retry-After"); retryAfter != "" {
  275. if seconds, err := strconv.Atoi(retryAfter); err == nil {
  276. b := time.Duration(seconds) * time.Second
  277. backoff = &b
  278. } else if date, err := time.Parse(time.RFC1123, retryAfter); err == nil {
  279. b := time.Until(date)
  280. backoff = &b
  281. }
  282. }
  283. wait := c.backoff.set(backoff)
  284. c.logger.Printf("Request to %s failed, backing-off for %s: got HTTP status %s", c.uri, wait, httpRsp.Status)
  285. default:
  286. return nil, nil, RspError{
  287. StatusCode: httpRsp.StatusCode,
  288. Body: body,
  289. Err: fmt.Errorf("got HTTP status %q", httpRsp.Status)}
  290. }
  291. }
  292. if err := c.waitForBackoff(ctx); err != nil {
  293. return nil, nil, err
  294. }
  295. }
  296. }