Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
elasticapm: sanitize cookies and POST form fields
Sanitize cookies and POST form fields by matching their names against a configurable regular expression. By default, we match against: (?i:password|passwd|pwd|secret|.*key|.*token|.*session.*|.*credit.*|.*card.*) The pattern can be overridden by specifying the environment variable ELASTIC_APM_SANITIZE_FIELD_NAMES. The value is treated as a regular expression, and is wrapped as "(?i:<pattern>)" to match case-insensitively by default. If a key should be matched case-sensitively, the flag can be unset with the syntax "(?-i:<pattern>)".
- Loading branch information
Showing
6 changed files
with
281 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package elasticapm | ||
|
||
import ( | ||
"bytes" | ||
"regexp" | ||
|
||
"github.com/elastic/apm-agent-go/model" | ||
) | ||
|
||
const redacted = "[REDACTED]" | ||
|
||
// sanitizeRequest sanitizes HTTP request data, redacting | ||
// the values of cookies and forms whose corresponding keys | ||
// match the given regular expression. | ||
func sanitizeRequest(r *model.Request, re *regexp.Regexp) { | ||
var anyCookiesRedacted bool | ||
for _, c := range r.Cookies { | ||
if !re.MatchString(c.Name) { | ||
continue | ||
} | ||
c.Value = redacted | ||
anyCookiesRedacted = true | ||
} | ||
if anyCookiesRedacted && r.Headers != nil { | ||
var b bytes.Buffer | ||
for i, c := range r.Cookies { | ||
if i != 0 { | ||
b.WriteRune(';') | ||
} | ||
b.WriteString(c.String()) | ||
} | ||
r.Headers.Cookie = b.String() | ||
} | ||
if r.Body != nil && r.Body.Form != nil { | ||
for key, values := range r.Body.Form { | ||
if !re.MatchString(key) { | ||
continue | ||
} | ||
for i := range values { | ||
values[i] = redacted | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package elasticapm_test | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/elastic/apm-agent-go/contrib/apmhttp" | ||
"github.com/elastic/apm-agent-go/model" | ||
"github.com/elastic/apm-agent-go/transport/transporttest" | ||
) | ||
|
||
func TestSanitizeRequest(t *testing.T) { | ||
tracer, transport := transporttest.NewRecorderTracer() | ||
defer tracer.Close() | ||
|
||
mux := http.NewServeMux() | ||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
w.WriteHeader(http.StatusTeapot) | ||
})) | ||
h := &apmhttp.Handler{ | ||
Handler: mux, | ||
Tracer: tracer, | ||
} | ||
|
||
w := httptest.NewRecorder() | ||
req, _ := http.NewRequest("GET", "http://server.testing/", nil) | ||
for _, c := range []*http.Cookie{ | ||
{Name: "secret", Value: "top"}, | ||
{Name: "Custom-Credit-Card-Number", Value: "top"}, | ||
{Name: "sessionid", Value: "123"}, | ||
{Name: "user_id", Value: "456"}, | ||
} { | ||
req.AddCookie(c) | ||
} | ||
h.ServeHTTP(w, req) | ||
tracer.Flush(nil) | ||
|
||
payloads := transport.Payloads() | ||
require.Len(t, payloads, 1) | ||
transactions := payloads[0].Transactions() | ||
require.Len(t, transactions, 1) | ||
|
||
tx := transactions[0] | ||
assert.Equal(t, tx.Context.Request.Cookies, model.Cookies{ | ||
{Name: "Custom-Credit-Card-Number", Value: "[REDACTED]"}, | ||
{Name: "secret", Value: "[REDACTED]"}, | ||
{Name: "sessionid", Value: "[REDACTED]"}, | ||
{Name: "user_id", Value: "456"}, | ||
}) | ||
assert.Equal(t, | ||
"secret=[REDACTED];Custom-Credit-Card-Number=[REDACTED];sessionid=[REDACTED];user_id=456", | ||
tx.Context.Request.Headers.Cookie, | ||
) | ||
} | ||
|
||
func TestSetSanitizedFieldNamesNone(t *testing.T) { | ||
testSetSanitizedFieldNames(t, "top") | ||
} | ||
|
||
func TestSetSanitizedFieldNamesCaseSensitivity(t *testing.T) { | ||
// patterns are matched case-insensitively by default | ||
testSetSanitizedFieldNames(t, "[REDACTED]", "Secret") | ||
|
||
// patterns can be made case-sensitive by clearing the "i" flag. | ||
testSetSanitizedFieldNames(t, "top", "(?-i:Secret)") | ||
} | ||
|
||
func testSetSanitizedFieldNames(t *testing.T, expect string, sanitized ...string) { | ||
tracer, transport := transporttest.NewRecorderTracer() | ||
defer tracer.Close() | ||
tracer.SetSanitizedFieldNames(sanitized...) | ||
|
||
mux := http.NewServeMux() | ||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
w.WriteHeader(http.StatusTeapot) | ||
})) | ||
h := &apmhttp.Handler{ | ||
Handler: mux, | ||
Tracer: tracer, | ||
} | ||
|
||
w := httptest.NewRecorder() | ||
req, _ := http.NewRequest("GET", "http://server.testing/", nil) | ||
req.AddCookie(&http.Cookie{Name: "secret", Value: "top"}) | ||
h.ServeHTTP(w, req) | ||
tracer.Flush(nil) | ||
|
||
payloads := transport.Payloads() | ||
require.Len(t, payloads, 1) | ||
transactions := payloads[0].Transactions() | ||
require.Len(t, transactions, 1) | ||
|
||
tx := transactions[0] | ||
assert.Equal(t, tx.Context.Request.Cookies, model.Cookies{ | ||
{Name: "secret", Value: expect}, | ||
}) | ||
assert.Equal(t, "secret="+expect, tx.Context.Request.Headers.Cookie) | ||
} |
Oops, something went wrong.