velero/pkg/cloudprovider/aws/v1_sign_request_handler.go

148 lines
3.6 KiB
Go

/*
Copyright 2018 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aws
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"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"
"github.com/pkg/errors"
)
var (
errInvalidMethod = errors.New("v1 signer only handles HTTP GET")
)
type signer struct {
// Values that must be populated from the request
request *request.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,
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 {
credentialsValue, err := v1.credentials.Get()
if err != nil {
return errors.Wrap(err, "error getting credentials")
}
httpRequest := v1.request.HTTPRequest
v1.query = httpRequest.URL.Query()
// Set new query parameters
v1.query.Set("AWSAccessKeyId", credentialsValue.AccessKeyID)
if credentialsValue.SessionToken != "" {
v1.query.Set("SecurityToken", credentialsValue.SessionToken)
}
// in case this is a retry, ensure no signature present
v1.query.Del("Signature")
method := httpRequest.Method
path := httpRequest.URL.Path
if path == "" {
path = "/"
}
duration := int64(v1.request.ExpireTime / time.Second)
expires := strconv.FormatInt(duration, 10)
// build the canonical string for the v1 signature
v1.stringToSign = strings.Join([]string{
method,
"",
"",
expires,
path,
}, "\n")
hash := hmac.New(sha1.New, []byte(credentialsValue.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)
}