Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(provider): redact sensitive headers from HTTP logging #479

Merged
merged 1 commit into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions tfe/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package tfe

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httputil"
"os"
"strings"
)

type loggingTransport struct {
name string
delegate http.RoundTripper
}

const (
EnvLog = "TF_LOG"
)

// redactedHeaders is a list of lowercase headers (with trailing colons) that signal that the
// header values should be redacted from logs
var redactedHeaders = []string{"authorization:", "proxy-authorization:"}

// IsDebugOrHigher returns whether or not the current log level is debug or trace
func IsDebugOrHigher() bool {
level := strings.ToUpper(os.Getenv(EnvLog))
return level == "DEBUG" || level == "TRACE"
}

// RoundTrip is a transport method that logs the request and response if the TF_LOG level is
// TRACE or DEBUG
func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if IsDebugOrHigher() {
reqData, err := httputil.DumpRequestOut(req, true)
if err == nil {
log.Printf("[DEBUG] "+logReqMsg, t.name, filterAndPrettyPrintLines(reqData))
} else {
log.Printf("[ERROR] %s API Request error: %#v", t.name, err)
}
}

resp, err := t.delegate.RoundTrip(req)
if err != nil {
return resp, err
}

if IsDebugOrHigher() {
respData, err := httputil.DumpResponse(resp, true)
if err == nil {
log.Printf("[DEBUG] "+logRespMsg, t.name, filterAndPrettyPrintLines(respData))
} else {
log.Printf("[ERROR] %s API Response error: %#v", t.name, err)
}
}

return resp, nil
}

// NewLoggingTransport wraps the given transport with a logger that logs request and
// response details
func NewLoggingTransport(name string, t http.RoundTripper) *loggingTransport {
return &loggingTransport{name, t}
}

// filterAndPrettyPrintLines iterates through a []byte line-by-line,
// redacting any sensitive lines and transforming any lines that are complete json into
// pretty-printed json.
func filterAndPrettyPrintLines(b []byte) string {
parts := strings.Split(string(b), "\n")
for i, p := range parts {
for _, check := range redactedHeaders {
if strings.HasPrefix(strings.ToLower(p), check) {
// This looks like a sensitive header to redact, so overwrite the entire line
parts[i] = fmt.Sprintf("%s <REDACTED>", p[0:len(check)])
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the continue redundant here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because I don't want to validate this as JSON (line 81-- it would not be valid)

}
}
if b := []byte(p); json.Valid(b) {
var out bytes.Buffer
_ = json.Indent(&out, b, "", " ") // already checked for validity
parts[i] = out.String()
}
}
return strings.Join(parts, "\n")
}

const logReqMsg = `%s API Request Details:
---[ REQUEST ]---------------------------------------
%s
-----------------------------------------------------`

const logRespMsg = `%s API Response Details:
---[ RESPONSE ]--------------------------------------
%s
-----------------------------------------------------`
5 changes: 2 additions & 3 deletions tfe/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
providerVersion "github.com/hashicorp/terraform-provider-tfe/version"
svchost "github.com/hashicorp/terraform-svchost"
Expand Down Expand Up @@ -184,7 +183,7 @@ func getClient(tfeHost, token string, insecure bool) (*tfe.Client, error) {
credsSrc := credentialsSource(config)
services := disco.NewWithCredentialsSource(credsSrc)
services.SetUserAgent(providerUaString)
services.Transport = logging.NewTransport("TFE Discovery", transport)
services.Transport = NewLoggingTransport("TFE Discovery", transport)

// Add any static host configurations service discovery object.
for userHost, hostConfig := range config.Hosts {
Expand Down Expand Up @@ -265,7 +264,7 @@ func getClient(tfeHost, token string, insecure bool) (*tfe.Client, error) {
}

// Wrap the configured transport to enable logging.
httpClient.Transport = logging.NewTransport("TFE", transport)
httpClient.Transport = NewLoggingTransport("TFE", transport)

// Create a new TFE client config
cfg := &tfe.Config{
Expand Down