state/remote/swift: Support Openstack request logging (#13583)

* provider/openstack: Expose LogRoundTripper fields externally

* state/remote/swift: Add support for debugging Openstack calls using
OS_DEBUG env variable.

* provider/openstack: Update LogRoundTripper to log headers aswell as body.

* Add `RedactHeaders` function in order to redact sensitive http Headers.
Refactor `logRequest` and `logResponse` to use `RedactHeaders` func.
This commit is contained in:
Gavin Williams 2017-04-15 15:11:28 +01:00 committed by Paul Stack
parent 1a016ac37f
commit c63ad9c0f8
4 changed files with 56 additions and 17 deletions

View File

@ -113,8 +113,8 @@ func (c *Config) loadAndValidate() error {
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
client.HTTPClient = http.Client{ client.HTTPClient = http.Client{
Transport: &LogRoundTripper{ Transport: &LogRoundTripper{
rt: transport, Rt: transport,
osDebug: osDebug, OsDebug: osDebug,
}, },
} }

View File

@ -24,8 +24,8 @@ import (
// LogRoundTripper satisfies the http.RoundTripper interface and is used to // LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default http client RoundTripper to allow for logging. // customize the default http client RoundTripper to allow for logging.
type LogRoundTripper struct { type LogRoundTripper struct {
rt http.RoundTripper Rt http.RoundTripper
osDebug bool OsDebug bool
} }
// RoundTrip performs a round-trip HTTP request and logs relevant information about it. // RoundTrip performs a round-trip HTTP request and logs relevant information about it.
@ -37,36 +37,36 @@ func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, er
}() }()
// for future reference, this is how to access the Transport struct: // for future reference, this is how to access the Transport struct:
//tlsconfig := lrt.rt.(*http.Transport).TLSClientConfig //tlsconfig := lrt.Rt.(*http.Transport).TLSClientConfig
var err error var err error
if lrt.osDebug { if lrt.OsDebug {
log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL) log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL)
if request.Body != nil { if request.Body != nil {
request.Body, err = lrt.logRequestBody(request.Body, request.Header) request.Body, err = lrt.logRequest(request.Body, request.Header)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
response, err := lrt.rt.RoundTrip(request) response, err := lrt.Rt.RoundTrip(request)
if response == nil { if response == nil {
return nil, err return nil, err
} }
if lrt.osDebug { if lrt.OsDebug {
response.Body, err = lrt.logResponseBody(response.Body, response.Header) response.Body, err = lrt.logResponse(response.Body, response.Header)
} }
return response, err return response, err
} }
// logRequestBody will log the HTTP Request body. // logRequest will log the HTTP Request details.
// If the body is JSON, it will attempt to be pretty-formatted. // If the body is JSON, it will attempt to be pretty-formatted.
func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) { func (lrt *LogRoundTripper) logRequest(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
defer original.Close() defer original.Close()
var bs bytes.Buffer var bs bytes.Buffer
@ -75,20 +75,25 @@ func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.
return nil, err return nil, err
} }
log.Printf("[DEBUG] Openstack Request headers:\n%s", strings.Join(RedactHeaders(headers), "\n"))
// Handle request contentType
contentType := headers.Get("Content-Type") contentType := headers.Get("Content-Type")
if strings.HasPrefix(contentType, "application/json") { if strings.HasPrefix(contentType, "application/json") {
debugInfo := lrt.formatJSON(bs.Bytes()) debugInfo := lrt.formatJSON(bs.Bytes())
log.Printf("[DEBUG] OpenStack Request Options: %s", debugInfo) log.Printf("[DEBUG] OpenStack Request Body: %s", debugInfo)
} else { } else {
log.Printf("[DEBUG] OpenStack Request Options: %s", bs.String()) log.Printf("[DEBUG] OpenStack Request Body: %s", bs.String())
} }
return ioutil.NopCloser(strings.NewReader(bs.String())), nil return ioutil.NopCloser(strings.NewReader(bs.String())), nil
} }
// logResponseBody will log the HTTP Response body. // logResponse will log the HTTP Response details.
// If the body is JSON, it will attempt to be pretty-formatted. // If the body is JSON, it will attempt to be pretty-formatted.
func (lrt *LogRoundTripper) logResponseBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) { func (lrt *LogRoundTripper) logResponse(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
log.Printf("[DEBUG] Openstack Response headers:\n%s", strings.Join(RedactHeaders(headers), "\n"))
contentType := headers.Get("Content-Type") contentType := headers.Get("Content-Type")
if strings.HasPrefix(contentType, "application/json") { if strings.HasPrefix(contentType, "application/json") {
var bs bytes.Buffer var bs bytes.Buffer

View File

@ -2,8 +2,10 @@ package openstack
import ( import (
"fmt" "fmt"
"net/http"
"os" "os"
"github.com/Unknwon/com"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
@ -58,3 +60,23 @@ func MapValueSpecs(d *schema.ResourceData) map[string]string {
} }
return m return m
} }
// List of headers that need to be redacted
var REDACT_HEADERS = []string{"x-auth-token", "x-auth-key", "x-service-token",
"x-storage-token", "x-account-meta-temp-url-key", "x-account-meta-temp-url-key-2",
"x-container-meta-temp-url-key", "x-container-meta-temp-url-key-2", "set-cookie",
"x-subject-token"}
// RedactHeaders processes a headers object, returning a redacted list
func RedactHeaders(headers http.Header) (processedHeaders []string) {
for name, header := range headers {
for _, v := range header {
if com.IsSliceContainsStr(REDACT_HEADERS, name) {
processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, "***"))
} else {
processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, v))
}
}
}
return
}

View File

@ -18,6 +18,7 @@ import (
"github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
tf_openstack "github.com/hashicorp/terraform/builtin/providers/openstack"
) )
const TFSTATE_NAME = "tfstate.tf" const TFSTATE_NAME = "tfstate.tf"
@ -249,8 +250,19 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
config.BuildNameToCertificate() config.BuildNameToCertificate()
} }
// if OS_DEBUG is set, log the requests and responses
var osDebug bool
if os.Getenv("OS_DEBUG") != "" {
osDebug = true
}
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
provider.HTTPClient.Transport = transport provider.HTTPClient = http.Client{
Transport: &tf_openstack.LogRoundTripper{
Rt: transport,
OsDebug: osDebug,
},
}
err = openstack.Authenticate(provider, ao) err = openstack.Authenticate(provider, ao)
if err != nil { if err != nil {