yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/util.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  // Copyright 2019 Huawei Technologies Co.,Ltd.
    16  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
    17  // this file except in compliance with the License.  You may obtain a copy of the
    18  // License at
    19  //
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  //
    22  // Unless required by applicable law or agreed to in writing, software distributed
    23  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    24  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    25  // specific language governing permissions and limitations under the License.
    26  
    27  package obs
    28  
    29  import (
    30  	"crypto/hmac"
    31  	"crypto/md5"
    32  	"crypto/sha1"
    33  	"crypto/sha256"
    34  	"encoding/base64"
    35  	"encoding/hex"
    36  	"encoding/xml"
    37  	"fmt"
    38  	"net/url"
    39  	"regexp"
    40  	"strconv"
    41  	"strings"
    42  	"time"
    43  )
    44  
    45  var regex = regexp.MustCompile("^[\u4e00-\u9fa5]$")
    46  var ipRegex = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
    47  var v4AuthRegex = regexp.MustCompile("Credential=(.+?),SignedHeaders=(.+?),Signature=.+")
    48  var regionRegex = regexp.MustCompile(".+/\\d+/(.+?)/.+")
    49  
    50  func StringContains(src string, subStr string, subTranscoding string) string {
    51  	return strings.Replace(src, subStr, subTranscoding, -1)
    52  }
    53  func XmlTranscoding(src string) string {
    54  	srcTmp := StringContains(src, "&", "&")
    55  	srcTmp = StringContains(srcTmp, "<", "&lt;")
    56  	srcTmp = StringContains(srcTmp, ">", "&gt;")
    57  	srcTmp = StringContains(srcTmp, "'", "&apos;")
    58  	srcTmp = StringContains(srcTmp, "\"", "&quot;")
    59  	return srcTmp
    60  }
    61  func StringToInt(value string, def int) int {
    62  	ret, err := strconv.Atoi(value)
    63  	if err != nil {
    64  		ret = def
    65  	}
    66  	return ret
    67  }
    68  
    69  func StringToInt64(value string, def int64) int64 {
    70  	ret, err := strconv.ParseInt(value, 10, 64)
    71  	if err != nil {
    72  		ret = def
    73  	}
    74  	return ret
    75  }
    76  
    77  func IntToString(value int) string {
    78  	return strconv.Itoa(value)
    79  }
    80  
    81  func Int64ToString(value int64) string {
    82  	return strconv.FormatInt(value, 10)
    83  }
    84  
    85  func GetCurrentTimestamp() int64 {
    86  	return time.Now().UnixNano() / 1000000
    87  }
    88  
    89  func FormatUtcNow(format string) string {
    90  	return time.Now().UTC().Format(format)
    91  }
    92  
    93  func FormatUtcToRfc1123(t time.Time) string {
    94  	ret := t.UTC().Format(time.RFC1123)
    95  	return ret[:strings.LastIndex(ret, "UTC")] + "GMT"
    96  }
    97  
    98  func Md5(value []byte) []byte {
    99  	m := md5.New()
   100  	_, err := m.Write(value)
   101  	if err != nil {
   102  		doLog(LEVEL_WARN, "MD5 failed to write with reason: %v", err)
   103  	}
   104  	return m.Sum(nil)
   105  }
   106  
   107  func HmacSha1(key, value []byte) []byte {
   108  	mac := hmac.New(sha1.New, key)
   109  	_, err := mac.Write(value)
   110  	if err != nil {
   111  		doLog(LEVEL_WARN, "HmacSha1 failed to write with reason: %v", err)
   112  	}
   113  	return mac.Sum(nil)
   114  }
   115  
   116  func HmacSha256(key, value []byte) []byte {
   117  	mac := hmac.New(sha256.New, key)
   118  	_, err := mac.Write(value)
   119  	if err != nil {
   120  		doLog(LEVEL_WARN, "HmacSha256 failed to write with reason: %v", err)
   121  	}
   122  	return mac.Sum(nil)
   123  }
   124  
   125  func Base64Encode(value []byte) string {
   126  	return base64.StdEncoding.EncodeToString(value)
   127  }
   128  
   129  func Base64Decode(value string) ([]byte, error) {
   130  	return base64.StdEncoding.DecodeString(value)
   131  }
   132  
   133  func HexMd5(value []byte) string {
   134  	return Hex(Md5(value))
   135  }
   136  
   137  func Base64Md5(value []byte) string {
   138  	return Base64Encode(Md5(value))
   139  }
   140  
   141  func Sha256Hash(value []byte) []byte {
   142  	hash := sha256.New()
   143  	_, err := hash.Write(value)
   144  	if err != nil {
   145  		doLog(LEVEL_WARN, "Sha256Hash failed to write with reason: %v", err)
   146  	}
   147  	return hash.Sum(nil)
   148  }
   149  
   150  func ParseXml(value []byte, result interface{}) error {
   151  	if len(value) == 0 {
   152  		return nil
   153  	}
   154  	return xml.Unmarshal(value, result)
   155  }
   156  
   157  func TransToXml(value interface{}) ([]byte, error) {
   158  	if value == nil {
   159  		return []byte{}, nil
   160  	}
   161  	return xml.Marshal(value)
   162  }
   163  
   164  func Hex(value []byte) string {
   165  	return hex.EncodeToString(value)
   166  }
   167  
   168  func HexSha256(value []byte) string {
   169  	return Hex(Sha256Hash(value))
   170  }
   171  
   172  func UrlDecode(value string) (string, error) {
   173  	ret, err := url.QueryUnescape(value)
   174  	if err == nil {
   175  		return ret, nil
   176  	}
   177  	return "", err
   178  }
   179  
   180  func UrlDecodeWithoutError(value string) string {
   181  	ret, err := UrlDecode(value)
   182  	if err == nil {
   183  		return ret
   184  	}
   185  	if isErrorLogEnabled() {
   186  		doLog(LEVEL_ERROR, "Url decode error: %v", err)
   187  	}
   188  	return ""
   189  }
   190  
   191  func IsIP(value string) bool {
   192  	return ipRegex.MatchString(value)
   193  }
   194  
   195  func UrlEncode(value string, chineseOnly bool) string {
   196  	if chineseOnly {
   197  		values := make([]string, 0, len(value))
   198  		for _, val := range value {
   199  			_value := string(val)
   200  			if regex.MatchString(_value) {
   201  				_value = url.QueryEscape(_value)
   202  			}
   203  			values = append(values, _value)
   204  		}
   205  		return strings.Join(values, "")
   206  	}
   207  	return url.QueryEscape(value)
   208  }
   209  
   210  func copyHeaders(m map[string][]string) (ret map[string][]string) {
   211  	if m != nil {
   212  		ret = make(map[string][]string, len(m))
   213  		for key, values := range m {
   214  			_values := make([]string, 0, len(values))
   215  			for _, value := range values {
   216  				_values = append(_values, value)
   217  			}
   218  			ret[strings.ToLower(key)] = _values
   219  		}
   220  	} else {
   221  		ret = make(map[string][]string)
   222  	}
   223  
   224  	return
   225  }
   226  
   227  func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) {
   228  	signature = "v2"
   229  	if receviedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receviedAuthorization) > 0 {
   230  		if strings.HasPrefix(receviedAuthorization[0], V4_HASH_PREFIX) {
   231  			signature = "v4"
   232  			matches := v4AuthRegex.FindStringSubmatch(receviedAuthorization[0])
   233  			if len(matches) >= 3 {
   234  				region = matches[1]
   235  				regions := regionRegex.FindStringSubmatch(region)
   236  				if len(regions) >= 2 {
   237  					region = regions[1]
   238  				}
   239  				signedHeaders = matches[2]
   240  			}
   241  
   242  		} else if strings.HasPrefix(receviedAuthorization[0], V2_HASH_PREFIX) {
   243  			signature = "v2"
   244  		}
   245  	}
   246  	return
   247  }
   248  
   249  func getTemporaryKeys() []string {
   250  	return []string{
   251  		"Signature",
   252  		"signature",
   253  		"X-Amz-Signature",
   254  		"x-amz-signature",
   255  	}
   256  }
   257  
   258  func getIsObs(isTemporary bool, querys []string, headers map[string][]string) bool {
   259  	isObs := true
   260  	if isTemporary {
   261  		for _, value := range querys {
   262  			keyPrefix := strings.ToLower(value)
   263  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   264  				isObs = false
   265  			} else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) {
   266  				isObs = false
   267  			}
   268  		}
   269  	} else {
   270  		for key := range headers {
   271  			keyPrefix := strings.ToLower(key)
   272  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   273  				isObs = false
   274  				break
   275  			}
   276  		}
   277  	}
   278  	return isObs
   279  }
   280  
   281  func isPathStyle(headers map[string][]string, bucketName string) bool {
   282  	if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
   283  		return true
   284  	}
   285  	return false
   286  }
   287  
   288  func GetV2Authorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
   289  
   290  	if strings.HasPrefix(queryUrl, "?") {
   291  		queryUrl = queryUrl[1:]
   292  	}
   293  
   294  	method = strings.ToUpper(method)
   295  
   296  	querys := strings.Split(queryUrl, "&")
   297  	querysResult := make([]string, 0)
   298  	for _, value := range querys {
   299  		if value != "=" && len(value) != 0 {
   300  			querysResult = append(querysResult, value)
   301  		}
   302  	}
   303  	params := make(map[string]string)
   304  
   305  	for _, value := range querysResult {
   306  		kv := strings.Split(value, "=")
   307  		length := len(kv)
   308  		if length == 1 {
   309  			key := UrlDecodeWithoutError(kv[0])
   310  			params[key] = ""
   311  		} else if length >= 2 {
   312  			key := UrlDecodeWithoutError(kv[0])
   313  			vals := make([]string, 0, length-1)
   314  			for i := 1; i < length; i++ {
   315  				val := UrlDecodeWithoutError(kv[i])
   316  				vals = append(vals, val)
   317  			}
   318  			params[key] = strings.Join(vals, "=")
   319  		}
   320  	}
   321  	headers = copyHeaders(headers)
   322  	pathStyle := isPathStyle(headers, bucketName)
   323  	conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
   324  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   325  		pathStyle: pathStyle}
   326  	conf.signature = SignatureObs
   327  	_, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   328  	ret = v2Auth(ak, sk, method, canonicalizedURL, headers, true)
   329  	v2HashPrefix := OBS_HASH_PREFIX
   330  	ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   331  	return
   332  }
   333  
   334  func GetAuthorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
   335  
   336  	if strings.HasPrefix(queryUrl, "?") {
   337  		queryUrl = queryUrl[1:]
   338  	}
   339  
   340  	method = strings.ToUpper(method)
   341  
   342  	querys := strings.Split(queryUrl, "&")
   343  	querysResult := make([]string, 0)
   344  	for _, value := range querys {
   345  		if value != "=" && len(value) != 0 {
   346  			querysResult = append(querysResult, value)
   347  		}
   348  	}
   349  	params := make(map[string]string)
   350  
   351  	for _, value := range querysResult {
   352  		kv := strings.Split(value, "=")
   353  		length := len(kv)
   354  		if length == 1 {
   355  			key := UrlDecodeWithoutError(kv[0])
   356  			params[key] = ""
   357  		} else if length >= 2 {
   358  			key := UrlDecodeWithoutError(kv[0])
   359  			vals := make([]string, 0, length-1)
   360  			for i := 1; i < length; i++ {
   361  				val := UrlDecodeWithoutError(kv[i])
   362  				vals = append(vals, val)
   363  			}
   364  			params[key] = strings.Join(vals, "=")
   365  		}
   366  	}
   367  	isTemporary := false
   368  	signature := "v2"
   369  	temporaryKeys := getTemporaryKeys()
   370  	for _, key := range temporaryKeys {
   371  		if _, ok := params[key]; ok {
   372  			isTemporary = true
   373  			if strings.ToLower(key) == "signature" {
   374  				signature = "v2"
   375  			} else if strings.ToLower(key) == "x-amz-signature" {
   376  				signature = "v4"
   377  			}
   378  			break
   379  		}
   380  	}
   381  	isObs := getIsObs(isTemporary, querysResult, headers)
   382  	headers = copyHeaders(headers)
   383  	pathStyle := false
   384  	if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
   385  		pathStyle = true
   386  	}
   387  	conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
   388  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   389  		pathStyle: pathStyle}
   390  
   391  	if isTemporary {
   392  		return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs)
   393  	}
   394  	signature, region, signedHeaders := parseHeaders(headers)
   395  	if signature == "v4" {
   396  		conf.signature = SignatureV4
   397  		requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
   398  		parsedRequestUrl, _err := url.Parse(requestUrl)
   399  		if _err != nil {
   400  			doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err)
   401  			return nil
   402  		}
   403  		headerKeys := strings.Split(signedHeaders, ";")
   404  		_headers := make(map[string][]string, len(headerKeys))
   405  		for _, headerKey := range headerKeys {
   406  			_headers[headerKey] = headers[headerKey]
   407  		}
   408  		ret = v4Auth(ak, sk, region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, _headers)
   409  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   410  	} else if signature == "v2" {
   411  		if isObs {
   412  			conf.signature = SignatureObs
   413  		} else {
   414  			conf.signature = SignatureV2
   415  		}
   416  		_, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
   417  		ret = v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs)
   418  		v2HashPrefix := V2_HASH_PREFIX
   419  		if isObs {
   420  			v2HashPrefix = OBS_HASH_PREFIX
   421  		}
   422  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   423  	}
   424  	return
   425  
   426  }
   427  
   428  func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string,
   429  	headers map[string][]string, isObs bool) (ret map[string]string) {
   430  
   431  	if signature == "v4" {
   432  		conf.signature = SignatureV4
   433  
   434  		longDate, ok := params[PARAM_DATE_AMZ_CAMEL]
   435  		if !ok {
   436  			longDate = params[HEADER_DATE_AMZ]
   437  		}
   438  		shortDate := longDate[:8]
   439  
   440  		credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL]
   441  		if !ok {
   442  			credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)]
   443  		}
   444  
   445  		_credential := UrlDecodeWithoutError(credential)
   446  
   447  		regions := regionRegex.FindStringSubmatch(_credential)
   448  		var region string
   449  		if len(regions) >= 2 {
   450  			region = regions[1]
   451  		}
   452  
   453  		_, scope := getCredential(ak, region, shortDate)
   454  
   455  		expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL]
   456  		if !ok {
   457  			expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)]
   458  		}
   459  
   460  		signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL]
   461  		if !ok {
   462  			signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)]
   463  		}
   464  
   465  		algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL]
   466  		if !ok {
   467  			algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)]
   468  		}
   469  
   470  		if _, ok := params[PARAM_SIGNATURE_AMZ_CAMEL]; ok {
   471  			delete(params, PARAM_SIGNATURE_AMZ_CAMEL)
   472  		} else if _, ok := params[strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)]; ok {
   473  			delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL))
   474  		}
   475  
   476  		ret = make(map[string]string, 6)
   477  		ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm
   478  		ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
   479  		ret[PARAM_DATE_AMZ_CAMEL] = longDate
   480  		ret[PARAM_EXPIRES_AMZ_CAMEL] = expires
   481  		ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders
   482  
   483  		requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
   484  		parsedRequestUrl, _err := url.Parse(requestUrl)
   485  		if _err != nil {
   486  			doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err)
   487  			return nil
   488  		}
   489  		stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers)
   490  		ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false)
   491  	} else if signature == "v2" {
   492  		if isObs {
   493  			conf.signature = SignatureObs
   494  		} else {
   495  			conf.signature = SignatureV2
   496  		}
   497  		_, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
   498  		expires, ok := params["Expires"]
   499  		if !ok {
   500  			expires = params["expires"]
   501  		}
   502  		headers[HEADER_DATE_CAMEL] = []string{expires}
   503  		stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, isObs)
   504  		ret = make(map[string]string, 3)
   505  		ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false)
   506  		ret["AWSAccessKeyId"] = UrlEncode(ak, false)
   507  		ret["Expires"] = UrlEncode(expires, false)
   508  	}
   509  
   510  	return
   511  }