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