github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/signer/aws-sign.go (about) 1 package signer 2 3 import ( 4 "context" 5 "crypto/sha256" 6 "encoding/hex" 7 "errors" 8 "io" 9 "net/http" 10 "time" 11 12 "github.com/aws/aws-sdk-go-v2/aws" 13 v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" 14 awsconfig "github.com/aws/aws-sdk-go-v2/config" 15 "github.com/aws/aws-sdk-go-v2/credentials" 16 "github.com/projectdiscovery/gologger" 17 errorutil "github.com/projectdiscovery/utils/errors" 18 ) 19 20 const defaultEmptyPayloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 21 22 // AWSOptions 23 type AWSOptions struct { 24 AwsID string 25 AwsSecretToken string 26 Service string 27 Region string 28 } 29 30 // Validate Signature Arguments 31 func (a *AWSOptions) Validate() error { 32 if a.Service == "" { 33 return errors.New("aws service cannot be empty") 34 } 35 if a.Region == "" { 36 return errors.New("aws region cannot be empty") 37 } 38 39 return nil 40 } 41 42 // AWS v4 signer 43 type AWSSigner struct { 44 creds *aws.Credentials 45 signer *v4.Signer 46 options *AWSOptions 47 } 48 49 // SignHTTP 50 func (a *AWSSigner) SignHTTP(ctx context.Context, request *http.Request) error { 51 if region, ok := ctx.Value(SignerArg("region")).(string); ok && region != "" { 52 a.options.Region = region 53 } 54 if service, ok := ctx.Value(SignerArg("service")).(string); ok && service != "" { 55 a.options.Service = service 56 } 57 if err := a.options.Validate(); err != nil { 58 return err 59 } 60 // contentHash is sha256 hash of response body 61 contentHash := a.getPayloadHash(request) 62 if err := a.signer.SignHTTP(ctx, *a.creds, request, contentHash, a.options.Service, a.options.Region, time.Now()); err != nil { 63 return errorutil.NewWithErr(err).Msgf("failed to sign http request using aws v4 signer") 64 } 65 // add x-amz-content-sha256 header to request 66 request.Header.Set("x-amz-content-sha256", contentHash) 67 return nil 68 } 69 70 // getPayloadHash returns hex encoded SHA-256 of request body 71 func (a *AWSSigner) getPayloadHash(request *http.Request) string { 72 if request.Body == nil { 73 // Default Hash of Empty Payload 74 return defaultEmptyPayloadHash 75 } 76 77 // no need to close request body since it is a reusablereadercloser 78 bin, err := io.ReadAll(request.Body) 79 if err != nil { 80 gologger.Error().Msgf("aws signer: failed to read request body: %s", err) 81 } 82 sha256Hash := sha256.Sum256(bin) 83 return hex.EncodeToString(sha256Hash[:]) 84 } 85 86 // NewAwsSigner 87 func NewAwsSigner(opts *AWSOptions) (*AWSSigner, error) { 88 credcache := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(opts.AwsID, opts.AwsSecretToken, "")) 89 awscred, err := credcache.Retrieve(context.TODO()) 90 if err != nil { 91 return nil, err 92 } 93 return &AWSSigner{ 94 creds: &awscred, 95 options: opts, 96 signer: v4.NewSigner(), 97 }, nil 98 } 99 100 // NewAwsSignerFromConfig 101 func NewAwsSignerFromConfig(opts *AWSOptions) (*AWSSigner, error) { 102 /* 103 NewAwsSignerFromConfig fetches credentials from both 104 1. Environment Variables (old & new) 105 2. Shared Credentials ($HOME/.aws) 106 */ 107 cfg, err := awsconfig.LoadDefaultConfig(context.TODO()) 108 if err != nil { 109 return nil, err 110 } 111 credcache := aws.NewCredentialsCache(cfg.Credentials) 112 awscred, err := credcache.Retrieve(context.TODO()) 113 if err != nil { 114 return nil, err 115 } 116 return &AWSSigner{ 117 creds: &awscred, 118 options: opts, 119 signer: v4.NewSigner(func(signer *v4.SignerOptions) { 120 // signer.DisableURIPathEscaping = true 121 }), 122 }, nil 123 } 124 125 var AwsSkipList = map[string]interface{}{ 126 "region": struct{}{}, 127 } 128 129 var AwsDefaultVars = map[string]interface{}{ 130 "region": "us-east-2", 131 "service": "sts", 132 } 133 134 var AwsInternalOnlyVars = map[string]interface{}{ 135 "aws-id": struct{}{}, 136 "aws-secret": struct{}{}, 137 }