Skip to content

Commit c8a451d

Browse files
committed
feat(nrawssdk): Accept NEW_RELIC_CLOUD_AWS_ACCOUNT_ID as env var
1 parent 2a88e41 commit c8a451d

File tree

9 files changed

+413
-57
lines changed

9 files changed

+413
-57
lines changed

v3/go.mod

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,22 @@ require (
77
google.golang.org/protobuf v1.34.2
88
)
99

10+
require github.com/aws/smithy-go v1.24.0 // indirect
11+
12+
require (
13+
github.com/aws/aws-sdk-go-v2 v1.41.1
14+
golang.org/x/net v0.25.0 // indirect
15+
golang.org/x/sys v0.20.0 // indirect
16+
golang.org/x/text v0.15.0 // indirect
17+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
18+
)
1019

1120
retract v3.22.0 // release process error corrected in v3.22.1
1221

1322
retract v3.25.0 // release process error corrected in v3.25.1
1423

1524
retract v3.34.0 // this release erronously referred to and invalid protobuf dependency
16-
retract v3.40.0 // this release erronously had deadlocks in utilization.go and incorrectly added aws-sdk-go to the go.mod file
25+
26+
retract v3.40.0 // this release erronously had deadlocks in utilization.go and incorrectly added aws-sdk-go to the go.mod file
27+
28+
replace github.com/newrelic/go-agent/v3 => /Users/cconklin/team-go/go-agent/./v3

