http.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package clients
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "sort"
  11. "strings"
  12. )
  13. // List of headers that need to be redacted
  14. var REDACT_HEADERS = []string{"x-auth-token", "x-auth-key", "x-service-token",
  15. "x-storage-token", "x-account-meta-temp-url-key", "x-account-meta-temp-url-key-2",
  16. "x-container-meta-temp-url-key", "x-container-meta-temp-url-key-2", "set-cookie",
  17. "x-subject-token"}
  18. // LogRoundTripper satisfies the http.RoundTripper interface and is used to
  19. // customize the default http client RoundTripper to allow logging.
  20. type LogRoundTripper struct {
  21. Rt http.RoundTripper
  22. }
  23. // RoundTrip performs a round-trip HTTP request and logs relevant information
  24. // about it.
  25. func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
  26. defer func() {
  27. if request.Body != nil {
  28. request.Body.Close()
  29. }
  30. }()
  31. var err error
  32. log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL)
  33. log.Printf("[DEBUG] OpenStack request Headers:\n%s", formatHeaders(request.Header))
  34. if request.Body != nil {
  35. request.Body, err = lrt.logRequest(request.Body, request.Header.Get("Content-Type"))
  36. if err != nil {
  37. return nil, err
  38. }
  39. }
  40. response, err := lrt.Rt.RoundTrip(request)
  41. if response == nil {
  42. return nil, err
  43. }
  44. log.Printf("[DEBUG] OpenStack Response Code: %d", response.StatusCode)
  45. log.Printf("[DEBUG] OpenStack Response Headers:\n%s", formatHeaders(response.Header))
  46. response.Body, err = lrt.logResponse(response.Body, response.Header.Get("Content-Type"))
  47. return response, err
  48. }
  49. // logRequest will log the HTTP Request details.
  50. // If the body is JSON, it will attempt to be pretty-formatted.
  51. func (lrt *LogRoundTripper) logRequest(original io.ReadCloser, contentType string) (io.ReadCloser, error) {
  52. defer original.Close()
  53. var bs bytes.Buffer
  54. _, err := io.Copy(&bs, original)
  55. if err != nil {
  56. return nil, err
  57. }
  58. // Handle request contentType
  59. if strings.HasPrefix(contentType, "application/json") {
  60. debugInfo := lrt.formatJSON(bs.Bytes())
  61. log.Printf("[DEBUG] OpenStack Request Body: %s", debugInfo)
  62. }
  63. return ioutil.NopCloser(strings.NewReader(bs.String())), nil
  64. }
  65. // logResponse will log the HTTP Response details.
  66. // If the body is JSON, it will attempt to be pretty-formatted.
  67. func (lrt *LogRoundTripper) logResponse(original io.ReadCloser, contentType string) (io.ReadCloser, error) {
  68. if strings.HasPrefix(contentType, "application/json") {
  69. var bs bytes.Buffer
  70. defer original.Close()
  71. _, err := io.Copy(&bs, original)
  72. if err != nil {
  73. return nil, err
  74. }
  75. debugInfo := lrt.formatJSON(bs.Bytes())
  76. if debugInfo != "" {
  77. log.Printf("[DEBUG] OpenStack Response Body: %s", debugInfo)
  78. }
  79. return ioutil.NopCloser(strings.NewReader(bs.String())), nil
  80. }
  81. log.Printf("[DEBUG] Not logging because OpenStack response body isn't JSON")
  82. return original, nil
  83. }
  84. // formatJSON will try to pretty-format a JSON body.
  85. // It will also mask known fields which contain sensitive information.
  86. func (lrt *LogRoundTripper) formatJSON(raw []byte) string {
  87. var data map[string]interface{}
  88. err := json.Unmarshal(raw, &data)
  89. if err != nil {
  90. log.Printf("[DEBUG] Unable to parse OpenStack JSON: %s", err)
  91. return string(raw)
  92. }
  93. // Mask known password fields
  94. if v, ok := data["auth"].(map[string]interface{}); ok {
  95. if v, ok := v["identity"].(map[string]interface{}); ok {
  96. if v, ok := v["password"].(map[string]interface{}); ok {
  97. if v, ok := v["user"].(map[string]interface{}); ok {
  98. v["password"] = "***"
  99. }
  100. }
  101. }
  102. }
  103. // Ignore the catalog
  104. if v, ok := data["token"].(map[string]interface{}); ok {
  105. if _, ok := v["catalog"]; ok {
  106. return ""
  107. }
  108. }
  109. pretty, err := json.MarshalIndent(data, "", " ")
  110. if err != nil {
  111. log.Printf("[DEBUG] Unable to re-marshal OpenStack JSON: %s", err)
  112. return string(raw)
  113. }
  114. return string(pretty)
  115. }
  116. // redactHeaders processes a headers object, returning a redacted list
  117. func redactHeaders(headers http.Header) (processedHeaders []string) {
  118. for name, header := range headers {
  119. var sensitive bool
  120. for _, redact_header := range REDACT_HEADERS {
  121. if strings.ToLower(name) == strings.ToLower(redact_header) {
  122. sensitive = true
  123. }
  124. }
  125. for _, v := range header {
  126. if sensitive {
  127. processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, "***"))
  128. } else {
  129. processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, v))
  130. }
  131. }
  132. }
  133. return
  134. }
  135. // formatHeaders processes a headers object plus a deliminator, returning a string
  136. func formatHeaders(headers http.Header) string {
  137. redactedHeaders := redactHeaders(headers)
  138. sort.Strings(redactedHeaders)
  139. return strings.Join(redactedHeaders, "\n")
  140. }