Skip to content

Commit 25fe7d3

Browse files
authored
Mask the value of the Authorization header if debug is enabled (#501)
* mask the value of the Authorization header if debug is enabled Signed-off-by: Ross Kirkpatrick <rkirkpat@akamai.com> Signed-off-by: rosskirkpat <rosskirkpat@outlook.com> * fix linters Signed-off-by: Ross Kirkpatrick <rosskirkpat@outlook.com> * make the auth header sanitization function private Signed-off-by: Ross Kirkpatrick <rosskirkpat@outlook.com> --------- Signed-off-by: Ross Kirkpatrick <rkirkpat@akamai.com> Signed-off-by: rosskirkpat <rosskirkpat@outlook.com> Signed-off-by: Ross Kirkpatrick <rosskirkpat@outlook.com>
1 parent 2f9320e commit 25fe7d3

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

client.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type (
9090
)
9191

9292
func init() {
93-
// Wether or not we will enable Resty debugging output
93+
// Whether we will enable Resty debugging output
9494
if apiDebug, ok := os.LookupEnv("LINODE_DEBUG"); ok {
9595
if parsed, err := strconv.ParseBool(apiDebug); err == nil {
9696
envDebug = parsed
@@ -122,6 +122,8 @@ func (c *Client) R(ctx context.Context) *resty.Request {
122122
func (c *Client) SetDebug(debug bool) *Client {
123123
c.debug = debug
124124
c.resty.SetDebug(debug)
125+
// this ensures that if there is an Authorization header present, the value is sanitized/masked
126+
c.sanitizeAuthorizationHeader()
125127

126128
return c
127129
}
@@ -412,6 +414,14 @@ func (c *Client) SetHeader(name, value string) {
412414
c.resty.SetHeader(name, value)
413415
}
414416

417+
func (c *Client) sanitizeAuthorizationHeader() {
418+
c.resty.OnRequestLog(func(r *resty.RequestLog) error {
419+
// masking authorization header
420+
r.Header.Set("Authorization", "Bearer *******************************")
421+
return nil
422+
})
423+
}
424+
415425
// NewClient factory to create new Client struct
416426
func NewClient(hc *http.Client) (client Client) {
417427
if hc != nil {

client_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package linodego
22

33
import (
4+
"bytes"
5+
"context"
46
"fmt"
7+
"reflect"
8+
"strings"
59
"testing"
610

711
"github.com/google/go-cmp/cmp"
12+
"github.com/jarcoal/httpmock"
13+
"github.com/linode/linodego/internal/testutil"
814
)
915

1016
func TestClient_SetAPIVersion(t *testing.T) {
@@ -139,3 +145,56 @@ api_version = v4beta
139145
[cool]
140146
token = blah
141147
`
148+
149+
func TestDebugLogSanitization(t *testing.T) {
150+
type instanceResponse struct {
151+
ID int `json:"id"`
152+
Region string `json:"region"`
153+
Label string `json:"label"`
154+
}
155+
156+
var testResp = instanceResponse{
157+
ID: 100,
158+
Region: "test-central",
159+
Label: "this-is-a-test-linode",
160+
}
161+
var lgr bytes.Buffer
162+
163+
plainTextToken := "NOTANAPIKEY"
164+
165+
mockClient := testutil.CreateMockClient(t, NewClient)
166+
logger := testutil.CreateLogger()
167+
mockClient.SetLogger(logger)
168+
logger.L.SetOutput(&lgr)
169+
170+
mockClient.SetDebug(true)
171+
if !mockClient.resty.Debug {
172+
t.Fatal("debug should be enabled")
173+
}
174+
mockClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", plainTextToken))
175+
176+
if mockClient.resty.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", plainTextToken) {
177+
t.Fatal("token not found in auth header")
178+
}
179+
180+
httpmock.RegisterRegexpResponder("GET", testutil.MockRequestURL("/linode/instances"),
181+
httpmock.NewJsonResponderOrPanic(200, &testResp))
182+
183+
result, err := doGETRequest[instanceResponse](
184+
context.Background(),
185+
mockClient,
186+
"/linode/instances",
187+
)
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
192+
logInfo := lgr.String()
193+
if !strings.Contains(logInfo, "Bearer *******************************") {
194+
t.Fatal("sanitized bearer token was expected")
195+
}
196+
197+
if !reflect.DeepEqual(*result, testResp) {
198+
t.Fatalf("actual response does not equal desired response: %s", cmp.Diff(result, testResponse))
199+
}
200+
}

internal/testutil/mock.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"encoding/json"
55
"fmt"
66
"io"
7+
"log"
78
"net/http"
9+
"os"
810
"reflect"
911
"regexp"
1012
"strings"
@@ -87,3 +89,40 @@ func CreateMockClient[T any](t *testing.T, createFunc func(*http.Client) T) *T {
8789
result := createFunc(client)
8890
return &result
8991
}
92+
93+
type Logger interface {
94+
Errorf(format string, v ...interface{})
95+
Warnf(format string, v ...interface{})
96+
Debugf(format string, v ...interface{})
97+
}
98+
99+
func CreateLogger() *TestLogger {
100+
l := &TestLogger{L: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
101+
return l
102+
}
103+
104+
var _ Logger = (*TestLogger)(nil)
105+
106+
type TestLogger struct {
107+
L *log.Logger
108+
}
109+
110+
func (l *TestLogger) Errorf(format string, v ...interface{}) {
111+
l.outputf("ERROR RESTY "+format, v...)
112+
}
113+
114+
func (l *TestLogger) Warnf(format string, v ...interface{}) {
115+
l.outputf("WARN RESTY "+format, v...)
116+
}
117+
118+
func (l *TestLogger) Debugf(format string, v ...interface{}) {
119+
l.outputf("DEBUG RESTY "+format, v...)
120+
}
121+
122+
func (l *TestLogger) outputf(format string, v ...interface{}) {
123+
if len(v) == 0 {
124+
l.L.Print(format)
125+
return
126+
}
127+
l.L.Printf(format, v...)
128+
}

0 commit comments

Comments
 (0)