-
Notifications
You must be signed in to change notification settings - Fork 62
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
Mask password/token in log messages. #205
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,11 @@ package sonarqube | |
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/hashicorp/go-retryablehttp" | ||
) | ||
|
@@ -26,17 +29,17 @@ type Paging struct { | |
} | ||
|
||
// helper function to make api request to sonarqube | ||
func httpRequestHelper(client *retryablehttp.Client, method string, sonarqubeURL string, expectedResponseCode int, errormsg string) (http.Response, error) { | ||
func httpRequestHelper(client *retryablehttp.Client, method string, sonarqubeURL url.URL, expectedResponseCode int, errormsg string) (http.Response, error) { | ||
// Prepare request | ||
req, err := retryablehttp.NewRequest(method, sonarqubeURL, http.NoBody) | ||
req, err := retryablehttp.NewRequest(method, sonarqubeURL.String(), http.NoBody) | ||
if err != nil { | ||
return http.Response{}, fmt.Errorf("failed to prepare http request: %v. Request: %v", err, req) | ||
return http.Response{}, fmt.Errorf("failed to prepare http request: %v", censorError(err, sonarqubeURL.User.String())) | ||
} | ||
|
||
// Execute request | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return http.Response{}, fmt.Errorf("failed to execute http request: %v. Request: %v", err, req) | ||
return http.Response{}, fmt.Errorf("failed to execute http request: %v", censorError(err, sonarqubeURL.User.String())) | ||
} | ||
|
||
// Check response code | ||
|
@@ -57,3 +60,13 @@ func httpRequestHelper(client *retryablehttp.Client, method string, sonarqubeURL | |
|
||
return *resp, nil | ||
} | ||
|
||
// https://github.com/jdamata/terraform-provider-sonarqube/issues/201 | ||
// go-retryablehttp error contains the token/user:pass in plaintext. | ||
// We want to censor that secret before logging the error | ||
func censorError(err error, secret string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are more types of sensitive data than just the token used to connect to sonar - for example what if there is an error in creating a user? Or simply a debug statement logging the params - Would we be emitting the user details, including the password into the logs? I wouldn't necessarily "limit" this to errors (which you do by the name of the function) and perhaps consider how would this be used if it is a user request that fails - would you call this function multiple times, once for the API token and again to mask the password? Or pass in an array of sensitive data strings so you can make one call and sensor both? I think ensuring that all logs are masked is something to consider, perhaps for a later issue - but here ensuring that all errors are masked (including any supplied password) should be the goal of this PR. |
||
// Replace the secret with asterisks of the same length | ||
asterisks := strings.Repeat("*", len(secret)) | ||
result := strings.Replace(err.Error(), secret, asterisks, -1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with this (admittedly a small one) is that you are still giving away information about the secret - ie the length of it because you return the same number of characters as the original. |
||
return errors.New(result) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package sonarqube | ||
|
||
import ( | ||
"errors" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
// TestCensorError calls censorError with sample log messages , checking | ||
// for a valid return value. | ||
func TestCensorError(t *testing.T) { | ||
cases := []struct { | ||
errorMessage error | ||
token string | ||
expected string | ||
}{ | ||
{ | ||
errorMessage: errors.New("Error updating SonarQube user: failed to execute http request: POST https://PASSWORD:@sonarqube.example.com/api/users/update_identity_provider?login=gitlab-john-doe&newExternalIdentity=john-doe&newExternalProvider=gitlab giving up after 5 attempt(s). Request: &{0xab1940 0xc00021c600}"), | ||
token: "PASSWORD", | ||
expected: "Error updating SonarQube user: failed to execute http request: POST https://********:@sonarqube.example.com/api/users/update_identity_provider?login=gitlab-john-doe&newExternalIdentity=john-doe&newExternalProvider=gitlab giving up after 5 attempt(s). Request: &{0xab1940 0xc00021c600}", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I would have such a "complex" string in the test - things like the number of attempts, the request id etc are not relevant to the test - wouldn't a simple GET request to an endpoint suffice? I would also argue that the test is a bit brittle because it is testing too much. You don't want the test to fail because for example some future refactor truncated the message. That's a different problem and would have a different test :) And indeed ideally you would have a test which has no sensitive info to check that you do get back the original string. |
||
}, | ||
} | ||
for _, c := range cases { | ||
out := censorError(c.errorMessage, c.token) | ||
if !reflect.DeepEqual(out.Error(), c.expected) { | ||
t.Fatalf("Error matching output and expected: %#v vs %#v", out.Error(), c.expected) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not objecting to the change but why make this a url now and then have to
.String()
it everywhere?