Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SigV4 Signer Design #955

Merged
merged 4 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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