yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/client/auth/signer.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package auth
    16  
    17  import (
    18  	"crypto/md5"
    19  	"crypto/sha256"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"sort"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client/auth/credentials"
    29  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client/auth/signers"
    30  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client/requests"
    31  )
    32  
    33  type Signer interface {
    34  	GetName() string                                 // 签名算法名称
    35  	GetAccessKeyId() (accessKeyId string, err error) // 签名access key
    36  	GetSecretKey() (secretKey string, err error)     // access secret
    37  	Sign(stringToSign, secretSuffix string) string   // 生成签名结果
    38  }
    39  
    40  func NewSignerWithCredential(credential Credential) (signer Signer, err error) {
    41  	switch instance := credential.(type) {
    42  	case *credentials.AccessKeyCredential:
    43  		return signers.NewAccessKeySigner(instance), nil
    44  	default:
    45  		return nil, fmt.Errorf("unsupported credential error")
    46  	}
    47  }
    48  
    49  // 对request进行签名
    50  func Sign(request requests.IRequest, signer Signer) (err error) {
    51  	return signRequest(request, signer)
    52  }
    53  
    54  func signRequest(request requests.IRequest, signer Signer) error {
    55  	// https://support.huaweicloud.com/api-dis/dis_02_0508.html
    56  	// requestTime
    57  	reqTime := time.Now()
    58  	// 添加 必须的Headers
    59  	fillRequiredHeaders(request, reqTime)
    60  	// 计算CanonicalRequest
    61  	canonicalRequest := canonicalRequest(request)
    62  	// stringToSign
    63  	credentialScope := strings.Join([]string{
    64  		formattedSignTime(reqTime, "Date"),
    65  		request.GetRegionId(),
    66  		request.GetProduct(),
    67  		"sdk_request",
    68  	}, "/")
    69  	stringToSign := strings.Join([]string{"SDK-HMAC-SHA256",
    70  		formattedSignTime(reqTime, "DateTime"),
    71  		credentialScope,
    72  		hashSha256([]byte(canonicalRequest)),
    73  	}, "\n")
    74  	// 计算SigningKey
    75  	secret, _ := signer.GetSecretKey()
    76  	signKey := getSigningKey(secret, formattedSignTime(reqTime, "Date"),
    77  		request.GetRegionId(), request.GetProduct())
    78  	// 计算Signature
    79  	signature := signer.Sign(stringToSign, signKey)
    80  	accesskey, _ := signer.GetAccessKeyId()
    81  	addAuthorizationHeader(request, accesskey, credentialScope, signature)
    82  	return nil
    83  }
    84  
    85  // https://support.huaweicloud.com/api-vpc/zh-cn_topic_0132456728.html
    86  // X-Project-Id 如果是专属云场景采用AK/SK 认证方式的接口请求或者多project场景采用AK/SK认证的接口请求则该字段必选。
    87  func fillRequiredHeaders(request requests.IRequest, t time.Time) {
    88  	request.AddHeaderParam("HOST", request.GetHost())
    89  	request.AddHeaderParam("X-Sdk-Date", formattedSignTime(t, "DateTime"))
    90  	if len(request.GetProjectId()) > 0 {
    91  		request.AddHeaderParam("X-Project-Id", request.GetProjectId())
    92  	}
    93  	return
    94  }
    95  
    96  func buildRequestStringToSign(request requests.IRequest) string {
    97  	return ""
    98  }
    99  
   100  func formattedSignTime(t time.Time, format string) string {
   101  	switch format {
   102  	case "Date":
   103  		return t.UTC().Format("20060102")
   104  	case "DateTime":
   105  		return t.UTC().Format("20060102T150405Z")
   106  	default:
   107  		return t.UTC().Format("20060102T150405Z")
   108  	}
   109  }
   110  
   111  func contentSha256(request requests.IRequest) string {
   112  	method := strings.ToUpper(request.GetMethod())
   113  	content := []byte{}
   114  	body := request.GetBodyReader()
   115  	content, _ = ioutil.ReadAll(body)
   116  	if method == "POST" {
   117  		if len(content) == 0 {
   118  			// other http method use query as content
   119  			content = []byte(request.BuildQueries())
   120  		}
   121  	}
   122  
   123  	return hashSha256(content)
   124  }
   125  
   126  func hashSha256(msg []byte) string {
   127  	sh256 := sha256.New()
   128  	sh256.Write(msg)
   129  
   130  	return hex.EncodeToString(sh256.Sum(nil))
   131  }
   132  
   133  func canonicalRequest(request requests.IRequest) string {
   134  	sha256 := contentSha256(request)
   135  	uri := request.GetURI()
   136  	if !strings.HasSuffix(uri, "/") {
   137  		uri = uri + "/"
   138  	}
   139  
   140  	return strings.Join([]string{
   141  		request.GetMethod(),
   142  		uri,
   143  		canonicalQueryString(request),
   144  		canonicalHeaders(request),
   145  		canonicalHeaderNames(request),
   146  		sha256,
   147  	}, "\n")
   148  }
   149  
   150  func sortedHeaderNames(request requests.IRequest) []string {
   151  	headers := request.GetHeaders()
   152  	keys := make([]string, 0)
   153  	for k := range headers {
   154  		keys = append(keys, k)
   155  	}
   156  
   157  	sort.Slice(keys, func(i, j int) bool {
   158  		return strings.ToLower(keys[i]) < strings.ToLower(keys[j])
   159  	})
   160  
   161  	return keys
   162  }
   163  
   164  func canonicalQueryString(request requests.IRequest) string {
   165  	if strings.ToUpper(request.GetMethod()) != "POST" {
   166  		return request.BuildQueries()
   167  	}
   168  
   169  	return ""
   170  }
   171  
   172  func canonicalHeaders(request requests.IRequest) string {
   173  	keys := sortedHeaderNames(request)
   174  	headers := request.GetHeaders()
   175  	ret := []string{}
   176  	for _, k := range keys {
   177  		ret = append(ret, strings.ToLower(k)+":"+strings.TrimSpace(headers[k]))
   178  	}
   179  
   180  	return strings.Join(ret, "\n") + "\n"
   181  }
   182  
   183  func canonicalHeaderNames(request requests.IRequest) string {
   184  	keys := sortedHeaderNames(request)
   185  	ret := strings.Join(keys, ";")
   186  	return strings.ToLower(ret)
   187  }
   188  
   189  var SigningKeyCache sync.Map // map[string][]byte{}
   190  
   191  func getSigningKey(secretKey, date, regionId, service string) string {
   192  	joinedKey := strings.Join([]string{secretKey, date, regionId, service}, "")
   193  	cacheKey := fmt.Sprintf("%x", md5.Sum([]byte(joinedKey)))
   194  	if v, ok := SigningKeyCache.Load(cacheKey); ok {
   195  		return v.(string)
   196  	}
   197  
   198  	ret := []byte("SDK" + secretKey)
   199  	for _, k := range []string{date, regionId, service, "sdk_request"} {
   200  		ret = signers.HmacSha256(k, ret)
   201  	}
   202  
   203  	SigningKeyCache.Store(cacheKey, string(ret))
   204  	return string(ret)
   205  }
   206  
   207  func addAuthorizationHeader(request requests.IRequest, accessKey, credentialScope, signature string) {
   208  	auth := "SDK-HMAC-SHA256" + " " + strings.Join([]string{
   209  		"Credential=" + accessKey + "/" + credentialScope,
   210  		"SignedHeaders=" + canonicalHeaderNames(request),
   211  		"Signature=" + signature,
   212  	}, ", ")
   213  
   214  	request.AddHeaderParam("Authorization", auth)
   215  }