Skip to content

Commit e162ec2

Browse files
authored
[Fix] Upgraded AWS go-sdk to V2 (only for AWS Canary Token Verification) (#3907)
* upgrade go-aws-sdk to v2 as the v1 is about to be deprecated and also returning errors randomly when verify canary tokens. ran go mod tidy. * accidently removed the old version. readding old version * use aws go sdk v2 for access key verification * remove unused param * updated comments. * use verification client for better logging. SDK has changed the error if the provided Id is invalid, incorporated the handling for that error as well. * resolved the http client issue using with aws sdk. passing the user-agent via middleware in aws sdk. remove the hash logic since it was making the AWS Access key test as flaky by randomly generating the incorrect values. * re-added the retry logic if AWS returns 403
1 parent 544cf23 commit e162ec2

File tree

6 files changed

+155
-137
lines changed

6 files changed

+155
-137
lines changed

go.mod

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ require (
2525
github.com/alecthomas/kingpin/v2 v2.4.0
2626
github.com/avast/apkparser v0.0.0-20240729092610-90591e0804ae
2727
github.com/aws/aws-sdk-go v1.55.6
28+
github.com/aws/aws-sdk-go-v2 v1.36.1
29+
github.com/aws/aws-sdk-go-v2/config v1.29.6
30+
github.com/aws/aws-sdk-go-v2/credentials v1.17.59
31+
github.com/aws/aws-sdk-go-v2/service/sns v1.33.19
2832
github.com/aymanbagabas/go-osc52 v1.2.1
2933
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
3034
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
@@ -148,7 +152,16 @@ require (
148152
github.com/andybalholm/brotli v1.1.1 // indirect
149153
github.com/apache/arrow/go/v14 v14.0.2 // indirect
150154
github.com/atotto/clipboard v0.1.4 // indirect
151-
github.com/aws/smithy-go v1.20.1 // indirect
155+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
156+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect
157+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect
158+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
159+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
160+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
161+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect
162+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
163+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect
164+
github.com/aws/smithy-go v1.22.2 // indirect
152165
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
153166
github.com/aymerick/douceur v0.2.0 // indirect
154167
github.com/beorn7/perks v1.0.1 // indirect

go.sum

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,34 @@ github.com/avast/apkparser v0.0.0-20240729092610-90591e0804ae h1:rDNramK9mnAbvUB
118118
github.com/avast/apkparser v0.0.0-20240729092610-90591e0804ae/go.mod h1:GNvprXNmXaDjpHmN3RFxz5QdK5VXTUvmQludCbjoBy4=
119119
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
120120
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
121-
github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
122-
github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
121+
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
122+
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
123+
github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg=
124+
github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ=
125+
github.com/aws/aws-sdk-go-v2/credentials v1.17.59 h1:9btwmrt//Q6JcSdgJOLI98sdr5p7tssS9yAsGe8aKP4=
126+
github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdWidyZuI1ghNe2xjzUZAyT+08=
127+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso=
128+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI=
129+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
130+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
131+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
132+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
133+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
134+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
135+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
136+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
137+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
138+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
139+
github.com/aws/aws-sdk-go-v2/service/sns v1.33.19 h1:ghgWtf6FnkD6YqDUq65Zg5lzQ92xADHBoJdWUyChiFw=
140+
github.com/aws/aws-sdk-go-v2/service/sns v1.33.19/go.mod h1:/TQAkYgLlLoH1/2Y9qgaE460iPWhdq67emlW/ue42U8=
141+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ=
142+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y=
143+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE=
144+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ=
145+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE=
146+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc=
147+
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
148+
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
123149
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
124150
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
125151
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=

pkg/common/http.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ type CustomTransport struct {
8989
T http.RoundTripper
9090
}
9191

92-
func userAgent() string {
92+
func UserAgent() string {
9393
if len(feature.UserAgentSuffix.Load()) > 0 {
9494
return "TruffleHog " + feature.UserAgentSuffix.Load()
9595
}
9696
return "TruffleHog"
9797
}
9898

9999
func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
100-
req.Header.Add("User-Agent", userAgent())
100+
req.Header.Add("User-Agent", UserAgent())
101101
return t.T.RoundTrip(req)
102102
}
103103

pkg/detectors/aws/access_keys/accesskey.go

Lines changed: 67 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package access_keys
22

33
import (
44
"context"
5-
"encoding/hex"
6-
"encoding/json"
75
"fmt"
8-
"io"
6+
"net"
97
"net/http"
108
"strings"
119
"time"
1210

11+
"github.com/aws/aws-sdk-go-v2/aws/middleware"
12+
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
13+
"github.com/aws/aws-sdk-go-v2/config"
14+
"github.com/aws/aws-sdk-go-v2/credentials"
15+
"github.com/aws/aws-sdk-go-v2/service/sts"
1316
regexp "github.com/wasilibs/go-re2"
1417

1518
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
@@ -20,7 +23,7 @@ import (
2023
)
2124

2225
type scanner struct {
23-
verificationClient *http.Client
26+
verificationClient config.HTTPClient
2427
skipIDs map[string]struct{}
2528
detectors.DefaultMultiPartCredentialProvider
2629
}
@@ -55,7 +58,6 @@ var _ interface {
5558
} = (*scanner)(nil)
5659

5760
var (
58-
defaultVerificationClient = common.SaneHttpClient()
5961

6062
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
6163
// Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
@@ -72,6 +74,32 @@ func (s scanner) Keywords() []string {
7274
}
7375
}
7476

77+
// The recommended way by AWS is to use the SDK's http client.
78+
// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-http.html
79+
// Note: Using default http.Client causes SignatureInvalid error in response. therefore, based on http default client implementation, we are using the same configuration.
80+
func getDefaultBuildableClient() *awshttp.BuildableClient {
81+
return awshttp.NewBuildableClient().
82+
WithTimeout(common.DefaultResponseTimeout).
83+
WithDialerOptions(func(dialer *net.Dialer) {
84+
dialer.Timeout = 2 * time.Second
85+
dialer.KeepAlive = 5 * time.Second
86+
}).
87+
WithTransportOptions(func(tr *http.Transport) {
88+
tr.Proxy = http.ProxyFromEnvironment
89+
tr.MaxIdleConns = 5
90+
tr.IdleConnTimeout = 5 * time.Second
91+
tr.TLSHandshakeTimeout = 3 * time.Second
92+
tr.ExpectContinueTimeout = 1 * time.Second
93+
})
94+
}
95+
96+
func (s scanner) getAWSBuilableClient() config.HTTPClient {
97+
if s.verificationClient == nil {
98+
s.verificationClient = getDefaultBuildableClient()
99+
}
100+
return s.verificationClient
101+
}
102+
75103
// FromData will find and optionally verify AWS secrets in a given set of bytes.
76104
func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
77105
logger := logContext.AddLogger(ctx).Logger().WithName("aws")
@@ -127,7 +155,7 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
127155
isCanary = true
128156
s1.ExtraData["message"] = thinkstMessage
129157
if verify {
130-
verified, arn, err := s.verifyCanary(idMatch, secretMatch)
158+
verified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch)
131159
s1.Verified = verified
132160
if arn != "" {
133161
s1.ExtraData["arn"] = arn
@@ -139,7 +167,7 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
139167
isCanary = true
140168
s1.ExtraData["message"] = thinkstKnockoffsMessage
141169
if verify {
142-
verified, arn, err := s.verifyCanary(idMatch, secretMatch)
170+
verified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch)
143171
s1.Verified = verified
144172
if arn != "" {
145173
s1.ExtraData["arn"] = arn
@@ -154,7 +182,7 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
154182
}
155183

156184
if verify && !isCanary {
157-
isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, true)
185+
isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, len(secretMatches) > 1)
158186
s1.Verified = isVerified
159187

160188
// Log if the calculated ID does not match the ID value from verification.
@@ -199,117 +227,53 @@ const (
199227
)
200228

201229
func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, retryOn403 bool) (bool, map[string]string, error) {
202-
// REQUEST VALUES.
203-
now := time.Now().UTC()
204-
datestamp := now.Format("20060102")
205-
amzDate := now.Format("20060102T150405Z0700")
206-
207-
req, err := http.NewRequestWithContext(ctx, method, endpoint, nil)
230+
// Prep AWS Creds for STS
231+
cfg, err := config.LoadDefaultConfig(ctx,
232+
config.WithRegion(region),
233+
config.WithHTTPClient(s.getAWSBuilableClient()),
234+
config.WithCredentialsProvider(
235+
credentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, ""),
236+
),
237+
)
208238
if err != nil {
209239
return false, nil, err
210240
}
211-
req.Header.Set("Accept", "application/json")
212-
213-
// TASK 1: CREATE A CANONICAL REQUEST.
214-
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
215-
canonicalURI := "/"
216-
canonicalHeaders := "host:" + host + "\n"
217-
signedHeaders := "host"
218-
algorithm := "AWS4-HMAC-SHA256"
219-
credentialScope := fmt.Sprintf("%s/%s/%s/aws4_request", datestamp, region, service)
220-
221-
params := req.URL.Query()
222-
params.Add("Action", "GetCallerIdentity")
223-
params.Add("Version", "2011-06-15")
224-
params.Add("X-Amz-Algorithm", algorithm)
225-
params.Add("X-Amz-Credential", resIDMatch+"/"+credentialScope)
226-
params.Add("X-Amz-Date", amzDate)
227-
params.Add("X-Amz-Expires", "30")
228-
params.Add("X-Amz-SignedHeaders", signedHeaders)
229-
230-
canonicalQuerystring := params.Encode()
231-
payloadHash := aws.GetHash("") // empty payload
232-
canonicalRequest := method + "\n" + canonicalURI + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash
233-
234-
// TASK 2: CREATE THE STRING TO SIGN.
235-
stringToSign := algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + aws.GetHash(canonicalRequest)
236-
237-
// TASK 3: CALCULATE THE SIGNATURE.
238-
// https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
239-
hash := aws.GetHMAC([]byte(fmt.Sprintf("AWS4%s", resSecretMatch)), []byte(datestamp))
240-
hash = aws.GetHMAC(hash, []byte(region))
241-
hash = aws.GetHMAC(hash, []byte(service))
242-
hash = aws.GetHMAC(hash, []byte("aws4_request"))
243-
244-
signature2 := aws.GetHMAC(hash, []byte(stringToSign)) // Get Signature HMAC SHA256
245-
signature := hex.EncodeToString(signature2)
246-
247-
// TASK 4: ADD SIGNING INFORMATION TO THE REQUEST.
248-
params.Add("X-Amz-Signature", signature)
249-
req.Header.Add("Content-type", "application/x-www-form-urlencoded; charset=utf-8")
250-
req.URL.RawQuery = params.Encode()
251-
252-
client := s.verificationClient
253-
if client == nil {
254-
client = defaultVerificationClient
255-
}
241+
// Create STS client
242+
stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {
243+
o.APIOptions = append(o.APIOptions, middleware.AddUserAgentKeyValue("User-Agent", common.UserAgent()))
244+
})
256245

257-
res, err := client.Do(req)
246+
// Make the GetCallerIdentity API call
247+
resp, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
258248
if err != nil {
259-
return false, nil, err
260-
}
261-
defer func() {
262-
_, _ = io.Copy(io.Discard, res.Body)
263-
_ = res.Body.Close()
264-
}()
265-
266-
// TODO: tighten range of acceptable status codes
267-
if res.StatusCode >= 200 && res.StatusCode < 300 {
268-
identityInfo := aws.IdentityResponse{}
269-
if err := json.NewDecoder(res.Body).Decode(&identityInfo); err != nil {
270-
return false, nil, err
271-
}
272-
273-
extraData := map[string]string{
274-
"rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
275-
"account": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account,
276-
"user_id": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID,
277-
"arn": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn,
278-
}
279-
return true, extraData, nil
280-
} else if res.StatusCode == 403 {
281-
// Experimentation has indicated that if you make two GetCallerIdentity requests within five seconds that
249+
// Experimentation has indicated that if you make multiple GetCallerIdentity requests within five seconds that
282250
// share a key ID but are signed with different secrets the second one will be rejected with a 403 that
283251
// carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is
284252
// valid. Since this is exactly our access pattern, we need to work around it.
285253
//
286254
// Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The
287-
// response to the resubmission will be as expected. But there's a caveat: You can't have closed the body of
288-
// the response to the original second request, or read to its end, or the resubmission will also yield a
289-
// SignatureDoesNotMatch. For this reason, we have to re-request all 403s. We can't re-request only
290-
// SignatureDoesNotMatch responses, because we can only tell whether a given 403 is a SignatureDoesNotMatch
291-
// after decoding its response body, which requires reading the entire response body, which disables the
292-
// workaround.
255+
// response to the resubmission will be as expected.
293256
//
294257
// We are clearly deep in the guts of AWS implementation details here, so this all might change with no
295258
// notice. If you're here because something in this detector broke, you have my condolences.
296-
if retryOn403 {
297-
return s.verifyMatch(ctx, resIDMatch, resSecretMatch, false)
298-
}
299-
300-
var body aws.ErrorResponseBody
301-
if err = json.NewDecoder(res.Body).Decode(&body); err != nil {
302-
return false, nil, fmt.Errorf("couldn't parse the sts response body (%v)", err)
303-
}
304-
// All instances of the code I've seen in the wild are PascalCased but this check is
305-
// case-insensitive out of an abundance of caution
306-
if strings.EqualFold(body.Error.Code, "InvalidClientTokenId") {
259+
if strings.Contains(err.Error(), "StatusCode: 403") {
260+
if retryOn403 {
261+
return s.verifyMatch(ctx, resIDMatch, resSecretMatch, false)
262+
}
263+
return false, nil, nil
264+
} else if strings.Contains(err.Error(), "InvalidClientTokenId") {
307265
return false, nil, nil
308266
}
309-
return false, nil, fmt.Errorf("request returned status %d with an unexpected reason (%s: %s)", res.StatusCode, body.Error.Code, body.Error.Message)
310-
} else {
311-
return false, nil, fmt.Errorf("request to %v returned unexpected status %d", res.Request.URL, res.StatusCode)
267+
return false, nil, fmt.Errorf("request returned unexpected error: %w", err)
268+
}
269+
270+
extraData := map[string]string{
271+
"rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
272+
"account": *resp.Account,
273+
"user_id": *resp.UserId,
274+
"arn": *resp.Arn,
312275
}
276+
return true, extraData, nil
313277
}
314278

315279
func (s scanner) CleanResults(results []detectors.Result) []detectors.Result {

0 commit comments

Comments
 (0)