From 0e521dc894ec887e31125067f28bd3992cf33310 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 16 Nov 2023 14:22:37 -0500 Subject: [PATCH] Release 3.28.0 (#818) * Error Expected Bug The attribute error.expected should be a boolean, not a string. It is also good practice to use a constant value for the key. * Bump google.golang.org/grpc from 1.54.0 to 1.56.3 in /v3/integrations/nrgraphqlgo/example (#811) --------- * Bump google.golang.org/grpc in /v3/integrations/nrgraphqlgo/example Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.54.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.54.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: indirect ... Signed-off-by: dependabot[bot] --------- * Bump google.golang.org/grpc from 1.54.0 to 1.56.3 in /v3/integrations/nrgrpc (#810) --------- * Bump google.golang.org/grpc in /v3/integrations/nrgrpc Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.54.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.54.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] --------- * Bump google.golang.org/grpc from 1.54.0 to 1.56.3 in /v3 (#809) --------- * Bump google.golang.org/grpc from 1.54.0 to 1.56.3 in /v3 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.54.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.54.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] --------- * Bump golang.org/x/net from 0.8.0 to 0.17.0 in /v3/integrations/nrgraphqlgo/example (#804) --------- * Bump golang.org/x/net in /v3/integrations/nrgraphqlgo/example Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.8.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --------- * Fix for out of memory error with request body (#806) * 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 * fix out of memory issue for req body * Added new config parameter for read request body * update request body buffer * minor fix for dataTruncated * Update readme file * Update csec-go-agent version * Added new wrapper for go-micro stream server * minor fix for GHA * Fix for cpu overhead * backward compatibility * update agent version * minor fix --------- Co-authored-by: Mirac Kara <55501260+mirackara@users.noreply.github.com> 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 * move fasthttp out of core library, and into integration package (#808) * move fasthttp out of core library, and into integration package * move examples over * add security agent headers to fasthttp object * fix examples and external segment * add fasthttp tests * cleanup of go mods * fix segment collection * add security agent inbound write capture to wrapped handle func * Update go.mod * Update Changelog * update version.go --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: aayush-ap <59004877+aayush-ap@users.noreply.github.com> Co-authored-by: Mirac Kara <55501260+mirackara@users.noreply.github.com> Co-authored-by: Steve Willoughby <76975199+nr-swilloughby@users.noreply.github.com> Co-authored-by: Julien Erard Co-authored-by: Steve Willoughby Co-authored-by: mirackara --- .github/workflows/ci.yaml | 2 + CHANGELOG.md | 15 +++ v3/go.mod | 3 +- .../examples/client-fasthttp/go.mod | 11 +++ .../examples/client-fasthttp/main.go | 4 +- .../examples/server-fasthttp/go.mod | 11 +++ .../examples/server-fasthttp/main.go | 7 +- v3/integrations/nrfasthttp/go.mod | 6 +- v3/integrations/nrfasthttp/instrumentation.go | 74 ++++++++++++++ .../nrfasthttp/instrumentation_test.go | 56 +++++++++++ v3/integrations/nrfasthttp/segment.go | 80 +++++++++++++++ v3/integrations/nrfasthttp/segment_test.go | 65 ++++++++++++ v3/integrations/nrgraphqlgo/example/go.mod | 12 +-- v3/integrations/nrgrpc/go.mod | 6 +- v3/integrations/nrmicro/nrmicro.go | 72 +++++++++++--- v3/integrations/nrsecurityagent/README.md | 2 + v3/integrations/nrsecurityagent/go.mod | 2 +- .../nrsecurityagent/nrsecurityagent.go | 21 ++++ v3/newrelic/context.go | 14 --- v3/newrelic/error_events.go | 2 +- v3/newrelic/instrumentation.go | 98 +++++-------------- v3/newrelic/internal_17_test.go | 43 -------- v3/newrelic/internal_context_test.go | 25 ----- v3/newrelic/secure_agent.go | 58 +++++++++-- v3/newrelic/segments.go | 50 ++++------ v3/newrelic/transaction.go | 35 ++++--- v3/newrelic/version.go | 2 +- 27 files changed, 532 insertions(+), 244 deletions(-) create mode 100644 v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod rename v3/{ => integrations/nrfasthttp}/examples/client-fasthttp/main.go (92%) create mode 100644 v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod rename v3/{ => integrations/nrfasthttp}/examples/server-fasthttp/main.go (83%) create mode 100644 v3/integrations/nrfasthttp/instrumentation.go create mode 100644 v3/integrations/nrfasthttp/instrumentation_test.go create mode 100644 v3/integrations/nrfasthttp/segment.go create mode 100644 v3/integrations/nrfasthttp/segment_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f35e91398..6b5636a35 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,8 @@ jobs: # v3 integrations - go-version: 1.19.x dirs: v3/integrations/nramqp + - go-version: 1.19.x + dirs: v3/integrations/nrfasthttp - go-version: 1.19.x dirs: v3/integrations/nrsarama - go-version: 1.19.x diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c696edb3..366e2ac6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.28.0 +### Fixed +* Bumped gRPC from 1.54.0 -> 1.56.3 in the following packages /v3/integrations/nrgrpc, /v3/, /v3/integrations/nrgrpc +* Bumped golang.org/x/net from 0.8.0 -> 0.17.0 in package /v3/integrations/nrgraphqlgo +* Fixed issue where nrfasthttp would not properly register security agent headers +* Move fasthttp instrumentation into a new integration package, nrfasthttp +* Fixed issue where usage of io.ReadAll() was causing a memory leak + +### 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. + +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.27.0 ### Added * Added Support for getting Container ID's from cgroup v2 docker containers diff --git a/v3/go.mod b/v3/go.mod index 02c244f6b..e064d1ccf 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,8 +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 + google.golang.org/grpc v1.56.3 ) retract v3.22.0 // release process error corrected in v3.22.1 diff --git a/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod new file mode 100644 index 000000000..104505e67 --- /dev/null +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/go.mod @@ -0,0 +1,11 @@ +module client-example + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.28.0 + github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 + github.com/valyala/fasthttp v1.49.0 +) + +replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/examples/client-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go similarity index 92% rename from v3/examples/client-fasthttp/main.go rename to v3/integrations/nrfasthttp/examples/client-fasthttp/main.go index 7a26b605f..bb958b840 100644 --- a/v3/examples/client-fasthttp/main.go +++ b/v3/integrations/nrfasthttp/examples/client-fasthttp/main.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" newrelic "github.com/newrelic/go-agent/v3/newrelic" "github.com/valyala/fasthttp" ) @@ -20,8 +21,7 @@ func doRequest(txn *newrelic.Transaction) error { req.SetRequestURI("http://localhost:8080/hello") req.Header.SetMethod("GET") - ctx := &fasthttp.RequestCtx{} - seg := newrelic.StartExternalSegmentFastHTTP(txn, ctx) + seg := nrfasthttp.StartExternalSegment(txn, req) defer seg.End() err := fasthttp.Do(req, resp) diff --git a/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod new file mode 100644 index 000000000..591d85885 --- /dev/null +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/go.mod @@ -0,0 +1,11 @@ +module server-example + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.28.0 + github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 + github.com/valyala/fasthttp v1.49.0 +) + +replace github.com/newrelic/go-agent/v3/integrations/nrfasthttp v1.0.0 => ../../ diff --git a/v3/examples/server-fasthttp/main.go b/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go similarity index 83% rename from v3/examples/server-fasthttp/main.go rename to v3/integrations/nrfasthttp/examples/server-fasthttp/main.go index 8ed532670..bdb642f85 100644 --- a/v3/examples/server-fasthttp/main.go +++ b/v3/integrations/nrfasthttp/examples/server-fasthttp/main.go @@ -9,7 +9,8 @@ import ( "os" "time" - newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/newrelic/go-agent/v3/integrations/nrfasthttp" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/valyala/fasthttp" ) @@ -39,8 +40,8 @@ func main() { if err := app.WaitForConnection(5 * time.Second); nil != err { fmt.Println(err) } - _, helloRoute := newrelic.WrapHandleFuncFastHTTP(app, "/hello", index) - _, errorRoute := newrelic.WrapHandleFuncFastHTTP(app, "/error", noticeError) + _, helloRoute := nrfasthttp.WrapHandleFunc(app, "/hello", index) + _, errorRoute := nrfasthttp.WrapHandleFunc(app, "/error", noticeError) handler := func(ctx *fasthttp.RequestCtx) { path := string(ctx.Path()) method := string(ctx.Method()) diff --git a/v3/integrations/nrfasthttp/go.mod b/v3/integrations/nrfasthttp/go.mod index 7f2b81134..5c97651a0 100644 --- a/v3/integrations/nrfasthttp/go.mod +++ b/v3/integrations/nrfasthttp/go.mod @@ -3,8 +3,6 @@ module github.com/newrelic/go-agent/v3/integrations/nrfasthttp go 1.19 require ( - github.com/newrelic/go-agent/v3 v3.26.0 - github.com/stretchr/testify v1.8.4 - github.com/valyala/fasthttp v1.48.0 + github.com/newrelic/go-agent/v3 v3.28.0 + github.com/valyala/fasthttp v1.49.0 ) -replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrfasthttp/instrumentation.go b/v3/integrations/nrfasthttp/instrumentation.go new file mode 100644 index 000000000..9ce64769c --- /dev/null +++ b/v3/integrations/nrfasthttp/instrumentation.go @@ -0,0 +1,74 @@ +package nrfasthttp + +import ( + "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" + "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) +} + +func (rw fasthttpWrapperResponse) Body() string { + body := rw.ctx.Response.Body() + return string(body) +} + +// WrapHandleFunc wrapps a fasthttp handler function for automatic instrumentation +func WrapHandleFunc(app *newrelic.Application, pattern string, handler func(*fasthttp.RequestCtx), options ...newrelic.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 := WrapHandle(app, pattern, fasthttp.RequestHandler(handler), options...) + return p, func(ctx *fasthttp.RequestCtx) { h(ctx) } +} + +// WrapHandle wraps a fasthttp request handler for automatic instrumentation +func WrapHandle(app *newrelic.Application, pattern string, handler fasthttp.RequestHandler, options ...newrelic.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). + return pattern, func(ctx *fasthttp.RequestCtx) { + cache := newrelic.NewCachedCodeLocation() + txnOptionList := newrelic.AddCodeLevelMetricsTraceOptions(app, options, cache, handler) + 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) + + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().SendEvent("INBOUND_WRITE", resp.Body(), resp.Header()) + } + handler(ctx) + } +} diff --git a/v3/integrations/nrfasthttp/instrumentation_test.go b/v3/integrations/nrfasthttp/instrumentation_test.go new file mode 100644 index 000000000..844c3682b --- /dev/null +++ b/v3/integrations/nrfasthttp/instrumentation_test.go @@ -0,0 +1,56 @@ +package nrfasthttp + +import ( + "testing" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +type myError struct{} + +func (e myError) Error() string { return "my msg" } + +func myErrorHandlerFastHTTP(ctx *fasthttp.RequestCtx) { + ctx.WriteString("noticing an error") + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + txn.NoticeError(myError{}) +} + +func TestWrapHandleFastHTTPFunc(t *testing.T) { + singleCount := []float64{1, 0, 0, 0, 0, 0, 0} + app := createTestApp(true) + + _, wrappedHandler := WrapHandleFunc(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: "nrfasthttp.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}, + }) +} diff --git a/v3/integrations/nrfasthttp/segment.go b/v3/integrations/nrfasthttp/segment.go new file mode 100644 index 000000000..aa480f5f0 --- /dev/null +++ b/v3/integrations/nrfasthttp/segment.go @@ -0,0 +1,80 @@ +package nrfasthttp + +import ( + "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" +) + +// StartExternalSegment automatically creates and fills out a New Relic external segment for a given +// fasthttp request object. This function will accept either a fasthttp.Request or a fasthttp.RequestContext +// object as the request argument. +func StartExternalSegment(txn *newrelic.Transaction, request any) *newrelic.ExternalSegment { + var secureAgentEvent any + var ctx *fasthttp.RequestCtx + + switch reqObject := request.(type) { + + case *fasthttp.RequestCtx: + ctx = reqObject + + case *fasthttp.Request: + ctx = &fasthttp.RequestCtx{} + reqObject.CopyTo(&ctx.Request) + + default: + return nil + } + + if nil == txn { + txn = transactionFromRequestContext(ctx) + } + req := &http.Request{} + + fasthttpadaptor.ConvertRequest(ctx, req, true) + s := &newrelic.ExternalSegment{ + StartTime: txn.StartSegmentNow(), + Request: req, + } + + if newrelic.IsSecurityAgentPresent() { + secureAgentEvent = newrelic.GetSecurityAgentInterface().SendEvent("OUTBOUND", request) + s.SetSecureAgentEvent(secureAgentEvent) + } + + if request != nil && req.Header != nil { + for key, values := range s.GetOutboundHeaders() { + for _, value := range values { + req.Header.Set(key, value) + } + } + + if newrelic.IsSecurityAgentPresent() { + newrelic.GetSecurityAgentInterface().DistributedTraceHeaders(req, secureAgentEvent) + } + + for k, values := range req.Header { + for _, value := range values { + ctx.Request.Header.Set(k, value) + } + } + } + + return s +} + +// FromContext extracts a transaction pointer from a fasthttp.RequestContext object +func FromContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { + return transactionFromRequestContext(ctx) +} + +func transactionFromRequestContext(ctx *fasthttp.RequestCtx) *newrelic.Transaction { + if nil != ctx { + txn := ctx.UserValue("transaction").(*newrelic.Transaction) + return txn + } + + return nil +} diff --git a/v3/integrations/nrfasthttp/segment_test.go b/v3/integrations/nrfasthttp/segment_test.go new file mode 100644 index 000000000..550e39a86 --- /dev/null +++ b/v3/integrations/nrfasthttp/segment_test.go @@ -0,0 +1,65 @@ +package nrfasthttp + +import ( + "testing" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/integrationsupport" + "github.com/newrelic/go-agent/v3/newrelic" + "github.com/valyala/fasthttp" +) + +func createTestApp(dt bool) integrationsupport.ExpectApp { + return integrationsupport.NewTestApp(replyFn, integrationsupport.ConfigFullTraces, newrelic.ConfigDistributedTracerEnabled(dt)) +} + +var replyFn = func(reply *internal.ConnectReply) { + reply.SetSampleEverything() +} + +func TestExternalSegment(t *testing.T) { + app := createTestApp(false) + txn := app.StartTransaction("myTxn") + + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(resp) + + ctx := &fasthttp.RequestCtx{Request: fasthttp.Request{}} + ctx.Request.SetRequestURI("http://localhost:8080/hello") + ctx.Request.Header.SetMethod("GET") + + seg := StartExternalSegment(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 TestExternalSegmentRequest(t *testing.T) { + app := createTestApp(false) + 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") + + seg := StartExternalSegment(txn, req) + 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}, + }) +} diff --git a/v3/integrations/nrgraphqlgo/example/go.mod b/v3/integrations/nrgraphqlgo/example/go.mod index d5a044811..ac6bc56ad 100644 --- a/v3/integrations/nrgraphqlgo/example/go.mod +++ b/v3/integrations/nrgraphqlgo/example/go.mod @@ -16,12 +16,12 @@ require ( github.com/klauspost/compress v1.16.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.49.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect ) replace github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo => ../ diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index 738fa628f..642db88de 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -9,9 +9,9 @@ require ( github.com/newrelic/go-agent/v3 v3.26.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 + google.golang.org/grpc v1.56.3 + google.golang.org/protobuf v1.30.0 ) - 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/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. diff --git a/v3/integrations/nrsecurityagent/go.mod b/v3/integrations/nrsecurityagent/go.mod index 4fc26a9ff..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.4.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 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 + } +} diff --git a/v3/newrelic/context.go b/v3/newrelic/context.go index 731dcb73f..5ce186f3d 100644 --- a/v3/newrelic/context.go +++ b/v3/newrelic/context.go @@ -8,7 +8,6 @@ import ( "net/http" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) // NewContext returns a new context.Context that carries the provided @@ -53,16 +52,3 @@ 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/error_events.go b/v3/newrelic/error_events.go index 07aca4f35..6087865d8 100644 --- a/v3/newrelic/error_events.go +++ b/v3/newrelic/error_events.go @@ -32,7 +32,7 @@ func (e *errorEvent) WriteJSON(buf *bytes.Buffer) { w.stringField("spanId", e.SpanID) } if e.Expect { - w.stringField("error.expected", "true") + w.boolField(expectErrorAttr, true) } sharedTransactionIntrinsics(&e.txnEvent, &w) diff --git a/v3/newrelic/instrumentation.go b/v3/newrelic/instrumentation.go index e4351a955..d0ffd7379 100644 --- a/v3/newrelic/instrumentation.go +++ b/v3/newrelic/instrumentation.go @@ -5,31 +5,8 @@ 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 @@ -99,54 +76,37 @@ 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() +// AddCodeLevelMetricsTraceOptions adds trace options to an existing slice of TraceOption objects depending on how code level metrics is configured +// in your application. +// Please call cache:=newrelic.NewCachedCodeLocation() before calling this function, and pass the cache to us in order to allow you to optimize the +// performance and accuracy of this function. +func AddCodeLevelMetricsTraceOptions(app *Application, options []TraceOption, cache *CachedCodeLocation, cachedLocations ...interface{}) []TraceOption { + var tOptions *traceOptSet + var txnOptionList []TraceOption - return pattern, func(ctx *fasthttp.RequestCtx) { - var tOptions *traceOptSet - var txnOptionList []TraceOption + if cache == nil { + return options + } - 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 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(cachedLocations); 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) } + 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)) + } + + return txnOptionList } // WrapHandleFunc instruments handler functions using Transactions. To @@ -184,14 +144,6 @@ 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 diff --git a/v3/newrelic/internal_17_test.go b/v3/newrelic/internal_17_test.go index 5ba7b6c7e..82d1dc8f1 100644 --- a/v3/newrelic/internal_17_test.go +++ b/v3/newrelic/internal_17_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) func myErrorHandler(w http.ResponseWriter, req *http.Request) { @@ -19,48 +18,6 @@ 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 51372d382..1e15e61cd 100644 --- a/v3/newrelic/internal_context_test.go +++ b/v3/newrelic/internal_context_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/newrelic/go-agent/v3/internal" - "github.com/valyala/fasthttp" ) func TestWrapHandlerContext(t *testing.T) { @@ -37,30 +36,6 @@ 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/secure_agent.go b/v3/newrelic/secure_agent.go index 5011a3e7d..c7f546a80 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,62 @@ 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 + isDataTruncated bool +} + +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 + } +} + +func (b *BodyBuffer) Len() int { + if b == nil { + return 0 + } + 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 + } + return string(b.buf), b.isDataTruncated + +} diff --git a/v3/newrelic/segments.go b/v3/newrelic/segments.go index 65344033a..328db4283 100644 --- a/v3/newrelic/segments.go +++ b/v3/newrelic/segments.go @@ -5,9 +5,6 @@ package newrelic import ( "net/http" - - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpadaptor" ) // SegmentStartTime is created by Transaction.StartSegmentNow and marks the @@ -289,6 +286,23 @@ func (s *ExternalSegment) outboundHeaders() http.Header { return outboundHeaders(s) } +func (s *ExternalSegment) GetOutboundHeaders() http.Header { + return s.outboundHeaders() +} + +// SetSecureAgentEvent allows integration packages to set the secureAgentEvent +// for this external segment. That field is otherwise unexported and not available +// for other manipulation. +func (s *ExternalSegment) SetSecureAgentEvent(event any) { + s.secureAgentEvent = event +} + +// GetSecureAgentEvent retrieves the secureAgentEvent previously stored by +// a SetSecureAgentEvent method. +func (s *ExternalSegment) GetSecureAgentEvent() any { + return s.secureAgentEvent +} + // StartSegmentNow starts timing a segment. // // Deprecated: StartSegmentNow is deprecated and will be removed in a future @@ -340,36 +354,6 @@ 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/transaction.go b/v3/newrelic/transaction.go index d1d519c63..af360f29a 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -4,7 +4,6 @@ package newrelic import ( - "bytes" "encoding/json" "fmt" "io" @@ -246,20 +245,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) *BodyBuffer { + if IsSecurityAgentPresent() { + buf := &BodyBuffer{buf: make([]byte, 0, 100)} + 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 +600,7 @@ type WebRequest struct { // The following fields are needed for the secure agent's vulnerability // detection features. - Body []byte + Body *BodyBuffer ServerName string Type string RemoteAddress string @@ -634,7 +627,17 @@ func (webrequest WebRequest) GetHost() string { } func (webrequest WebRequest) GetBody() []byte { - return webrequest.Body + 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 { diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index 0b8c0fc6f..085452c7a 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.27.0" + Version = "3.28.0" ) var (