v3/integrations/nrawssdk-v2/example/main.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,19 @@ func main() {
3838
// Start recording a New Relic transaction
3939
txn := app.StartTransaction("My sample transaction")
4040

41-
ctx := context.Background()
41+
ctx := newrelic.NewContext(context.Background(), txn)
4242

4343
awsConfig, err := config.LoadDefaultConfig(ctx, func(awsConfig *config.LoadOptions) error {
4444
// Instrument all new AWS clients with New Relic
45-
4645
return nil
4746
})
48-
creds, err := awsConfig.Credentials.Retrieve(ctx)
49-
if err != nil {
50-
log.Println("Warning couldn't get flags")
47+
48+
// Ensure transaction is in context for NRAppendMiddlewares
49+
if txn != nil {
50+
ctx = newrelic.NewContext(ctx, txn)
5151
}
52-
nrawssdk.AppendMiddlewares(&awsConfig.APIOptions, nil, creds)
52+
53+
nrawssdk.NRAppendMiddlewares(&awsConfig.APIOptions, ctx, awsConfig)
5354
if err != nil {
5455
log.Fatal(err)
5556
}

v3/integrations/nrawssdk-v2/go.mod

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,39 @@ module github.com/newrelic/go-agent/v3/integrations/nrawssdk-v2
55
go 1.24
66

77
require (
8-
github.com/aws/aws-sdk-go-v2 v1.30.4
8+
github.com/aws/aws-sdk-go-v2 v1.41.1
99
github.com/aws/aws-sdk-go-v2/config v1.27.31
1010
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.6
1111
github.com/aws/aws-sdk-go-v2/service/lambda v1.58.1
1212
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0
1313
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.6
14-
github.com/aws/smithy-go v1.20.4
14+
github.com/aws/smithy-go v1.24.0
1515
github.com/newrelic/go-agent/v3 v3.42.0
1616
)
1717

18+
require (
19+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
20+
github.com/aws/aws-sdk-go-v2/credentials v1.17.30 // indirect
21+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect
22+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
23+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
24+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
25+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect
26+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
27+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect
28+
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect
29+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect
30+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect
31+
github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect
32+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect
33+
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect
34+
github.com/jmespath/go-jmespath v0.4.0 // indirect
35+
golang.org/x/net v0.25.0 // indirect
36+
golang.org/x/sys v0.20.0 // indirect
37+
golang.org/x/text v0.15.0 // indirect
38+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
39+
google.golang.org/grpc v1.65.0 // indirect
40+
google.golang.org/protobuf v1.34.2 // indirect
41+
)
1842

1943
replace github.com/newrelic/go-agent/v3 => ../..

v3/integrations/nrawssdk-v2/nrawssdk.go

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ package nrawssdk
2828

2929
import (
3030
"context"
31+
"encoding/base32"
3132
"fmt"
3233
"net/url"
3334
"strconv"
@@ -40,16 +41,22 @@ import (
4041
"github.com/aws/smithy-go/middleware"
4142
smithymiddle "github.com/aws/smithy-go/middleware"
4243
smithyhttp "github.com/aws/smithy-go/transport/http"
43-
"github.com/newrelic/go-agent/v3/internal/awssupport"
4444
"github.com/newrelic/go-agent/v3/newrelic"
4545
"github.com/newrelic/go-agent/v3/newrelic/integrationsupport"
4646
)
4747

48+
type credentialsResolver interface {
49+
AWSAccountIdFromAWSAccessKey(creds aws.Credentials) (string, error)
50+
}
51+
4852
type nrMiddleware struct {
49-
txn *newrelic.Transaction
50-
creds aws.Credentials
53+
txn *newrelic.Transaction
54+
accountID string
55+
resolver credentialsResolver
5156
}
5257

58+
type defaultResolver struct{}
59+
5360
type contextKey string
5461

5562
const (
@@ -77,14 +84,7 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error {
7784
serviceName := awsmiddle.GetServiceID(ctx)
7885
operation := awsmiddle.GetOperationName(ctx)
7986
region := awsmiddle.GetRegion(ctx)
80-
81-
creds := awsmiddle.GetSigningCredentials(ctx)
82-
accountID, err := awssupport.AWSAccountIdFromAWSAccessKey(creds)
83-
if err != nil {
84-
accountID = ""
85-
fmt.Println(err.Error())
86-
}
87-
87+
accountID := m.accountID
8888
var segment endable
8989

9090
if serviceName == "dynamodb" || serviceName == "DynamoDB" {
@@ -138,7 +138,9 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error {
138138
integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeAWSElastSearchDomainEndpoint, httpRequest.URL.String()) // this way I don't have to pull it out of context
139139
}
140140
// Set additional span attributes
141+
141142
integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeCloudAccountID, accountID) // setting account ID here, why do we only do this if it is an SQS service?
143+
142144
integrationsupport.AddAgentSpanAttribute(txn,
143145
newrelic.AttributeResponseCode, strconv.Itoa(response.StatusCode))
144146
integrationsupport.AddAgentSpanAttribute(txn,
@@ -161,7 +163,6 @@ func (m nrMiddleware) serializeMiddleware(stack *middleware.Stack) error {
161163
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
162164
out middleware.InitializeOutput, metadata middleware.Metadata, err error) {
163165
serviceName := awsmiddle.GetServiceID(ctx)
164-
ctx = awsmiddle.SetSigningCredentials(ctx, m.creds)
165166
switch serviceName {
166167
case "dynamodb", "DynamoDB":
167168
ctx = context.WithValue(ctx, dynamodbInputKey, dynamoDBInputFromMiddlewareInput(in))
@@ -229,8 +230,30 @@ func (m nrMiddleware) serializeMiddleware(stack *middleware.Stack) error {
229230
// if err != nil {
230231
// log.Fatal(err)
231232
// }
232-
func AppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, txn *newrelic.Transaction, creds aws.Credentials) {
233-
m := nrMiddleware{txn: txn, creds: creds}
233+
func AppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, txn *newrelic.Transaction) {
234+
m := nrMiddleware{txn: txn}
235+
*apiOptions = append(*apiOptions, m.deserializeMiddleware)
236+
*apiOptions = append(*apiOptions, m.serializeMiddleware)
237+
}
238+
239+
func NRAppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, ctx context.Context, awsConfig aws.Config) {
240+
txn := newrelic.FromContext(ctx)
241+
242+
creds, err := awsConfig.Credentials.Retrieve(ctx)
243+
if err != nil {
244+
fmt.Println("error: Couldn't get AWS Credentials")
245+
}
246+
247+
cfg, ok := txn.Application().Config()
248+
m := nrMiddleware{txn: txn, resolver: &defaultResolver{}}
249+
250+
if ok {
251+
err := m.ResolveAWSCredentials(cfg, creds)
252+
if err != nil {
253+
fmt.Println("error: Couldn't resolve AWS credentials")
254+
}
255+
}
256+
234257
*apiOptions = append(*apiOptions, m.deserializeMiddleware)
235258
*apiOptions = append(*apiOptions, m.serializeMiddleware)
236259
}
@@ -291,3 +314,55 @@ func dynamoDBInputFromMiddlewareInput(in middleware.InitializeInput) dynamodbInp
291314
return dynamodbInput{}
292315
}
293316
}
317+
318+
func (m *nrMiddleware) ResolveAWSCredentials(cfg newrelic.Config, creds aws.Credentials) error {
319+
320+
if m.resolver == nil {
321+
m.resolver = &defaultResolver{}
322+
}
323+
324+
accountID, err := m.resolver.AWSAccountIdFromAWSAccessKey(creds)
325+
if err != nil {
326+
return err
327+
}
328+
329+
// Use resolved accountID if:
330+
// 1. No accountID is set in config (cfg.CloudAWS.AccountID is empty), OR
331+
// 2. Resolved accountID is different from config accountID
332+
if cfg.CloudAWS.AccountID == "" || (accountID != "" && accountID != cfg.CloudAWS.AccountID) {
333+
m.accountID = accountID
334+
return nil
335+
}
336+
337+
// Otherwise use the config accountID
338+
m.accountID = cfg.CloudAWS.AccountID
339+
return nil
340+
}
341+
342+
func (m *defaultResolver) AWSAccountIdFromAWSAccessKey(creds aws.Credentials) (string, error) {
343+
if creds.AccountID != "" {
344+
return creds.AccountID, nil
345+
}
346+
if creds.AccessKeyID == "" {
347+
return "", fmt.Errorf("no access key id found")
348+
}
349+
if len(creds.AccessKeyID) < 16 {
350+
return "", fmt.Errorf("improper access key id format")
351+
}
352+
trimmedAccessKey := creds.AccessKeyID[4:]
353+
decoded, err := base32.StdEncoding.DecodeString(trimmedAccessKey)
354+
if err != nil {
355+
return "", fmt.Errorf("error decoding access keys")
356+
}
357+
var bigEndian uint64
358+
for i := 0; i < 6; i++ {
359+
bigEndian = bigEndian << 8 // shift 8 bits left. Most significant byte read in first (decoded[i])
360+
bigEndian |= uint64(decoded[i]) // apply OR for current byte
361+
}
362+
363+
mask := uint64(0x7fffffffff80)
364+
365+
num := (bigEndian & mask) >> 7 // apply mask and get rid of last 7 bytes from mask
366+
367+
return fmt.Sprintf("%d", num), nil
368+
}

0 commit comments

Comments
 (0)