src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }