From f3da43d4836e2d54de86c3cd8d11e967711c4f4d Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 8 May 2017 11:03:01 -0600 Subject: [PATCH 1/4] Custom sanitize fields. Sanitize headers. --- http.go | 16 ++++++++++++---- http_test.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/http.go b/http.go index 32107b8..53e14e4 100644 --- a/http.go +++ b/http.go @@ -18,14 +18,15 @@ func NewHttp(req *http.Request) *Http { h := &Http{ Method: req.Method, Cookies: req.Header.Get("Cookie"), - Query: sanitizeQuery(req.URL.Query()).Encode(), + Query: url.Values(sanitizeValues(req.URL.Query())).Encode(), URL: proto + "://" + req.Host + req.URL.Path, Headers: make(map[string]string, len(req.Header)), } if addr, port, err := net.SplitHostPort(req.RemoteAddr); err == nil { h.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} } - for k, v := range req.Header { + + for k, v := range http.Header(sanitizeValues(req.Header)) { h.Headers[k] = strings.Join(v, ",") } return h @@ -33,10 +34,10 @@ func NewHttp(req *http.Request) *Http { var querySecretFields = []string{"password", "passphrase", "passwd", "secret"} -func sanitizeQuery(query url.Values) url.Values { +func sanitizeValues(query map[string][]string) map[string][]string { for _, keyword := range querySecretFields { for field := range query { - if strings.Contains(field, keyword) { + if strings.Contains(strings.ToLower(field), strings.ToLower(keyword)) { query[field] = []string{"********"} } } @@ -44,6 +45,13 @@ func sanitizeQuery(query url.Values) url.Values { return query } +// AddSanitizewField adds a custom sanitize field to the array of fields to +// search for and sanitize. This allows you to hide sensitive information in +// both the query string and headers. +func AddSanitizeField(field string) { + querySecretFields = append(querySecretFields, field) +} + // https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces type Http struct { // Required diff --git a/http_test.go b/http_test.go index 7d611b1..ba73c09 100644 --- a/http_test.go +++ b/http_test.go @@ -140,10 +140,38 @@ func parseQuery(q string) url.Values { func TestSanitizeQuery(t *testing.T) { for _, test := range sanitizeQueryTests { - actual := sanitizeQuery(parseQuery(test.input)) + actual := url.Values(sanitizeValues(parseQuery(test.input))) expected := parseQuery(test.output) if !reflect.DeepEqual(actual, expected) { t.Errorf("incorrect sanitization: got %+v, want %+v", actual, expected) } } } + +var sanitizeHeadersTest = []struct { + input, output string +}{ + {"foo=bar", "foo=bar"}, + {"password=foo", "password=********"}, + {"passphrase=foo", "passphrase=********"}, + {"passwd=foo", "passwd=********"}, + {"secret=foo", "secret=********"}, + {"secretstuff=foo", "secretstuff=********"}, + {"foo=bar&secret=foo", "foo=bar&secret=********"}, + {"secret=foo&secret=bar", "secret=********"}, +} + +func parseHeaders(q string) http.Header { + r, _ := url.ParseQuery(q) + return http.Header(r) +} + +func TestSanitizeHeaders(t *testing.T) { + for _, test := range sanitizeHeadersTest { + actual := http.Header(sanitizeValues(parseQuery(test.input))) + expected := parseHeaders(test.output) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("incorrect sanitization: got %+v, want %+v", actual, expected) + } + } +} From 4fddc9fd9d0b3d05f37bffa77a46c846fdf4f204 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 8 May 2017 11:19:52 -0600 Subject: [PATCH 2/4] Add ReportHandler --- http.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/http.go b/http.go index 53e14e4..eaad679 100644 --- a/http.go +++ b/http.go @@ -70,9 +70,12 @@ type Http struct { func (h *Http) Class() string { return "request" } -// Recovery handler to wrap the stdlib net/http Mux. +// Recovery handler to wrap the stdlib net/http Mux. This function will detect a +// panic, report it, and recover from the panic, preventing it from continuing +// further. +// // Example: -// http.HandleFunc("/", raven.RecoveryHandler(func(w http.ResponseWriter, r *http.Request) { +// http.HandleFunc("/", raven.ReportHandler(func(w http.ResponseWriter, r *http.Request) { // ... // })) func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { @@ -90,3 +93,27 @@ func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http handler(w, r) } } + +// Report handler to wrap the stdlib net/http Mux. This function will detect a +// panic, report it, and allow the panic to contune. +// +// Example: +// http.HandleFunc("/", raven.ReportHandler(func(w http.ResponseWriter, r *http.Request) { +// ... +// })) +func ReportHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if rval := recover(); rval != nil { + debug.PrintStack() + rvalStr := fmt.Sprint(rval) + packet := NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r)) + Capture(packet, nil) + w.WriteHeader(http.StatusInternalServerError) + panic(rval) + } + }() + + handler(w, r) + } +} From 4a1ad61c68d85a883006c1620d0d5263286d7e03 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Mon, 8 May 2017 11:41:32 -0600 Subject: [PATCH 3/4] Add ReportPanic(AndWait) --- client.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/client.go b/client.go index ccee839..0395953 100644 --- a/client.go +++ b/client.go @@ -728,6 +728,85 @@ func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interfa return DefaultClient.CapturePanicAndWait(f, tags, interfaces...) } +// ReportPanic reports a panic to the Sentry server if it occurs and allows that panic to continue. +func (client *Client) ReportPanic(tags map[string]string, interfaces ...Interface) { + // Note: This doesn't need to check for client, because we still want to go through the defer/recover path + // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the + // *Packet just to be thrown away, this should not be the normal case. Could be refactored to + // be completely noop though if we cared. + + err := recover() + if err == nil { + return + } + + var packet *Packet + switch rval := err.(type) { + case nil: + return + case error: + if client.shouldExcludeErr(rval.Error()) { + return + } + packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...) + default: + rvalStr := fmt.Sprint(rval) + if client.shouldExcludeErr(rvalStr) { + return + } + packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...) + } + + client.Capture(packet, tags) + // send the panic up the stack + panic(err) +} + +// ReportPanic reports a panic to the Sentry server if it occurs and allows that panic to continue. +func ReportPanic(tags map[string]string, interfaces ...Interface) { + DefaultClient.ReportPanic(tags, interfaces...) +} + +// ReportPanicAndWait is identical to ReportPanic, except it blocks and assures that the event was sent. +func (client *Client) ReportPanicAndWait(tags map[string]string, interfaces ...Interface) { + // Note: This doesn't need to check for client, because we still want to go through the defer/recover path + // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the + // *Packet just to be thrown away, this should not be the normal case. Could be refactored to + // be completely noop though if we cared. + + err := recover() + if err == nil { + return + } + + var packet *Packet + switch rval := err.(type) { + case nil: + return + case error: + if client.shouldExcludeErr(rval.Error()) { + return + } + packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...) + default: + rvalStr := fmt.Sprint(rval) + if client.shouldExcludeErr(rvalStr) { + return + } + packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...) + } + _, ch := client.Capture(packet, tags) + // block to make sure the report is sent + <-ch + // send the panic up the stack + panic(err) +} + +// ReportPanicAndWait is identical to ReportPanic, except it blocks and assures that the event was sent. +func ReportPanicAndWait(tags map[string]string, interfaces ...Interface) { + DefaultClient.ReportPanicAndWait(tags, interfaces...) +} + func (client *Client) Close() { close(client.queue) } From d8c19df89d9c361a9a53d70ffee6b7dc9ec640d1 Mon Sep 17 00:00:00 2001 From: Tyler Bunnell Date: Tue, 9 May 2017 12:46:58 -0600 Subject: [PATCH 4/4] Move recover() call to raw function --- client.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/client.go b/client.go index 0395953..9b750af 100644 --- a/client.go +++ b/client.go @@ -729,17 +729,11 @@ func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interfa } // ReportPanic reports a panic to the Sentry server if it occurs and allows that panic to continue. -func (client *Client) ReportPanic(tags map[string]string, interfaces ...Interface) { +func (client *Client) ReportPanic(err interface{}, tags map[string]string, interfaces ...Interface) { // Note: This doesn't need to check for client, because we still want to go through the defer/recover path // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the // *Packet just to be thrown away, this should not be the normal case. Could be refactored to // be completely noop though if we cared. - - err := recover() - if err == nil { - return - } - var packet *Packet switch rval := err.(type) { case nil: @@ -764,21 +758,20 @@ func (client *Client) ReportPanic(tags map[string]string, interfaces ...Interfac // ReportPanic reports a panic to the Sentry server if it occurs and allows that panic to continue. func ReportPanic(tags map[string]string, interfaces ...Interface) { - DefaultClient.ReportPanic(tags, interfaces...) + err := recover() + if err == nil { + return + } + DefaultClient.ReportPanic(err, tags, interfaces...) } // ReportPanicAndWait is identical to ReportPanic, except it blocks and assures that the event was sent. -func (client *Client) ReportPanicAndWait(tags map[string]string, interfaces ...Interface) { +func (client *Client) ReportPanicAndWait(err interface{}, tags map[string]string, interfaces ...Interface) { // Note: This doesn't need to check for client, because we still want to go through the defer/recover path // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the // *Packet just to be thrown away, this should not be the normal case. Could be refactored to // be completely noop though if we cared. - err := recover() - if err == nil { - return - } - var packet *Packet switch rval := err.(type) { case nil: @@ -804,7 +797,11 @@ func (client *Client) ReportPanicAndWait(tags map[string]string, interfaces ...I // ReportPanicAndWait is identical to ReportPanic, except it blocks and assures that the event was sent. func ReportPanicAndWait(tags map[string]string, interfaces ...Interface) { - DefaultClient.ReportPanicAndWait(tags, interfaces...) + err := recover() + if err == nil { + return + } + DefaultClient.ReportPanicAndWait(err, tags, interfaces...) } func (client *Client) Close() {