middleware.go (14871B)
1 package v4 2 3 import ( 4 "context" 5 "crypto/sha256" 6 "encoding/hex" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 12 "github.com/aws/aws-sdk-go-v2/aws" 13 awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" 14 "github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics" 15 v4Internal "github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4" 16 internalauth "github.com/aws/aws-sdk-go-v2/internal/auth" 17 "github.com/aws/aws-sdk-go-v2/internal/sdk" 18 "github.com/aws/smithy-go/middleware" 19 smithyhttp "github.com/aws/smithy-go/transport/http" 20 ) 21 22 const computePayloadHashMiddlewareID = "ComputePayloadHash" 23 24 // HashComputationError indicates an error occurred while computing the signing hash 25 type HashComputationError struct { 26 Err error 27 } 28 29 // Error is the error message 30 func (e *HashComputationError) Error() string { 31 return fmt.Sprintf("failed to compute payload hash: %v", e.Err) 32 } 33 34 // Unwrap returns the underlying error if one is set 35 func (e *HashComputationError) Unwrap() error { 36 return e.Err 37 } 38 39 // SigningError indicates an error condition occurred while performing SigV4 signing 40 type SigningError struct { 41 Err error 42 } 43 44 func (e *SigningError) Error() string { 45 return fmt.Sprintf("failed to sign request: %v", e.Err) 46 } 47 48 // Unwrap returns the underlying error cause 49 func (e *SigningError) Unwrap() error { 50 return e.Err 51 } 52 53 // UseDynamicPayloadSigningMiddleware swaps the compute payload sha256 middleware with a resolver middleware that 54 // switches between unsigned and signed payload based on TLS state for request. 55 // This middleware should not be used for AWS APIs that do not support unsigned payload signing auth. 56 // By default, SDK uses this middleware for known AWS APIs that support such TLS based auth selection . 57 // 58 // Usage example - 59 // S3 PutObject API allows unsigned payload signing auth usage when TLS is enabled, and uses this middleware to 60 // dynamically switch between unsigned and signed payload based on TLS state for request. 61 func UseDynamicPayloadSigningMiddleware(stack *middleware.Stack) error { 62 _, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &dynamicPayloadSigningMiddleware{}) 63 return err 64 } 65 66 // dynamicPayloadSigningMiddleware dynamically resolves the middleware that computes and set payload sha256 middleware. 67 type dynamicPayloadSigningMiddleware struct { 68 } 69 70 // ID returns the resolver identifier 71 func (m *dynamicPayloadSigningMiddleware) ID() string { 72 return computePayloadHashMiddlewareID 73 } 74 75 // HandleFinalize delegates SHA256 computation according to whether the request 76 // is TLS-enabled. 77 func (m *dynamicPayloadSigningMiddleware) HandleFinalize( 78 ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, 79 ) ( 80 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 81 ) { 82 req, ok := in.Request.(*smithyhttp.Request) 83 if !ok { 84 return out, metadata, fmt.Errorf("unknown transport type %T", in.Request) 85 } 86 87 if req.IsHTTPS() { 88 return (&UnsignedPayload{}).HandleFinalize(ctx, in, next) 89 } 90 return (&ComputePayloadSHA256{}).HandleFinalize(ctx, in, next) 91 } 92 93 // UnsignedPayload sets the SigV4 request payload hash to unsigned. 94 // 95 // Will not set the Unsigned Payload magic SHA value, if a SHA has already been 96 // stored in the context. (e.g. application pre-computed SHA256 before making 97 // API call). 98 // 99 // This middleware does not check the X-Amz-Content-Sha256 header, if that 100 // header is serialized a middleware must translate it into the context. 101 type UnsignedPayload struct{} 102 103 // AddUnsignedPayloadMiddleware adds unsignedPayload to the operation 104 // middleware stack 105 func AddUnsignedPayloadMiddleware(stack *middleware.Stack) error { 106 return stack.Finalize.Insert(&UnsignedPayload{}, "ResolveEndpointV2", middleware.After) 107 } 108 109 // ID returns the unsignedPayload identifier 110 func (m *UnsignedPayload) ID() string { 111 return computePayloadHashMiddlewareID 112 } 113 114 // HandleFinalize sets the payload hash magic value to the unsigned sentinel. 115 func (m *UnsignedPayload) HandleFinalize( 116 ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, 117 ) ( 118 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 119 ) { 120 if GetPayloadHash(ctx) == "" { 121 ctx = SetPayloadHash(ctx, v4Internal.UnsignedPayload) 122 } 123 return next.HandleFinalize(ctx, in) 124 } 125 126 // ComputePayloadSHA256 computes SHA256 payload hash to sign. 127 // 128 // Will not set the Unsigned Payload magic SHA value, if a SHA has already been 129 // stored in the context. (e.g. application pre-computed SHA256 before making 130 // API call). 131 // 132 // This middleware does not check the X-Amz-Content-Sha256 header, if that 133 // header is serialized a middleware must translate it into the context. 134 type ComputePayloadSHA256 struct{} 135 136 // AddComputePayloadSHA256Middleware adds computePayloadSHA256 to the 137 // operation middleware stack 138 func AddComputePayloadSHA256Middleware(stack *middleware.Stack) error { 139 return stack.Finalize.Insert(&ComputePayloadSHA256{}, "ResolveEndpointV2", middleware.After) 140 } 141 142 // RemoveComputePayloadSHA256Middleware removes computePayloadSHA256 from the 143 // operation middleware stack 144 func RemoveComputePayloadSHA256Middleware(stack *middleware.Stack) error { 145 _, err := stack.Finalize.Remove(computePayloadHashMiddlewareID) 146 return err 147 } 148 149 // ID is the middleware name 150 func (m *ComputePayloadSHA256) ID() string { 151 return computePayloadHashMiddlewareID 152 } 153 154 // HandleFinalize computes the payload hash for the request, storing it to the 155 // context. This is a no-op if a caller has previously set that value. 156 func (m *ComputePayloadSHA256) HandleFinalize( 157 ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, 158 ) ( 159 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 160 ) { 161 if GetPayloadHash(ctx) != "" { 162 return next.HandleFinalize(ctx, in) 163 } 164 165 req, ok := in.Request.(*smithyhttp.Request) 166 if !ok { 167 return out, metadata, &HashComputationError{ 168 Err: fmt.Errorf("unexpected request middleware type %T", in.Request), 169 } 170 } 171 172 hash := sha256.New() 173 if stream := req.GetStream(); stream != nil { 174 _, err = io.Copy(hash, stream) 175 if err != nil { 176 return out, metadata, &HashComputationError{ 177 Err: fmt.Errorf("failed to compute payload hash, %w", err), 178 } 179 } 180 181 if err := req.RewindStream(); err != nil { 182 return out, metadata, &HashComputationError{ 183 Err: fmt.Errorf("failed to seek body to start, %w", err), 184 } 185 } 186 } 187 188 ctx = SetPayloadHash(ctx, hex.EncodeToString(hash.Sum(nil))) 189 190 return next.HandleFinalize(ctx, in) 191 } 192 193 // SwapComputePayloadSHA256ForUnsignedPayloadMiddleware replaces the 194 // ComputePayloadSHA256 middleware with the UnsignedPayload middleware. 195 // 196 // Use this to disable computing the Payload SHA256 checksum and instead use 197 // UNSIGNED-PAYLOAD for the SHA256 value. 198 func SwapComputePayloadSHA256ForUnsignedPayloadMiddleware(stack *middleware.Stack) error { 199 _, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &UnsignedPayload{}) 200 return err 201 } 202 203 // ContentSHA256Header sets the X-Amz-Content-Sha256 header value to 204 // the Payload hash stored in the context. 205 type ContentSHA256Header struct{} 206 207 // AddContentSHA256HeaderMiddleware adds ContentSHA256Header to the 208 // operation middleware stack 209 func AddContentSHA256HeaderMiddleware(stack *middleware.Stack) error { 210 return stack.Finalize.Insert(&ContentSHA256Header{}, computePayloadHashMiddlewareID, middleware.After) 211 } 212 213 // RemoveContentSHA256HeaderMiddleware removes contentSHA256Header middleware 214 // from the operation middleware stack 215 func RemoveContentSHA256HeaderMiddleware(stack *middleware.Stack) error { 216 _, err := stack.Finalize.Remove((*ContentSHA256Header)(nil).ID()) 217 return err 218 } 219 220 // ID returns the ContentSHA256HeaderMiddleware identifier 221 func (m *ContentSHA256Header) ID() string { 222 return "SigV4ContentSHA256Header" 223 } 224 225 // HandleFinalize sets the X-Amz-Content-Sha256 header value to the Payload hash 226 // stored in the context. 227 func (m *ContentSHA256Header) HandleFinalize( 228 ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, 229 ) ( 230 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 231 ) { 232 req, ok := in.Request.(*smithyhttp.Request) 233 if !ok { 234 return out, metadata, &HashComputationError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)} 235 } 236 237 req.Header.Set(v4Internal.ContentSHAKey, GetPayloadHash(ctx)) 238 return next.HandleFinalize(ctx, in) 239 } 240 241 // SignHTTPRequestMiddlewareOptions is the configuration options for 242 // [SignHTTPRequestMiddleware]. 243 // 244 // Deprecated: [SignHTTPRequestMiddleware] is deprecated. 245 type SignHTTPRequestMiddlewareOptions struct { 246 CredentialsProvider aws.CredentialsProvider 247 Signer HTTPSigner 248 LogSigning bool 249 } 250 251 // SignHTTPRequestMiddleware is a `FinalizeMiddleware` implementation for SigV4 252 // HTTP Signing. 253 // 254 // Deprecated: AWS service clients no longer use this middleware. Signing as an 255 // SDK operation is now performed through an internal per-service middleware 256 // which opaquely selects and uses the signer from the resolved auth scheme. 257 type SignHTTPRequestMiddleware struct { 258 credentialsProvider aws.CredentialsProvider 259 signer HTTPSigner 260 logSigning bool 261 } 262 263 // NewSignHTTPRequestMiddleware constructs a [SignHTTPRequestMiddleware] using 264 // the given [Signer] for signing requests. 265 // 266 // Deprecated: SignHTTPRequestMiddleware is deprecated. 267 func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware { 268 return &SignHTTPRequestMiddleware{ 269 credentialsProvider: options.CredentialsProvider, 270 signer: options.Signer, 271 logSigning: options.LogSigning, 272 } 273 } 274 275 // ID is the SignHTTPRequestMiddleware identifier. 276 // 277 // Deprecated: SignHTTPRequestMiddleware is deprecated. 278 func (s *SignHTTPRequestMiddleware) ID() string { 279 return "Signing" 280 } 281 282 // HandleFinalize will take the provided input and sign the request using the 283 // SigV4 authentication scheme. 284 // 285 // Deprecated: SignHTTPRequestMiddleware is deprecated. 286 func (s *SignHTTPRequestMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) ( 287 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 288 ) { 289 if !haveCredentialProvider(s.credentialsProvider) { 290 return next.HandleFinalize(ctx, in) 291 } 292 293 req, ok := in.Request.(*smithyhttp.Request) 294 if !ok { 295 return out, metadata, &SigningError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)} 296 } 297 298 signingName, signingRegion := awsmiddleware.GetSigningName(ctx), awsmiddleware.GetSigningRegion(ctx) 299 payloadHash := GetPayloadHash(ctx) 300 if len(payloadHash) == 0 { 301 return out, metadata, &SigningError{Err: fmt.Errorf("computed payload hash missing from context")} 302 } 303 304 mctx := metrics.Context(ctx) 305 306 if mctx != nil { 307 if attempt, err := mctx.Data().LatestAttempt(); err == nil { 308 attempt.CredentialFetchStartTime = sdk.NowTime() 309 } 310 } 311 312 credentials, err := s.credentialsProvider.Retrieve(ctx) 313 314 if mctx != nil { 315 if attempt, err := mctx.Data().LatestAttempt(); err == nil { 316 attempt.CredentialFetchEndTime = sdk.NowTime() 317 } 318 } 319 320 if err != nil { 321 return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)} 322 } 323 324 signerOptions := []func(o *SignerOptions){ 325 func(o *SignerOptions) { 326 o.Logger = middleware.GetLogger(ctx) 327 o.LogSigning = s.logSigning 328 }, 329 } 330 331 // existing DisableURIPathEscaping is equivalent in purpose 332 // to authentication scheme property DisableDoubleEncoding 333 disableDoubleEncoding, overridden := internalauth.GetDisableDoubleEncoding(ctx) 334 if overridden { 335 signerOptions = append(signerOptions, func(o *SignerOptions) { 336 o.DisableURIPathEscaping = disableDoubleEncoding 337 }) 338 } 339 340 if mctx != nil { 341 if attempt, err := mctx.Data().LatestAttempt(); err == nil { 342 attempt.SignStartTime = sdk.NowTime() 343 } 344 } 345 346 err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, sdk.NowTime(), signerOptions...) 347 348 if mctx != nil { 349 if attempt, err := mctx.Data().LatestAttempt(); err == nil { 350 attempt.SignEndTime = sdk.NowTime() 351 } 352 } 353 354 if err != nil { 355 return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)} 356 } 357 358 ctx = awsmiddleware.SetSigningCredentials(ctx, credentials) 359 360 return next.HandleFinalize(ctx, in) 361 } 362 363 // StreamingEventsPayload signs input event stream messages. 364 type StreamingEventsPayload struct{} 365 366 // AddStreamingEventsPayload adds the streamingEventsPayload middleware to the stack. 367 func AddStreamingEventsPayload(stack *middleware.Stack) error { 368 return stack.Finalize.Add(&StreamingEventsPayload{}, middleware.Before) 369 } 370 371 // ID identifies the middleware. 372 func (s *StreamingEventsPayload) ID() string { 373 return computePayloadHashMiddlewareID 374 } 375 376 // HandleFinalize marks the input stream to be signed with SigV4. 377 func (s *StreamingEventsPayload) HandleFinalize( 378 ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, 379 ) ( 380 out middleware.FinalizeOutput, metadata middleware.Metadata, err error, 381 ) { 382 contentSHA := GetPayloadHash(ctx) 383 if len(contentSHA) == 0 { 384 contentSHA = v4Internal.StreamingEventsPayload 385 } 386 387 ctx = SetPayloadHash(ctx, contentSHA) 388 389 return next.HandleFinalize(ctx, in) 390 } 391 392 // GetSignedRequestSignature attempts to extract the signature of the request. 393 // Returning an error if the request is unsigned, or unable to extract the 394 // signature. 395 func GetSignedRequestSignature(r *http.Request) ([]byte, error) { 396 const authHeaderSignatureElem = "Signature=" 397 398 if auth := r.Header.Get(authorizationHeader); len(auth) != 0 { 399 ps := strings.Split(auth, ", ") 400 for _, p := range ps { 401 if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 { 402 sig := p[len(authHeaderSignatureElem):] 403 if len(sig) == 0 { 404 return nil, fmt.Errorf("invalid request signature authorization header") 405 } 406 return hex.DecodeString(sig) 407 } 408 } 409 } 410 411 if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 { 412 return hex.DecodeString(sig) 413 } 414 415 return nil, fmt.Errorf("request not signed") 416 } 417 418 func haveCredentialProvider(p aws.CredentialsProvider) bool { 419 if p == nil { 420 return false 421 } 422 423 return !aws.IsCredentialsProvider(p, (*aws.AnonymousCredentials)(nil)) 424 } 425 426 type payloadHashKey struct{} 427 428 // GetPayloadHash retrieves the payload hash to use for signing 429 // 430 // Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues 431 // to clear all stack values. 432 func GetPayloadHash(ctx context.Context) (v string) { 433 v, _ = middleware.GetStackValue(ctx, payloadHashKey{}).(string) 434 return v 435 } 436 437 // SetPayloadHash sets the payload hash to be used for signing the request 438 // 439 // Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues 440 // to clear all stack values. 441 func SetPayloadHash(ctx context.Context, hash string) context.Context { 442 return middleware.WithStackValue(ctx, payloadHashKey{}, hash) 443 }