Skip to content

Commit

Permalink
Allow to use AWS Signature v1 for creating signed AWS urls
Browse files Browse the repository at this point in the history
Some aws implementations, for example the quobyte object storage, do not
support the v4 signing algorithm, but only v1.
This makes it possible to configure the signatureVersion.

The algorithm implementation was ported from https://github.com/oNestLab/botocore/blob/d6c1be296e8cfe0706cb0c8bbcad9c095d0f4d09/botocore/auth.py#L860-L862
which is used by the aws CLI client.

This fixes vmware-tanzu#811.
  • Loading branch information
bashofmann committed Oct 25, 2018
1 parent 74cb6a2 commit 3308463
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 4 deletions.
18 changes: 14 additions & 4 deletions pkg/cloudprovider/aws/object_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package aws

import (
"github.com/aws/aws-sdk-go/aws/signer/v4"
"io"
"strconv"
"time"
Expand All @@ -36,13 +37,15 @@ const (
kmsKeyIDKey = "kmsKeyId"
s3ForcePathStyleKey = "s3ForcePathStyle"
bucketKey = "bucket"
signatureVersionKey = "signatureVersion"
)

type objectStore struct {
log logrus.FieldLogger
s3 *s3.S3
s3Uploader *s3manager.Uploader
kmsKeyID string
log logrus.FieldLogger
s3 *s3.S3
s3Uploader *s3manager.Uploader
kmsKeyID string
signatureVersion string
}

func NewObjectStore(logger logrus.FieldLogger) cloudprovider.ObjectStore {
Expand All @@ -55,6 +58,7 @@ func (o *objectStore) Init(config map[string]string) error {
s3URL = config[s3URLKey]
kmsKeyID = config[kmsKeyIDKey]
s3ForcePathStyleVal = config[s3ForcePathStyleKey]
signatureVersion = config[signatureVersionKey]

// note that bucket is automatically added to the config map
// by the server from the ObjectStorageProviderConfig so
Expand Down Expand Up @@ -112,6 +116,7 @@ func (o *objectStore) Init(config map[string]string) error {
o.s3 = s3.New(sess)
o.s3Uploader = s3manager.NewUploader(sess)
o.kmsKeyID = kmsKeyID
o.signatureVersion = signatureVersion

return nil
}
Expand Down Expand Up @@ -207,5 +212,10 @@ func (o *objectStore) CreateSignedURL(bucket, key string, ttl time.Duration) (st
Key: aws.String(key),
})

if o.signatureVersion == "1" {
req.Handlers.Sign.Remove(v4.SignRequestHandler)
req.Handlers.Sign.PushBackNamed(V1SignRequestHandler)
}

return req.Presign(ttl)
}
131 changes: 131 additions & 0 deletions pkg/cloudprovider/aws/v1_sign_request_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package aws

import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
)

var (
errInvalidMethod = errors.New("v1 signer only handles HTTP GET")
)

type signer struct {
// Values that must be populated from the request
Request *http.Request
Time time.Time
Credentials *credentials.Credentials
Debug aws.LogLevelType
Logger aws.Logger

Query url.Values
stringToSign string
signature string
}

// SignRequestHandler is a named request handler the SDK will use to sign
// service client request with using the V4 signature.
var V1SignRequestHandler = request.NamedHandler{
Name: "v1.SignRequestHandler", Fn: SignSDKRequest,
}

func SignSDKRequest(req *request.Request) {
// If the request does not need to be signed ignore the signing of the
// request if the AnonymousCredentials object is used.
if req.Config.Credentials == credentials.AnonymousCredentials {
return
}

if req.HTTPRequest.Method != "GET" {
// The V1 signer only supports GET
req.Error = errInvalidMethod
return
}

v1 := signer{
Request: req.HTTPRequest,
Time: req.Time,
Credentials: req.Config.Credentials,
Debug: req.Config.LogLevel.Value(),
Logger: req.Config.Logger,
}

req.Error = v1.Sign()

if req.Error != nil {
return
}

req.HTTPRequest.URL.RawQuery = v1.Query.Encode()
}

func (v1 *signer) Sign() error {
credValue, err := v1.Credentials.Get()
if err != nil {
return err
}

v1.Query = v1.Request.URL.Query()

// Set new query parameters
v1.Query.Set("AWSAccessKeyId", credValue.AccessKeyID)
if credValue.SessionToken != "" {
v1.Query.Set("SecurityToken", credValue.SessionToken)
}

// in case this is a retry, ensure no signature present
v1.Query.Del("Signature")

method := v1.Request.Method
//host := v1.Request.URL.Host
path := v1.Request.URL.Path
if path == "" {
path = "/"
}

expires := strconv.FormatInt(v1.Time.Unix()+3600, 10)

// build the canonical string for the v1 signature
v1.stringToSign = strings.Join([]string{
method,
"",
"",
expires,
path,
}, "\n")

hash := hmac.New(sha1.New, []byte(credValue.SecretAccessKey))
hash.Write([]byte(v1.stringToSign))
v1.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
v1.Query.Set("Signature", v1.signature)
v1.Query.Set("Expires", expires)

if v1.Debug.Matches(aws.LogDebugWithSigning) {
v1.logSigningInfo()
}

return nil
}

const logSignInfoMsg = `DEBUG: Request Signature:
---[ STRING TO SIGN ]--------------------------------
%s
---[ SIGNATURE ]-------------------------------------
%s
-----------------------------------------------------`

func (v1 *signer) logSigningInfo() {
msg := fmt.Sprintf(logSignInfoMsg, v1.stringToSign, v1.Query.Get("Signature"))
v1.Logger.Log(msg)
}

0 comments on commit 3308463

Please sign in to comment.