Skip to content

Commit 0ce4397

Browse files
committed
feat(nrawssdk): Convert AccessKeyId to AccountID
1 parent 14bcbb4 commit 0ce4397

File tree

3 files changed

+175
-2
lines changed

3 files changed

+175
-2
lines changed

v3/integrations/nrawssdk-v2/nrawssdk.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ package nrawssdk
2828

2929
import (
3030
"context"
31+
"fmt"
3132
"net/url"
3233
"strconv"
3334
"strings"
@@ -39,6 +40,7 @@ import (
3940
"github.com/aws/smithy-go/middleware"
4041
smithymiddle "github.com/aws/smithy-go/middleware"
4142
smithyhttp "github.com/aws/smithy-go/transport/http"
43+
"github.com/newrelic/go-agent/v3/internal/awssupport"
4244
"github.com/newrelic/go-agent/v3/newrelic"
4345
"github.com/newrelic/go-agent/v3/newrelic/integrationsupport"
4446
)
@@ -69,13 +71,19 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error {
6971
}
7072

7173
smithyRequest := in.Request.(*smithyhttp.Request)
72-
7374
// The actual http.Request is inside the smithyhttp.Request
7475
httpRequest := smithyRequest.Request
7576
serviceName := awsmiddle.GetServiceID(ctx)
7677
operation := awsmiddle.GetOperationName(ctx)
7778
region := awsmiddle.GetRegion(ctx)
7879

80+
creds := awsmiddle.GetSigningCredentials(ctx)
81+
accountID, err := awssupport.AWSAccountIdFromAWSAccessKey(creds)
82+
if err != nil {
83+
accountID = ""
84+
return out, metadata, fmt.Errorf("error parsing accountID from access key")
85+
}
86+
7987
var segment endable
8088

8189
if serviceName == "dynamodb" || serviceName == "DynamoDB" {
@@ -129,6 +137,7 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error {
129137
integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeAWSElastSearchDomainEndpoint, httpRequest.URL.String()) // this way I don't have to pull it out of context
130138
}
131139
// Set additional span attributes
140+
integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeCloudAccountID, accountID) // setting account ID here, why do we only do this if it is an SQS service?
132141
integrationsupport.AddAgentSpanAttribute(txn,
133142
newrelic.AttributeResponseCode, strconv.Itoa(response.StatusCode))
134143
integrationsupport.AddAgentSpanAttribute(txn,

v3/internal/awssupport/awssupport.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ package awssupport
88

99
import (
1010
"context"
11-
"github.com/newrelic/go-agent/v3/newrelic/integrationsupport"
11+
"encoding/base32"
12+
"fmt"
1213
"net/http"
1314
"reflect"
1415

16+
"github.com/aws/aws-sdk-go-v2/aws"
17+
"github.com/newrelic/go-agent/v3/newrelic/integrationsupport"
18+
1519
newrelic "github.com/newrelic/go-agent/v3/newrelic"
1620
)
1721

@@ -114,3 +118,31 @@ func EndSegment(ctx context.Context, resp *http.Response) {
114118
segment.End()
115119
}
116120
}
121+
122+
func AWSAccountIdFromAWSAccessKey(creds aws.Credentials) (string, error) {
123+
if creds.AccountID != "" {
124+
return creds.AccountID, nil
125+
}
126+
if creds.AccessKeyID == "" {
127+
return "", fmt.Errorf("no access key id found")
128+
}
129+
if len(creds.AccessKeyID) < 16 {
130+
return "", fmt.Errorf("improper access key id format")
131+
}
132+
trimmedAccessKey := creds.AccessKeyID[4:]
133+
decoded, err := base32.StdEncoding.DecodeString(trimmedAccessKey)
134+
if err != nil {
135+
return "", fmt.Errorf("error decoding access keys")
136+
}
137+
var bigEndian uint64
138+
for i := 0; i < 6; i++ {
139+
bigEndian = bigEndian << 8 // shift 8 bits left. Most significant byte read in first (decoded[i])
140+
bigEndian |= uint64(decoded[i]) // apply OR for current byte
141+
}
142+
143+
mask := uint64(0x7fffffffff80)
144+
145+
num := (bigEndian & mask) >> 7 // apply mask and get rid of last 7 bytes from mask
146+
147+
return fmt.Sprintf("%d", num), nil
148+
}

v3/internal/awssupport/awssupport_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"net/http"
1111
"strings"
1212
"testing"
13+
14+
"github.com/aws/aws-sdk-go-v2/aws"
1315
)
1416

