Skip to content

Commit

Permalink
Update SigV4 Signer Design (#955)
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed Dec 10, 2020
1 parent 734d12f commit 4e5e79c
Show file tree
Hide file tree
Showing 253 changed files with 2,504 additions and 1,273 deletions.
2 changes: 1 addition & 1 deletion aws/credential_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *C

return &CredentialsCache{
provider: provider,
options: options,
options: options,
}
}

Expand Down
4 changes: 2 additions & 2 deletions aws/credential_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ func TestCredentialsCache_ExpireTime(t *testing.T) {
},
},
"no expire window with jitter": {
ExpireTime: mockTime,
JitterFrac: 0.5,
ExpireTime: mockTime,
JitterFrac: 0.5,
Validate: func(t *testing.T, v time.Time) {
t.Helper()
if e, a := mockTime, v; !e.Equal(a) {
Expand Down
2 changes: 1 addition & 1 deletion aws/signer/v4/functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestStandaloneSign_CustomURIEscape(t *testing.T) {
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
signer := v4.NewSigner(func(signer *v4.Signer) {
signer := v4.NewSigner(func(signer *v4.SignerOptions) {
signer.DisableURIPathEscaping = true
})

Expand Down
34 changes: 25 additions & 9 deletions aws/signer/v4/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,24 +195,36 @@ func (m *contentSHA256Header) HandleBuild(
return next.HandleBuild(ctx, in)
}

// SignHTTPRequest is a `FinalizeMiddleware` implementation for SigV4 HTTP Signing
type SignHTTPRequest struct {
// SignHTTPRequestMiddlewareOptions is the configuration options for the SignHTTPRequestMiddleware middleware.
type SignHTTPRequestMiddlewareOptions struct {
CredentialsProvider aws.CredentialsProvider
Signer HTTPSigner
LogSigning bool
}

// SignHTTPRequestMiddleware is a `FinalizeMiddleware` implementation for SigV4 HTTP Signing
type SignHTTPRequestMiddleware struct {
credentialsProvider aws.CredentialsProvider
signer HTTPSigner
logSigning bool
}

// NewSignHTTPRequestMiddleware constructs a SignHTTPRequest using the given Signer for signing requests
func NewSignHTTPRequestMiddleware(credentialsProvider aws.CredentialsProvider, signer HTTPSigner) *SignHTTPRequest {
return &SignHTTPRequest{credentialsProvider: credentialsProvider, signer: signer}
// NewSignHTTPRequestMiddleware constructs a SignHTTPRequestMiddleware using the given Signer for signing requests
func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware {
return &SignHTTPRequestMiddleware{
credentialsProvider: options.CredentialsProvider,
signer: options.Signer,
logSigning: options.LogSigning,
}
}

// ID is the SignHTTPRequest identifier
func (s *SignHTTPRequest) ID() string {
// ID is the SignHTTPRequestMiddleware identifier
func (s *SignHTTPRequestMiddleware) ID() string {
return "Signing"
}

// HandleFinalize will take the provided input and sign the request using the SigV4 authentication scheme
func (s *SignHTTPRequest) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
func (s *SignHTTPRequestMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
if !haveCredentialProvider(s.credentialsProvider) {
Expand All @@ -235,14 +247,18 @@ func (s *SignHTTPRequest) HandleFinalize(ctx context.Context, in middleware.Fina
return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)}
}

err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, sdk.NowTime())
err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, sdk.NowTime(), s.addSignerOptions)
if err != nil {
return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)}
}

return next.HandleFinalize(ctx, in)
}

func (s *SignHTTPRequestMiddleware) addSignerOptions(options *SignerOptions) {
options.LogSigning = s.logSigning
}

func haveCredentialProvider(p aws.CredentialsProvider) bool {
if p == nil {
return false
Expand Down
12 changes: 6 additions & 6 deletions aws/signer/v4/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ func TestComputePayloadHashMiddleware(t *testing.T) {
}
}

type httpSignerFunc func(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time) error
type httpSignerFunc func(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error

func (f httpSignerFunc) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time) error {
return f(ctx, credentials, r, payloadHash, service, region, signingTime)
func (f httpSignerFunc) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error {
return f(ctx, credentials, r, payloadHash, service, region, signingTime, optFns...)
}

func TestSignHTTPRequestMiddleware(t *testing.T) {
Expand Down Expand Up @@ -117,12 +117,12 @@ func TestSignHTTPRequestMiddleware(t *testing.T) {

for name, tt := range cases {
t.Run(name, func(t *testing.T) {
c := &SignHTTPRequest{
c := &SignHTTPRequestMiddleware{
credentialsProvider: tt.creds,
signer: httpSignerFunc(
func(ctx context.Context,
credentials aws.Credentials, r *http.Request, payloadHash string,
service string, region string, signingTime time.Time,
service string, region string, signingTime time.Time, _ ...func(*SignerOptions),
) error {
expectCreds, _ := unit.StubCredentialsProvider{}.Retrieve(context.Background())
if e, a := expectCreds, credentials; e != a {
Expand Down Expand Up @@ -196,5 +196,5 @@ var (
_ middleware.BuildMiddleware = &unsignedPayload{}
_ middleware.BuildMiddleware = &computePayloadSHA256{}
_ middleware.BuildMiddleware = &contentSHA256Header{}
_ middleware.FinalizeMiddleware = &SignHTTPRequest{}
_ middleware.FinalizeMiddleware = &SignHTTPRequestMiddleware{}
)
24 changes: 19 additions & 5 deletions aws/signer/v4/presign_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type HTTPPresigner interface {
PresignHTTP(
ctx context.Context, credentials aws.Credentials, r *http.Request,
payloadHash string, service string, region string, signingTime time.Time,
optFns ...func(*SignerOptions),
) (url string, signedHeader http.Header, err error)
}

Expand All @@ -30,6 +31,13 @@ type PresignedHTTPRequest struct {
SignedHeader http.Header
}

// PresignHTTPRequestMiddlewareOptions is the options for the PresignHTTPRequestMiddleware middleware.
type PresignHTTPRequestMiddlewareOptions struct {
CredentialsProvider aws.CredentialsProvider
Presigner HTTPPresigner
LogSigning bool
}

// PresignHTTPRequestMiddleware provides the Finalize middleware for creating a
// presigned URL for an HTTP request.
//
Expand All @@ -38,19 +46,21 @@ type PresignedHTTPRequest struct {
type PresignHTTPRequestMiddleware struct {
credentialsProvider aws.CredentialsProvider
presigner HTTPPresigner
logSigning bool
}

// NewPresignHTTPRequestMiddleware returns a new PresignHTTPRequestMiddleware
// initialized with the presigner.
func NewPresignHTTPRequestMiddleware(provider aws.CredentialsProvider, presigner HTTPPresigner) *PresignHTTPRequestMiddleware {
func NewPresignHTTPRequestMiddleware(options PresignHTTPRequestMiddlewareOptions) *PresignHTTPRequestMiddleware {
return &PresignHTTPRequestMiddleware{
credentialsProvider: provider,
presigner: presigner,
credentialsProvider: options.CredentialsProvider,
presigner: options.Presigner,
logSigning: options.LogSigning,
}
}

// ID provides the middleware ID.
func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequestMiddleware" }
func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequest" }

// HandleFinalize will take the provided input and create a presigned url for
// the http request using the SigV4 presign authentication scheme.
Expand Down Expand Up @@ -96,7 +106,7 @@ func (s *PresignHTTPRequestMiddleware) HandleFinalize(
}

u, h, err := s.presigner.PresignHTTP(ctx, credentials,
httpReq, payloadHash, signingName, signingRegion, sdk.NowTime())
httpReq, payloadHash, signingName, signingRegion, sdk.NowTime(), s.addSignerOptions)
if err != nil {
return out, metadata, &SigningError{
Err: fmt.Errorf("failed to sign http request, %w", err),
Expand All @@ -111,3 +121,7 @@ func (s *PresignHTTPRequestMiddleware) HandleFinalize(

return out, metadata, nil
}

func (s *PresignHTTPRequestMiddleware) addSignerOptions(options *SignerOptions) {
options.LogSigning = s.logSigning
}
5 changes: 4 additions & 1 deletion aws/signer/v4/presign_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ import (
type httpPresignerFunc func(
ctx context.Context, credentials aws.Credentials, r *http.Request,
payloadHash string, service string, region string, signingTime time.Time,
optFns ...func(*SignerOptions),
) (url string, signedHeader http.Header, err error)

func (f httpPresignerFunc) PresignHTTP(
ctx context.Context, credentials aws.Credentials, r *http.Request,
payloadHash string, service string, region string, signingTime time.Time,
optFns ...func(*SignerOptions),
) (
url string, signedHeader http.Header, err error,
) {
return f(ctx, credentials, r, payloadHash, service, region, signingTime)
return f(ctx, credentials, r, payloadHash, service, region, signingTime, optFns...)
}

func TestPresignHTTPRequestMiddleware(t *testing.T) {
Expand Down Expand Up @@ -109,6 +111,7 @@ func TestPresignHTTPRequestMiddleware(t *testing.T) {
presigner: httpPresignerFunc(func(
ctx context.Context, credentials aws.Credentials, r *http.Request,
payloadHash string, service string, region string, signingTime time.Time,
_ ...func(*SignerOptions),
) (url string, signedHeader http.Header, err error) {

if !haveCredentialProvider(c.Creds) {
Expand Down
63 changes: 38 additions & 25 deletions aws/signer/v4/v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,15 @@ const (

// HTTPSigner is an interface to a SigV4 signer that can sign HTTP requests
type HTTPSigner interface {
SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time) error
SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error
}

type keyDerivator interface {
DeriveKey(credential aws.Credentials, service, region string, signingTime v4Internal.SigningTime) []byte
}

// Signer applies AWS v4 signing to given request. Use this to sign requests
// that need to be signed with AWS V4 Signatures.
type Signer struct {
// SignerOptions is the SigV4 Signer options.
type SignerOptions struct {
// Disables the Signer's moving HTTP header key/value pairs from the HTTP
// request header to the request's query string. This is most commonly used
// with pre-signed requests preventing headers from being added to the
Expand All @@ -101,19 +100,24 @@ type Signer struct {
// This will enable logging of the canonical request, the string to sign, and for presigning the subsequent
// presigned URL.
LogSigning bool
}

// Signer applies AWS v4 signing to given request. Use this to sign requests
// that need to be signed with AWS V4 Signatures.
type Signer struct {
options SignerOptions
keyDerivator keyDerivator
}

// NewSigner returns a new SigV4 Signer
func NewSigner(optFns ...func(signer *Signer)) *Signer {
s := &Signer{keyDerivator: v4Internal.NewSigningKeyDeriver()}
func NewSigner(optFns ...func(signer *SignerOptions)) *Signer {
options := SignerOptions{}

for _, fn := range optFns {
fn(s)
fn(&options)
}

return s
return &Signer{options: options, keyDerivator: v4Internal.NewSigningKeyDeriver()}
}

type httpSigner struct {
Expand Down Expand Up @@ -240,10 +244,6 @@ func buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature
return parts.String()
}

func (v4 Signer) getLogger(ctx context.Context) logging.Logger {
return logging.WithContext(ctx, v4.Logger)
}

// SignHTTP signs AWS v4 requests with the provided payload hash, service name, region the
// request is made to, and time the request is signed at. The signTime allows
// you to specify that a request is signed for the future, and cannot be
Expand All @@ -255,25 +255,31 @@ func (v4 Signer) getLogger(ctx context.Context) logging.Logger {
// will not be lost.
//
// The passed in request will be modified in place.
func (v4 Signer) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time) error {
func (s Signer) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(options *SignerOptions)) error {
options := s.options

for _, fn := range optFns {
fn(&options)
}

signer := &httpSigner{
Request: r,
PayloadHash: payloadHash,
ServiceName: service,
Region: region,
Credentials: credentials,
Time: v4Internal.NewSigningTime(signingTime.UTC()),
DisableHeaderHoisting: v4.DisableHeaderHoisting,
DisableURIPathEscaping: v4.DisableURIPathEscaping,
KeyDerivator: v4.keyDerivator,
DisableHeaderHoisting: options.DisableHeaderHoisting,
DisableURIPathEscaping: options.DisableURIPathEscaping,
KeyDerivator: s.keyDerivator,
}

signedRequest, err := signer.Build()
if err != nil {
return err
}

v4.logSigningInfo(ctx, &signedRequest, false)
logSigningInfo(ctx, options, &signedRequest, false)

return nil
}
Expand Down Expand Up @@ -307,10 +313,17 @@ func (v4 Signer) SignHTTP(ctx context.Context, credentials aws.Credentials, r *h
// req.URL.RawQuery = query.Encode()
//
// This method does not modify the provided request.
func (v4 *Signer) PresignHTTP(
func (s *Signer) PresignHTTP(
ctx context.Context, credentials aws.Credentials, r *http.Request,
payloadHash string, service string, region string, signingTime time.Time,
optFns ...func(*SignerOptions),
) (signedURI string, signedHeaders http.Header, err error) {
options := s.options

for _, fn := range optFns {
fn(&options)
}

signer := &httpSigner{
Request: r.Clone(r.Context()),
PayloadHash: payloadHash,
Expand All @@ -319,17 +332,17 @@ func (v4 *Signer) PresignHTTP(
Credentials: credentials,
Time: v4Internal.NewSigningTime(signingTime.UTC()),
IsPreSign: true,
DisableHeaderHoisting: v4.DisableHeaderHoisting,
DisableURIPathEscaping: v4.DisableURIPathEscaping,
KeyDerivator: v4.keyDerivator,
DisableHeaderHoisting: options.DisableHeaderHoisting,
DisableURIPathEscaping: options.DisableURIPathEscaping,
KeyDerivator: s.keyDerivator,
}

signedRequest, err := signer.Build()
if err != nil {
return "", nil, err
}

v4.logSigningInfo(ctx, &signedRequest, true)
logSigningInfo(ctx, options, &signedRequest, true)

return signedRequest.Request.URL.String(), signedRequest.SignedHeaders, nil
}
Expand Down Expand Up @@ -461,15 +474,15 @@ func (s *httpSigner) setRequiredSigningFields(headers http.Header, query url.Val
}
}

func (v4 Signer) logSigningInfo(ctx context.Context, request *signedRequest, isPresign bool) {
if !v4.LogSigning {
func logSigningInfo(ctx context.Context, options SignerOptions, request *signedRequest, isPresign bool) {
if !options.LogSigning {
return
}
signedURLMsg := ""
if isPresign {
signedURLMsg = fmt.Sprintf(logSignedURLMsg, request.Request.URL.String())
}
logger := v4.getLogger(ctx)
logger := logging.WithContext(ctx, options.Logger)
logger.Logf(logging.Debug, logSignInfoMsg, request.CanonicalString, request.StringToSign, signedURLMsg)
}

Expand Down
Loading

0 comments on commit 4e5e79c

Please sign in to comment.