From a8c6bf874136d1f3f9833bbf68dcbd9868345186 Mon Sep 17 00:00:00 2001 From: Mirac Kara <55501260+mirackara@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:35:03 -0500 Subject: [PATCH 01/13] Release 3.25.0 (#782) * minor fix for complete security disable flag * Create FastHTTP Client Functions * FastHTTP Request Integration * FastHTTP example file * FastHTTP Request Integration * FastHTTP Response file * mod file * update security agent version * supportability metric * Created unit tests and removed extraneous file * Moved FastHTTP to internal instrumentation * Added testing for errors * chore: add logs-in-context example with logrus * chore: move example to specific folder * FastHTTP external segments/Client example * License for Server Example * Added test for external segment/minor fixes * FastHTTP Integration (#774) Added Support For FastHTTP * V3.25.0 Changelog (#781) * V3.25.0 * update version * corrected changelog for 3.25 release * Fixed test not passing * Update segments.go Removed extra function --------- Co-authored-by: aayush-ap Co-authored-by: Steve Willoughby <76975199+nr-swilloughby@users.noreply.github.com> Co-authored-by: Julien Erard Co-authored-by: Emilio Garcia Co-authored-by: Steve Willoughby --- CHANGELOG.md | 17 +++- v3/examples/client-fasthttp/main.go | 62 ++++++++++++ v3/examples/server-fasthttp/main.go | 58 +++++++++++ v3/go.mod | 1 + v3/integrations/nrfasthttp/go.mod | 9 ++ .../server-http-logs-in-context/main.go | 97 +++++++++++++++++++ .../{example => examples/server}/main.go | 0 v3/integrations/nrsecurityagent/go.mod | 2 +- .../nrsecurityagent/nrsecurityagent.go | 2 +- v3/newrelic/context.go | 14 +++ v3/newrelic/instrumentation.go | 91 ++++++++++++++++- v3/newrelic/internal_17_test.go | 43 ++++++++ v3/newrelic/internal_context_test.go | 25 +++++ v3/newrelic/segments.go | 33 +++++++ v3/newrelic/version.go | 2 +- 15 files changed, 447 insertions(+), 9 deletions(-) create mode 100644 v3/examples/client-fasthttp/main.go create mode 100644 v3/examples/server-fasthttp/main.go create mode 100644 v3/integrations/nrfasthttp/go.mod create mode 100644 v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go rename v3/integrations/nrlogrus/{example => examples/server}/main.go (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5719b313b..92ae88fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,30 @@ +## 3.25.0 +### Added + * Added Support for FastHTTP package + * Added newrelic.WrapHandleFuncFastHTTP() and newrelic.StartExternalSegmentFastHTTP() functions to instrument fasthttp context and create wrapped handlers. These functions work similarly to the existing ones for net/http + * Added client-fasthttp and server-fasthttp examples to help get started with FastHTTP integration + +### Fixed + * Corrected a bug where the security agent failed to correctly parse the `NEW_RELIC_SECURITY_AGENT_ENABLED` environment variable. + +### Support statement +We use the latest version of the Go language. At minimum, you should be using no version of Go older than what is supported by the Go team themselves (i.e., Go versions 1.19 and later are supported). +We recommend updating to the latest agent version as soon as it’s available. If you can’t upgrade to the latest version, update your agents to a version no more than 90 days old. Read more about keeping agents up to date. (https://docs.newrelic.com/docs/new-relic-solutions/new-relic-one/install-configure/update-new-relic-agent/) +See the [Go agent EOL Policy](/docs/apm/agents/go-agent/get-started/go-agent-eol-policy/) for details about supported versions of the Go agent and third-party components. + ## 3.24.1 ### Fixed * Performance improvement around calls to security agent. In some cases, unnecessary setup operations were being performed even if there was no security agent present to use that. These are now conditional on the security agent being present in the application (note that this will enable the setup code if the security agent is *present* in the application, regardless of whether it's currently enabled to run). This affects: * Base agent code (updated to v3.24.1) * `nrmongo` integration (updated to v1.1.1) + * Resolved a race condition caused by the above-mentioned calls to the security agent. * Fixed unit tests for integrations which were failing because code level metrics are enabled by default now: * `nrawssdk-v1` (updated to v1.1.2) * `nrawssdk-v2` (updated to v1.2.2) * `nrecho-v3` (updated to v1.0.2) * `nrecho-v4` (updated to v1.0.4) - * `nrhttprouter` (updated to + * `nrhttprouter` (updated to v1.0.2) * `nrlambda` (updated to v1.2.2) * `nrnats` (updated to v1.1.5) * `nrredis-v8` (updated to v1.0.1) diff --git a/v3/examples/client-fasthttp/main.go b/v3/examples/client-fasthttp/main.go new file mode 100644 index 000000000..7a26b605f --- /dev/null +++ b/v3/examples/client-fasthttp/main.go @@ -0,0 +1,62 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +func doRequest(txn *newrelic.Transaction) error { + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(resp) + + req.SetRequestURI("http://localhost:8080/hello") + req.Header.SetMethod("GET") + + ctx := &fasthttp.RequestCtx{} + seg := newrelic.StartExternalSegmentFastHTTP(txn, ctx) + defer seg.End() + + err := fasthttp.Do(req, resp) + if err != nil { + return err + } + + fmt.Println("Response Code is ", resp.StatusCode()) + return nil + +} + +func main() { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("Client App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + txn := app.StartTransaction("client-txn") + err = doRequest(txn) + if err != nil { + txn.NoticeError(err) + } + txn.End() + + // Shut down the application to flush data to New Relic. + app.Shutdown(10 * time.Second) +} diff --git a/v3/examples/server-fasthttp/main.go b/v3/examples/server-fasthttp/main.go new file mode 100644 index 000000000..8ed532670 --- /dev/null +++ b/v3/examples/server-fasthttp/main.go @@ -0,0 +1,58 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "errors" + "fmt" + "os" + "time" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + + "github.com/valyala/fasthttp" +) + +func index(ctx *fasthttp.RequestCtx) { + ctx.WriteString("Hello World") +} + +func noticeError(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + txn.NoticeError(errors.New("my error message")) +} + +func main() { + // Initialize New Relic + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("FastHTTP App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDistributedTracerEnabled(true), + ) + if err != nil { + fmt.Println(err) + return + } + if err := app.WaitForConnection(5 * time.Second); nil != err { + fmt.Println(err) + } + _, helloRoute := newrelic.WrapHandleFuncFastHTTP(app, "/hello", index) + _, errorRoute := newrelic.WrapHandleFuncFastHTTP(app, "/error", noticeError) + handler := func(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + method := string(ctx.Method()) + + switch { + case method == "GET" && path == "/hello": + helloRoute(ctx) + case method == "GET" && path == "/error": + errorRoute(ctx) + } + } + + // Start the server with the instrumented handler + fasthttp.ListenAndServe(":8080", handler) +} diff --git a/v3/go.mod b/v3/go.mod index d00562a5a..910c288d1 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/golang/protobuf v1.5.3 + github.com/valyala/fasthttp v1.49.0 google.golang.org/grpc v1.54.0 ) diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod new file mode 100644 index 000000000..d4e207230 --- /dev/null +++ b/v3/integrations/nrfasthttp/go.mod @@ -0,0 +1,9 @@ +module github.com/newrelic/go-agent/v3/integrations/nrfasthttp + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.23.1 + github.com/stretchr/testify v1.8.4 + github.com/valyala/fasthttp v1.48.0 +) diff --git a/v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go b/v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go new file mode 100644 index 000000000..7ce3a5782 --- /dev/null +++ b/v3/integrations/nrlogrus/examples/server-http-logs-in-context/main.go @@ -0,0 +1,97 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// An application that illustrates Distributed Tracing with Logs-in-Context +// when using http.Server or similar frameworks. +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus" + newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/sirupsen/logrus" +) + +type handler struct { + App *newrelic.Application +} + +func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { + // The call to StartTransaction must include the response writer and the + // request. + txn := h.App.StartTransaction("server-txn") + defer txn.End() + + txnLogger := logrus.WithContext(newrelic.NewContext(context.Background(), txn)) + + writer = txn.SetWebResponse(writer) + txn.SetWebRequestHTTP(req) + + if req.URL.String() == "/segments" { + defer txn.StartSegment("f1").End() + + txnLogger.Infof("/segments just started") + + func() { + defer txn.StartSegment("f2").End() + + io.WriteString(writer, "segments!") + time.Sleep(10 * time.Millisecond) + + txnLogger.Infof("segment func just about to complete") + }() + time.Sleep(10 * time.Millisecond) + } else { + // Transaction.WriteHeader has to be used instead of invoking + // WriteHeader on the response writer. + writer.WriteHeader(http.StatusNotFound) + } + txnLogger.Infof("handler completing") +} + +func makeApplication() (*newrelic.Application, error) { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("HTTP Server App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + ) + if nil != err { + return nil, err + } + nrlogrusFormatter := nrlogrus.NewFormatter(app, &logrus.TextFormatter{}) + logrus.SetFormatter(nrlogrusFormatter) + // Alternatively and if preferred, create a new logger and use that logger + // for logging with + // log := logrus.New() + // log.SetFormatter(nrlogrusFormatter) + + // Wait for the application to connect. + if err = app.WaitForConnection(5 * time.Second); nil != err { + return nil, err + } + + return app, nil +} + +func main() { + + app, err := makeApplication() + if nil != err { + fmt.Println(err) + os.Exit(1) + } + + logrus.Infof("Application Starting") + + server := http.Server{ + Addr: ":8000", + Handler: &handler{App: app}, + } + + server.ListenAndServe() +} diff --git a/v3/integrations/nrlogrus/example/main.go b/v3/integrations/nrlogrus/examples/server/main.go similarity index 100% rename from v3/integrations/nrlogrus/example/main.go rename to v3/integrations/nrlogrus/examples/server/main.go diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 2e805f3b8..990ec08c0 100644 --- a/v3/integrations/nrsecurityagent/go.mod +++ b/v3/integrations/nrsecurityagent/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrsecurityagent go 1.19 require ( - github.com/newrelic/csec-go-agent v0.3.0 + github.com/newrelic/csec-go-agent v0.4.0 github.com/newrelic/go-agent/v3 v3.24.1 github.com/newrelic/go-agent/v3/integrations/nrsqlite3 v1.2.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/v3/integrations/nrsecurityagent/nrsecurityagent.go b/v3/integrations/nrsecurityagent/nrsecurityagent.go index acc994eca..c7264d7ad 100644 --- a/v3/integrations/nrsecurityagent/nrsecurityagent.go +++ b/v3/integrations/nrsecurityagent/nrsecurityagent.go @@ -37,7 +37,7 @@ func defaultSecurityConfig() SecurityConfig { // If env is set to false,the security module is not loaded func isSecurityAgentEnabled() bool { if env := os.Getenv("NEW_RELIC_SECURITY_AGENT_ENABLED"); env != "" { - if b, err := strconv.ParseBool("false"); err == nil { + if b, err := strconv.ParseBool(env); err == nil { return b } } diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 5ce186f3d..731dcb73f 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) // NewContext returns a new context.Context that carries the provided @@ -52,3 +53,16 @@ func transactionFromRequestContext(req *http.Request) *Transaction { } return txn } + +func transactionFromRequestContextFastHTTP(ctx *fasthttp.RequestCtx) *Transaction { + var txn *Transaction + if nil != ctx { + txn := ctx.UserValue("transaction").(*Transaction) + return txn + } + + if txn != nil { + return txn + } + return nil +} diff --git a/v3/newrelic/instrumentation.go b/v3/newrelic/instrumentation.go index 4e37e5316..e4351a955 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -5,18 +5,41 @@ package newrelic import ( "net/http" + + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" ) +type fasthttpWrapperResponse struct { + ctx *fasthttp.RequestCtx +} + +func (rw fasthttpWrapperResponse) Header() http.Header { + hdrs := http.Header{} + rw.ctx.Request.Header.VisitAll(func(key, value []byte) { + hdrs.Add(string(key), string(value)) + }) + return hdrs +} + +func (rw fasthttpWrapperResponse) Write(b []byte) (int, error) { + return rw.ctx.Write(b) +} + +func (rw fasthttpWrapperResponse) WriteHeader(code int) { + rw.ctx.SetStatusCode(code) +} + // instrumentation.go contains helpers built on the lower level api. // WrapHandle instruments http.Handler handlers with Transactions. To // instrument this code: // -// http.Handle("/foo", myHandler) +// http.Handle("/foo", myHandler) // // Perform this replacement: // -// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) +// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) // // WrapHandle adds the Transaction to the request's context. Access it using // FromContext to add attributes, create segments, or notice errors: @@ -76,6 +99,56 @@ func WrapHandle(app *Application, pattern string, handler http.Handler, options }) } +func WrapHandleFastHTTP(app *Application, pattern string, handler fasthttp.RequestHandler, options ...TraceOption) (string, fasthttp.RequestHandler) { + if app == nil { + return pattern, handler + } + + // add the wrapped function to the trace options as the source code reference point + // (but only if we know we're collecting CLM for this transaction and the user didn't already + // specify a different code location explicitly). + cache := NewCachedCodeLocation() + + return pattern, func(ctx *fasthttp.RequestCtx) { + var tOptions *traceOptSet + var txnOptionList []TraceOption + + if app.app != nil && app.app.run != nil && app.app.run.Config.CodeLevelMetrics.Enabled { + tOptions = resolveCLMTraceOptions(options) + if tOptions != nil && !tOptions.SuppressCLM && (tOptions.DemandCLM || app.app.run.Config.CodeLevelMetrics.Scope == 0 || (app.app.run.Config.CodeLevelMetrics.Scope&TransactionCLM) != 0) { + // we are for sure collecting CLM here, so go to the trouble of collecting this code location if nothing else has yet. + if tOptions.LocationOverride == nil { + if loc, err := cache.FunctionLocation(handler); err == nil { + WithCodeLocation(loc)(tOptions) + } + } + } + } + if tOptions == nil { + // we weren't able to curate the options above, so pass whatever we were given downstream + txnOptionList = options + } else { + txnOptionList = append(txnOptionList, withPreparedOptions(tOptions)) + } + + method := string(ctx.Method()) + path := string(ctx.Path()) + txn := app.StartTransaction(method+" "+path, txnOptionList...) + ctx.SetUserValue("transaction", txn) + defer txn.End() + r := &http.Request{} + fasthttpadaptor.ConvertRequest(ctx, r, true) + resp := fasthttpWrapperResponse{ctx: ctx} + + txn.SetWebResponse(resp) + txn.SetWebRequestHTTP(r) + + r = RequestWithTransactionContext(r, txn) + + handler(ctx) + } +} + // WrapHandleFunc instruments handler functions using Transactions. To // instrument this code: // @@ -111,15 +184,23 @@ func WrapHandleFunc(app *Application, pattern string, handler func(http.Response return p, func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } -// +func WrapHandleFuncFastHTTP(app *Application, pattern string, handler func(*fasthttp.RequestCtx), options ...TraceOption) (string, func(*fasthttp.RequestCtx)) { + // add the wrapped function to the trace options as the source code reference point + // (to the beginning of the option list, so that the user can override this) + + p, h := WrapHandleFastHTTP(app, pattern, fasthttp.RequestHandler(handler), options...) + return p, func(ctx *fasthttp.RequestCtx) { h(ctx) } +} + // WrapListen wraps an HTTP endpoint reference passed to functions like http.ListenAndServe, // which causes security scanning to be done for that incoming endpoint when vulnerability // scanning is enabled. It returns the endpoint string, so you can replace a call like // -// http.ListenAndServe(":8000", nil) +// http.ListenAndServe(":8000", nil) +// // with -// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) // +// http.ListenAndServe(newrelic.WrapListen(":8000"), nil) func WrapListen(endpoint string) string { if IsSecurityAgentPresent() { secureAgent.SendEvent("APP_INFO", endpoint) diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 82d1dc8f1..5ba7b6c7e 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func myErrorHandler(w http.ResponseWriter, req *http.Request) { @@ -18,6 +19,48 @@ func myErrorHandler(w http.ResponseWriter, req *http.Request) { txn.NoticeError(myError{}) } +func myErrorHandlerFastHTTP(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*Transaction) + txn.NoticeError(myError{}) +} + +func TestWrapHandleFastHTTPFunc(t *testing.T) { + app := testApp(nil, ConfigDistributedTracerEnabled(true), t) + + _, wrappedHandler := WrapHandleFuncFastHTTP(app.Application, "/hello", myErrorHandlerFastHTTP) + + if wrappedHandler == nil { + t.Error("Error when creating a wrapped handler") + } + ctx := &fasthttp.RequestCtx{} + ctx.Request.Header.SetMethod("GET") + ctx.Request.SetRequestURI("/hello") + wrappedHandler(ctx) + app.ExpectErrors(t, []internal.WantError{{ + TxnName: "WebTransaction/Go/GET /hello", + Msg: "my msg", + Klass: "newrelic.myError", + }}) + + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: nil}, + {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, + {Name: "WebTransactionTotalTime/Go/GET /hello", Scope: "", Forced: false, Data: nil}, + {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, + {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, + {Name: "Apdex", Scope: "", Forced: true, Data: nil}, + {Name: "Apdex/Go/GET /hello", Scope: "", Forced: false, Data: nil}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, + {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, + {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, + {Name: "Errors/allWeb", Scope: "", Forced: true, Data: singleCount}, + {Name: "Errors/WebTransaction/Go/GET /hello", Scope: "", Forced: true, Data: singleCount}, + {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, + {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, + }) +} + func TestWrapHandleFunc(t *testing.T) { app := testApp(nil, ConfigDistributedTracerEnabled(false), t) mux := http.NewServeMux() diff --git a/v3/newrelic/internal_context_test.go b/v3/newrelic/internal_context_test.go index 1e15e61cd..51372d382 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" + "github.com/valyala/fasthttp" ) func TestWrapHandlerContext(t *testing.T) { @@ -36,6 +37,30 @@ func TestWrapHandlerContext(t *testing.T) { {Name: "Custom/mySegment", Scope: scope, Forced: false, Data: nil}, }) } +func TestExternalSegmentFastHTTP(t *testing.T) { + app := testApp(nil, ConfigDistributedTracerEnabled(false), t) + txn := app.StartTransaction("myTxn") + + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(resp) + + req.SetRequestURI("http://localhost:8080/hello") + req.Header.SetMethod("GET") + + ctx := &fasthttp.RequestCtx{} + seg := StartExternalSegmentFastHTTP(txn, ctx) + defer seg.End() + + txn.End() + app.ExpectMetrics(t, []internal.WantMetric{ + {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, + {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, + {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, + }) +} func TestStartExternalSegmentNilTransaction(t *testing.T) { // Test that StartExternalSegment pulls the transaction from the diff --git a/v3/newrelic/segments.go b/v3/newrelic/segments.go index 91f8fcc5a..65344033a 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -5,6 +5,9 @@ package newrelic import ( "net/http" + + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" ) // SegmentStartTime is created by Transaction.StartSegmentNow and marks the @@ -337,6 +340,36 @@ func StartExternalSegment(txn *Transaction, request *http.Request) *ExternalSegm return s } +func StartExternalSegmentFastHTTP(txn *Transaction, ctx *fasthttp.RequestCtx) *ExternalSegment { + if nil == txn { + txn = transactionFromRequestContextFastHTTP(ctx) + } + request := &http.Request{} + + fasthttpadaptor.ConvertRequest(ctx, request, true) + s := &ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: request, + } + if IsSecurityAgentPresent() { + s.secureAgentEvent = secureAgent.SendEvent("OUTBOUND", request) + } + + if request != nil && request.Header != nil { + for key, values := range s.outboundHeaders() { + for _, value := range values { + request.Header.Set(key, value) + } + } + + if IsSecurityAgentPresent() { + secureAgent.DistributedTraceHeaders(request, s.secureAgentEvent) + } + } + + return s +} + func addSpanAttr(start SegmentStartTime, key string, val interface{}) { if nil == start.thread { return diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index b1a8adefd..c6b63256f 100644 --- a/v3/newrelic/version.go +++ b/v3/newrelic/version.go @@ -11,7 +11,7 @@ import ( const ( // Version is the full string version of this Go Agent. - Version = "3.24.1" + Version = "3.25.0" ) var ( From 24c46487688f21257eaa5bebd4213857ca3baea8 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Thu, 19 Oct 2023 11:52:17 +0000 Subject: [PATCH 02/13] fix out of memory issue for req body --- v3/newrelic/transaction.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index d1d519c63..1b7b8eb92 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -246,20 +246,14 @@ func serverName(r *http.Request) string { return "" } -func reqBody(req *http.Request) []byte { - var bodyBuffer bytes.Buffer - requestBuffer := make([]byte, 0) - bodyReader := io.TeeReader(req.Body, &bodyBuffer) - - if bodyReader != nil && req.Body != nil { - reqBuffer, err := io.ReadAll(bodyReader) - if err == nil { - requestBuffer = reqBuffer - } - r := io.NopCloser(bytes.NewBuffer(requestBuffer)) - req.Body = r +func reqBody(req *http.Request) *bytes.Buffer { + if IsSecurityAgentPresent() { + buf := &bytes.Buffer{} + tee := io.TeeReader(req.Body, buf) + req.Body = io.NopCloser(tee) + return buf } - return bytes.TrimRight(requestBuffer, "\x00") + return nil } // SetWebRequest marks the transaction as a web transaction. SetWebRequest @@ -607,7 +601,7 @@ type WebRequest struct { // The following fields are needed for the secure agent's vulnerability // detection features. - Body []byte + Body *bytes.Buffer ServerName string Type string RemoteAddress string @@ -633,7 +627,7 @@ func (webrequest WebRequest) GetHost() string { return webrequest.Host } -func (webrequest WebRequest) GetBody() []byte { +func (webrequest WebRequest) GetBody() *bytes.Buffer { return webrequest.Body } From 93283f410e33d10efe48d721fac087475de2fdc8 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Fri, 20 Oct 2023 04:47:00 +0000 Subject: [PATCH 03/13] Added new config parameter for read request body --- .../nrsecurityagent/nrsecurityagent.go | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/v3/integrations/nrsecurityagent/nrsecurityagent.go b/v3/integrations/nrsecurityagent/nrsecurityagent.go index c7264d7ad..bb21f6765 100644 --- a/v3/integrations/nrsecurityagent/nrsecurityagent.go +++ b/v3/integrations/nrsecurityagent/nrsecurityagent.go @@ -30,6 +30,7 @@ func defaultSecurityConfig() SecurityConfig { cfg.Security.Mode = "IAST" cfg.Security.Agent.Enabled = true cfg.Security.Detection.Rxss.Enabled = true + cfg.Security.Request.BodyLimit = 300 return cfg } @@ -108,6 +109,8 @@ func ConfigSecurityFromYaml() ConfigOption { // NEW_RELIC_SECURITY_MODE scanning mode: "IAST" for now // NEW_RELIC_SECURITY_AGENT_ENABLED (boolean) // NEW_RELIC_SECURITY_DETECTION_RXSS_ENABLED (boolean) +// NEW_RELIC_SECURITY_REQUEST_BODY_LIMIT (integer) set limit on read request body in kb. By default, this is "300" + func ConfigSecurityFromEnvironment() ConfigOption { return func(cfg *SecurityConfig) { assignBool := func(field *bool, name string) { @@ -125,11 +128,22 @@ func ConfigSecurityFromEnvironment() ConfigOption { } } + assignInt := func(field *int, name string) { + if env := os.Getenv(name); env != "" { + if i, err := strconv.Atoi(env); nil != err { + cfg.Error = fmt.Errorf("invalid %s value: %s", name, env) + } else { + *field = i + } + } + } + assignBool(&cfg.Security.Enabled, "NEW_RELIC_SECURITY_ENABLED") assignString(&cfg.Security.Validator_service_url, "NEW_RELIC_SECURITY_VALIDATOR_SERVICE_URL") assignString(&cfg.Security.Mode, "NEW_RELIC_SECURITY_MODE") assignBool(&cfg.Security.Agent.Enabled, "NEW_RELIC_SECURITY_AGENT_ENABLED") assignBool(&cfg.Security.Detection.Rxss.Enabled, "NEW_RELIC_SECURITY_DETECTION_RXSS_ENABLED") + assignInt(&cfg.Security.Request.BodyLimit, "NEW_RELIC_SECURITY_REQUEST_BODY_LIMIT") } } @@ -160,3 +174,10 @@ func ConfigSecurityEnable(isEnabled bool) ConfigOption { cfg.Security.Enabled = isEnabled } } + +// ConfigSecurityRequestBodyLimit set limit on read request body in kb. By default, this is "300" +func ConfigSecurityRequestBodyLimit(bodyLimit int) ConfigOption { + return func(cfg *SecurityConfig) { + cfg.Security.Request.BodyLimit = bodyLimit + } +} From 476fefd5db94a4a6527cc6a763fab508653d630f Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Mon, 23 Oct 2023 08:12:13 +0000 Subject: [PATCH 04/13] update request body buffer --- v3/newrelic/secure_agent.go | 38 +++++++++++++++++++++++++++++++------ v3/newrelic/transaction.go | 10 +++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index 5011a3e7d..7c74acba9 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -4,7 +4,6 @@ import ( "net/http" ) -// // secureAgent is a global interface point for the nrsecureagent's hooks into the go agent. // The default value for this is a noOpSecurityAgent value, which has null definitions for // the methods. The Go compiler is expected to optimize away all the securityAgent method @@ -12,10 +11,8 @@ import ( // // If the nrsecureagent integration was initialized, it will register a real securityAgent // value in the securityAgent varialble instead, thus "activating" the hooks. -// var secureAgent securityAgent = noOpSecurityAgent{} -// // GetSecurityAgentInterface returns the securityAgent value // which provides the working interface to the installed // security agent (or to a no-op interface if none were @@ -26,7 +23,6 @@ var secureAgent securityAgent = noOpSecurityAgent{} // This avoids exposing the variable itself so it's not // writable externally and also sets up for the future if this // ends up not being a global variable later. -// func GetSecurityAgentInterface() securityAgent { return secureAgent } @@ -38,6 +34,7 @@ type securityAgent interface { IsSecurityActive() bool DistributedTraceHeaders(hdrs *http.Request, secureAgentevent any) SendExitEvent(any, error) + RequestBodyReadLimit() int } func (app *Application) RegisterSecurityAgent(s securityAgent) { @@ -88,13 +85,42 @@ func (t noOpSecurityAgent) DistributedTraceHeaders(hdrs *http.Request, secureAge func (t noOpSecurityAgent) SendExitEvent(secureAgentevent any, err error) { } +func (t noOpSecurityAgent) RequestBodyReadLimit() int { + return 300 * 1000 +} -// // IsSecurityAgentPresent returns true if there's an actual security agent hooked in to the // Go APM agent, whether or not it's enabled or operating in any particular mode. It returns // false only if the hook-in interface for those functions is a No-Op will null functionality. -// func IsSecurityAgentPresent() bool { _, isNoOp := secureAgent.(noOpSecurityAgent) return !isNoOp } + +type BodyBuffer struct { + buf []byte +} + +func (b *BodyBuffer) Write(p []byte) (int, error) { + if l := len(b.buf); len(p) <= cap(b.buf)-l { + b.buf = append(b.buf, p...) + return len(p), nil + } else { + return 0, nil + } +} + +func (b *BodyBuffer) Len() int { + if b == nil { + return 0 + } + return len(b.buf) + +} +func (b *BodyBuffer) String() string { + if b == nil { + return "" + } + return string(b.buf) + +} diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index 1b7b8eb92..dfd3ce8d6 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -4,7 +4,6 @@ package newrelic import ( - "bytes" "encoding/json" "fmt" "io" @@ -246,9 +245,9 @@ func serverName(r *http.Request) string { return "" } -func reqBody(req *http.Request) *bytes.Buffer { +func reqBody(req *http.Request) io.Writer { if IsSecurityAgentPresent() { - buf := &bytes.Buffer{} + buf := &BodyBuffer{buf: make([]byte, 0, secureAgent.RequestBodyReadLimit())} tee := io.TeeReader(req.Body, buf) req.Body = io.NopCloser(tee) return buf @@ -601,7 +600,7 @@ type WebRequest struct { // The following fields are needed for the secure agent's vulnerability // detection features. - Body *bytes.Buffer + Body io.Writer ServerName string Type string RemoteAddress string @@ -627,7 +626,8 @@ func (webrequest WebRequest) GetHost() string { return webrequest.Host } -func (webrequest WebRequest) GetBody() *bytes.Buffer { +func (webrequest WebRequest) GetBody() interface{} { + fmt.Println("webrequest.Body", webrequest.Body) return webrequest.Body } From 32826a6783611051f90f556abdb9947b92606a04 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Mon, 23 Oct 2023 12:30:28 +0000 Subject: [PATCH 05/13] minor fix for dataTruncated --- v3/newrelic/secure_agent.go | 10 ++++++---- v3/newrelic/transaction.go | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index 7c74acba9..d6a1861ed 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -98,7 +98,8 @@ func IsSecurityAgentPresent() bool { } type BodyBuffer struct { - buf []byte + buf []byte + isDataTruncated bool } func (b *BodyBuffer) Write(p []byte) (int, error) { @@ -106,6 +107,7 @@ func (b *BodyBuffer) Write(p []byte) (int, error) { b.buf = append(b.buf, p...) return len(p), nil } else { + b.isDataTruncated = true return 0, nil } } @@ -117,10 +119,10 @@ func (b *BodyBuffer) Len() int { return len(b.buf) } -func (b *BodyBuffer) String() string { +func (b *BodyBuffer) String() (string, bool) { if b == nil { - return "" + return "", false } - return string(b.buf) + return string(b.buf), b.isDataTruncated } diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index dfd3ce8d6..8e773041e 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -627,7 +627,6 @@ func (webrequest WebRequest) GetHost() string { } func (webrequest WebRequest) GetBody() interface{} { - fmt.Println("webrequest.Body", webrequest.Body) return webrequest.Body } From 3e087ce6cd1c5ee9ebf1bd80241442a4a111a48e Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Mon, 23 Oct 2023 12:54:24 +0000 Subject: [PATCH 06/13] Update readme file --- v3/integrations/nrsecurityagent/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/v3/integrations/nrsecurityagent/README.md b/v3/integrations/nrsecurityagent/README.md index ba77280c3..7ad9fb5b0 100644 --- a/v3/integrations/nrsecurityagent/README.md +++ b/v3/integrations/nrsecurityagent/README.md @@ -54,6 +54,8 @@ validator_service_url: wss://csec.nr-data.net detection: rxss: enabled: true +request: + body_limit:1 ``` * Based on additional packages imported by the user application, add suitable instrumentation package imports. From 31ac930e03e57e877c74b2309bea070bc03eeea6 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Mon, 23 Oct 2023 12:55:13 +0000 Subject: [PATCH 07/13] Update csec-go-agent version --- v3/integrations/nrsecurityagent/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 4fc26a9ff..883368bb8 100644 --- a/v3/integrations/nrsecurityagent/go.mod +++ b/v3/integrations/nrsecurityagent/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrsecurityagent go 1.19 require ( - github.com/newrelic/csec-go-agent v0.4.0 + github.com/newrelic/csec-go-agent v0.5.0 github.com/newrelic/go-agent/v3 v3.26.0 github.com/newrelic/go-agent/v3/integrations/nrsqlite3 v1.2.0 gopkg.in/yaml.v2 v2.4.0 From 5eea3c6552acf48f9a876b23aac394b80bbb0b83 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Fri, 27 Oct 2023 12:55:42 +0000 Subject: [PATCH 08/13] Added new wrapper for go-micro stream server --- v3/integrations/nrgrpc/go.mod | 3 +- v3/integrations/nrmicro/nrmicro.go | 72 +++++++++++++++++++++++++----- v3/newrelic/transaction.go | 2 +- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index 738fa628f..f072828ce 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -7,7 +7,7 @@ require ( // because all dependencies were removed in this version. github.com/golang/protobuf v1.5.3 github.com/newrelic/go-agent/v3 v3.26.0 - github.com/newrelic/go-agent/v3/integrations/nrsecurityagent v1.1.0 + github.com/newrelic/go-agent/v3/integrations/nrsecurityagent v1.2.0 // v1.15.0 is the earliest version of grpc using modules. google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.28.1 @@ -15,3 +15,4 @@ require ( replace github.com/newrelic/go-agent/v3 => ../.. +replace github.com/newrelic/go-agent/v3/integrations/nrsecurityagent => ../integrations/nrsecurityagent diff --git a/v3/integrations/nrmicro/nrmicro.go b/v3/integrations/nrmicro/nrmicro.go index 7198a81fb..804e531ff 100644 --- a/v3/integrations/nrmicro/nrmicro.go +++ b/v3/integrations/nrmicro/nrmicro.go @@ -5,6 +5,7 @@ package nrmicro import ( "context" + "io" "net/http" "net/url" "strings" @@ -15,9 +16,11 @@ import ( "github.com/micro/go-micro/registry" "github.com/micro/go-micro/server" + protoV1 "github.com/golang/protobuf/proto" "github.com/newrelic/go-agent/v3/internal" "github.com/newrelic/go-agent/v3/internal/integrationsupport" "github.com/newrelic/go-agent/v3/newrelic" + protoV2 "google.golang.org/protobuf/proto" ) type nrWrapper struct { @@ -162,7 +165,19 @@ func HandlerWrapper(app *newrelic.Application) server.HandlerWrapper { return func(ctx context.Context, req server.Request, rsp interface{}) error { txn := startWebTransaction(ctx, app, req) defer txn.End() - err := fn(newrelic.NewContext(ctx, txn), req, rsp) + if req.Body() != nil && newrelic.IsSecurityAgentPresent() { + messageType, version := getMessageType(req.Body()) + newrelic.GetSecurityAgentInterface().SendEvent("GRPC", req.Body(), messageType, version) + } + + nrrsp := rsp + if req.Stream() && newrelic.IsSecurityAgentPresent() { + if stream, ok := rsp.(server.Stream); ok { + nrrsp = wrappedServerStream{stream} + } + } + + err := fn(newrelic.NewContext(ctx, txn), req, nrrsp) var code int if err != nil { if t, ok := err.(*errors.Error); ok { @@ -227,9 +242,6 @@ func SubscriberWrapper(app *newrelic.Application) server.SubscriberWrapper { func startWebTransaction(ctx context.Context, app *newrelic.Application, req server.Request) *newrelic.Transaction { var hdrs http.Header - var unencodedBody []byte - var err error - if md, ok := metadata.FromContext(ctx); ok { hdrs = make(http.Header, len(md)) for k, v := range md { @@ -242,20 +254,58 @@ func startWebTransaction(ctx context.Context, app *newrelic.Application, req ser Host: req.Service(), Path: req.Endpoint(), } - - if unencodedBody, err = req.Read(); err != nil { - unencodedBody = nil - } - webReq := newrelic.WebRequest{ Header: hdrs, URL: u, Method: req.Method(), Transport: newrelic.TransportHTTP, - Body: unencodedBody, - Type: "HTTP", + Type: "micro", } txn.SetWebRequest(webReq) return txn } + +type wrappedServerStream struct { + stream server.Stream +} + +func (s wrappedServerStream) Context() context.Context { + return s.stream.Context() +} +func (s wrappedServerStream) Request() server.Request { + return s.stream.Request() +} +func (s wrappedServerStream) Send(msg any) error { + return s.stream.Send(msg) +} +func (s wrappedServerStream) Recv(msg any) error { + err := s.stream.Recv(msg) + if err != io.EOF { + messageType, version := getMessageType(msg) + newrelic.GetSecurityAgentInterface().SendEvent("GRPC", msg, messageType, version) + } + return err +} +func (s wrappedServerStream) Error() error { + return s.stream.Error() +} +func (s wrappedServerStream) Close() error { + return s.stream.Close() +} + +func getMessageType(req any) (string, string) { + messageType := "" + version := "v2" + messagev2, ok := req.(protoV2.Message) + if ok { + messageType = string(messagev2.ProtoReflect().Descriptor().FullName()) + } else { + messagev1, ok := req.(protoV1.Message) + if ok { + messageType = string(protoV1.MessageReflect(messagev1).Descriptor().FullName()) + version = "v1" + } + } + return messageType, version +} diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index 8e773041e..01aeb2925 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -626,7 +626,7 @@ func (webrequest WebRequest) GetHost() string { return webrequest.Host } -func (webrequest WebRequest) GetBody() interface{} { +func (webrequest WebRequest) GetBody() any { return webrequest.Body } From e5a77bebfa21610842380f37e6f122e3f720d173 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Fri, 27 Oct 2023 13:04:07 +0000 Subject: [PATCH 09/13] minor fix for GHA --- v3/integrations/nrgrpc/go.mod | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index f072828ce..7137e7856 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -7,12 +7,11 @@ require ( // because all dependencies were removed in this version. github.com/golang/protobuf v1.5.3 github.com/newrelic/go-agent/v3 v3.26.0 - github.com/newrelic/go-agent/v3/integrations/nrsecurityagent v1.2.0 + github.com/newrelic/go-agent/v3/integrations/nrsecurityagent v1.1.0 // v1.15.0 is the earliest version of grpc using modules. google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.28.1 ) - replace github.com/newrelic/go-agent/v3 => ../.. -replace github.com/newrelic/go-agent/v3/integrations/nrsecurityagent => ../integrations/nrsecurityagent +replace github.com/newrelic/go-agent/v3/integrations/nrsecurityagent => ../../integrations/nrsecurityagent From 536d99d452e1f2cd4c50e20e6655b866e849289d Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Tue, 31 Oct 2023 17:18:28 +0000 Subject: [PATCH 10/13] Fix for cpu overhead --- v3/newrelic/secure_agent.go | 2 +- v3/newrelic/transaction.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index d6a1861ed..159a25e75 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -103,7 +103,7 @@ type BodyBuffer struct { } func (b *BodyBuffer) Write(p []byte) (int, error) { - if l := len(b.buf); len(p) <= cap(b.buf)-l { + if l := len(b.buf); len(p) <= secureAgent.RequestBodyReadLimit()-l { b.buf = append(b.buf, p...) return len(p), nil } else { diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index 01aeb2925..b10f216ed 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -247,7 +247,7 @@ func serverName(r *http.Request) string { func reqBody(req *http.Request) io.Writer { if IsSecurityAgentPresent() { - buf := &BodyBuffer{buf: make([]byte, 0, secureAgent.RequestBodyReadLimit())} + buf := &BodyBuffer{buf: make([]byte, 0, 100)} tee := io.TeeReader(req.Body, buf) req.Body = io.NopCloser(tee) return buf From e5f4211449bd5ae345a1d2eb1717ed38a113d487 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Thu, 16 Nov 2023 06:43:41 +0000 Subject: [PATCH 11/13] backward compatibility --- v3/newrelic/secure_agent.go | 14 ++++++++++++++ v3/newrelic/transaction.go | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index 159a25e75..ac4c24fc4 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -119,6 +119,20 @@ func (b *BodyBuffer) Len() int { return len(b.buf) } + +func (b *BodyBuffer) read() []byte { + if b == nil { + return make([]byte, 0) + } + return b.buf +} + +func (b *BodyBuffer) isBodyTruncated() bool { + if b == nil { + return false + } + return b.isDataTruncated +} func (b *BodyBuffer) String() (string, bool) { if b == nil { return "", false diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index b10f216ed..af360f29a 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -245,7 +245,7 @@ func serverName(r *http.Request) string { return "" } -func reqBody(req *http.Request) io.Writer { +func reqBody(req *http.Request) *BodyBuffer { if IsSecurityAgentPresent() { buf := &BodyBuffer{buf: make([]byte, 0, 100)} tee := io.TeeReader(req.Body, buf) @@ -600,7 +600,7 @@ type WebRequest struct { // The following fields are needed for the secure agent's vulnerability // detection features. - Body io.Writer + Body *BodyBuffer ServerName string Type string RemoteAddress string @@ -626,8 +626,18 @@ func (webrequest WebRequest) GetHost() string { return webrequest.Host } -func (webrequest WebRequest) GetBody() any { - return webrequest.Body +func (webrequest WebRequest) GetBody() []byte { + if webrequest.Body == nil { + return make([]byte, 0) + } + return webrequest.Body.read() +} + +func (webrequest WebRequest) IsDataTruncated() bool { + if webrequest.Body == nil { + return false + } + return webrequest.Body.isBodyTruncated() } func (webrequest WebRequest) GetServerName() string { From 1a30a28c9755c479924f608a2d49cd91d166efad Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Thu, 16 Nov 2023 18:06:47 +0000 Subject: [PATCH 12/13] update agent version --- v3/integrations/nrsecurityagent/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 883368bb8..c0300761f 100644 --- a/v3/integrations/nrsecurityagent/go.mod +++ b/v3/integrations/nrsecurityagent/go.mod @@ -3,7 +3,7 @@ module github.com/newrelic/go-agent/v3/integrations/nrsecurityagent go 1.19 require ( - github.com/newrelic/csec-go-agent v0.5.0 + github.com/newrelic/csec-go-agent v0.5.1 github.com/newrelic/go-agent/v3 v3.26.0 github.com/newrelic/go-agent/v3/integrations/nrsqlite3 v1.2.0 gopkg.in/yaml.v2 v2.4.0 From 9cfa0524c12dca6c5f5be9fec65ddbbd762a0f12 Mon Sep 17 00:00:00 2001 From: aayush-ap Date: Thu, 16 Nov 2023 18:11:10 +0000 Subject: [PATCH 13/13] minor fix --- v3/newrelic/secure_agent.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/v3/newrelic/secure_agent.go b/v3/newrelic/secure_agent.go index ac4c24fc4..c7f546a80 100644 --- a/v3/newrelic/secure_agent.go +++ b/v3/newrelic/secure_agent.go @@ -106,6 +106,10 @@ func (b *BodyBuffer) Write(p []byte) (int, error) { if l := len(b.buf); len(p) <= secureAgent.RequestBodyReadLimit()-l { b.buf = append(b.buf, p...) return len(p), nil + } else if l := len(b.buf); secureAgent.RequestBodyReadLimit()-l > 1 { + end := secureAgent.RequestBodyReadLimit() - l + b.buf = append(b.buf, p[:end-1]...) + return end, nil } else { b.isDataTruncated = true return 0, nil