1517
func TestGetTableName(t *testing.T) {
@@ -84,3 +86,133 @@ func TestGetRequestID(t *testing.T) {
8486
}
8587
}
8688
}
89+
90+
func TestAWSAccountIdFromAWSAccessKey(t *testing.T) {
91+
tests := []struct {
92+
name string // description of this test case
93+
// Named input parameters for target function.
94+
creds aws.Credentials
95+
want string
96+
wantErr bool
97+
wantErrStr string // error message returned
98+
}{
99+
{
100+
name: "first test",
101+
creds: aws.Credentials{
102+
AccountID: "",
103+
AccessKeyID: "AKIASAWSR23456AWS357",
104+
},
105+
want: "138954266361",
106+
wantErr: false,
107+
},
108+
{
109+
name: "AccountID already exists and access key exists. Should return AccountID immediately",
110+
creds: aws.Credentials{
111+
AccountID: "123451234512",
112+
AccessKeyID: "ASKDHA123457AKJFHAKS",
113+
},
114+
want: "123451234512",
115+
wantErr: false,
116+
},
117+
{
118+
name: "AccountID already exists and access key exists with too short of length. Should return AccountID immediately",
119+
creds: aws.Credentials{
120+
AccountID: "123451234512",
121+
AccessKeyID: "a",
122+
},
123+
want: "123451234512",
124+
wantErr: false,
125+
},
126+
{
127+
name: "AccountID already exists and access key exists with improper format. Should return AccountID immediately",
128+
creds: aws.Credentials{
129+
AccountID: "123451234512",
130+
AccessKeyID: "a a a. ",
131+
},
132+
want: "123451234512",
133+
wantErr: false,
134+
},
135+
{
136+
name: "AccountID already exists and access key does not exist. Should return AccountID immediately",
137+
creds: aws.Credentials{
138+
AccountID: "123451234512",
139+
},
140+
want: "123451234512",
141+
wantErr: false,
142+
},
143+
{
144+
name: "AccountID does not exist and access key does not exist. Should return an error",
145+
creds: aws.Credentials{},
146+
want: "",
147+
wantErr: true,
148+
wantErrStr: "no access key id found",
149+
},
150+
{
151+
name: "AccountID does not exist and access key is in an improper format. Should return an error",
152+
creds: aws.Credentials{
153+
AccessKeyID: "123asdfas",
154+
},
155+
want: "",
156+
wantErr: true,
157+
wantErrStr: "improper access key id format",
158+
},
159+
{
160+
name: "AccountID does not exist and access key is in an improper format with only one character. Should return an error",
161+
creds: aws.Credentials{
162+
AccessKeyID: "a",
163+
},
164+
want: "",
165+
wantErr: true,
166+
wantErrStr: "improper access key id format",
167+
},
168+
{
169+
name: "AccountID does not exist and access key is in an improper format for decoding.",
170+
creds: aws.Credentials{
171+
AccessKeyID: "a a a. ",
172+
},
173+
want: "",
174+
wantErr: true,
175+
wantErrStr: "error decoding access keys",
176+
},
177+
{
178+
name: "AccountID does not exist and access key contains non base32 characters",
179+
creds: aws.Credentials{
180+
AccessKeyID: "AKIA1234567899876541",
181+
},
182+
want: "",
183+
wantErr: true,
184+
wantErrStr: "error decoding access keys",
185+
},
186+
{
187+
name: "AccountID does not exist and access key contains non base32 characters and is too short in length",
188+
creds: aws.Credentials{
189+
AccessKeyID: "AKIA1818",
190+
},
191+
want: "",
192+
wantErr: true,
193+
wantErrStr: "improper access key id format",
194+
},
195+
}
196+
for _, tt := range tests {
197+
t.Run(tt.name, func(t *testing.T) {
198+
got, gotErr := AWSAccountIdFromAWSAccessKey(tt.creds)
199+
if gotErr != nil {
200+
if !tt.wantErr {
201+
t.Errorf("AWSAccountIdFromAWSAccessKey() failed: %v", gotErr)
202+
} else {
203+
if tt.wantErrStr != gotErr.Error() {
204+
t.Errorf("AWSAccountIdFromAWSAccessKey() error = %v, want %v", gotErr.Error(), tt.wantErrStr)
205+
}
206+
}
207+
return
208+
}
209+
if tt.wantErr {
210+
t.Fatal("AWSAccountIdFromAWSAccessKey() succeeded unexpectedly")
211+
}
212+
// TODO: update the condition below to compare got with tt.want.
213+
if tt.want != got {
214+
t.Errorf("AWSAccountIdFromAWSAccessKey() = %v, want %v", got, tt.want)
215+
}
216+
})
217+
}
218+
}

0 commit comments

Comments
 (0)