yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/auth.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  	"fmt"
    31  	"net/url"
    32  	"sort"
    33  	"strings"
    34  	"time"
    35  )
    36  
    37  func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, params map[string]string,
    38  	headers map[string][]string, expires int64) (requestUrl string, err error) {
    39  	isAkSkEmpty := obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == ""
    40  	if isAkSkEmpty == false && obsClient.conf.securityProvider.securityToken != "" {
    41  		if obsClient.conf.signature == SignatureObs {
    42  			params[HEADER_STS_TOKEN_OBS] = obsClient.conf.securityProvider.securityToken
    43  		} else {
    44  			params[HEADER_STS_TOKEN_AMZ] = obsClient.conf.securityProvider.securityToken
    45  		}
    46  	}
    47  	requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    48  	parsedRequestUrl, err := url.Parse(requestUrl)
    49  	if err != nil {
    50  		return "", err
    51  	}
    52  	encodeHeaders(headers)
    53  	hostName := parsedRequestUrl.Host
    54  
    55  	isV4 := obsClient.conf.signature == SignatureV4
    56  	prepareHostAndDate(headers, hostName, isV4)
    57  
    58  	if isAkSkEmpty {
    59  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
    60  	} else {
    61  		if isV4 {
    62  			date, parseDateErr := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0])
    63  			if parseDateErr != nil {
    64  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
    65  				return "", parseDateErr
    66  			}
    67  			delete(headers, HEADER_DATE_CAMEL)
    68  			shortDate := date.Format(SHORT_DATE_FORMAT)
    69  			longDate := date.Format(LONG_DATE_FORMAT)
    70  			if len(headers[HEADER_HOST_CAMEL]) != 0 {
    71  				index := strings.LastIndex(headers[HEADER_HOST_CAMEL][0], ":")
    72  				if index != -1 {
    73  					port := headers[HEADER_HOST_CAMEL][0][index+1:]
    74  					if port == "80" || port == "443" {
    75  						headers[HEADER_HOST_CAMEL] = []string{headers[HEADER_HOST_CAMEL][0][:index]}
    76  					}
    77  				}
    78  
    79  			}
    80  
    81  			signedHeaders, _headers := getSignedHeaders(headers)
    82  
    83  			credential, scope := getCredential(obsClient.conf.securityProvider.ak, obsClient.conf.region, shortDate)
    84  			params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
    85  			params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
    86  			params[PARAM_DATE_AMZ_CAMEL] = longDate
    87  			params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires)
    88  			params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";")
    89  
    90  			requestUrl, canonicalizedUrl = obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    91  			parsedRequestUrl, _err := url.Parse(requestUrl)
    92  			if _err != nil {
    93  				doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err)
    94  				return "", _err
    95  			}
    96  
    97  			stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers)
    98  			signature := getSignature(stringToSign, obsClient.conf.securityProvider.sk, obsClient.conf.region, shortDate)
    99  
   100  			requestUrl += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false))
   101  
   102  		} else {
   103  			originDate := headers[HEADER_DATE_CAMEL][0]
   104  			date, parseDateErr := time.Parse(RFC1123_FORMAT, originDate)
   105  			if parseDateErr != nil {
   106  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
   107  				return "", parseDateErr
   108  			}
   109  			expires += date.Unix()
   110  			headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)}
   111  
   112  			stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, obsClient.conf.signature == SignatureObs)
   113  			signature := UrlEncode(Base64Encode(HmacSha1([]byte(obsClient.conf.securityProvider.sk), []byte(stringToSign))), false)
   114  			if strings.Index(requestUrl, "?") < 0 {
   115  				requestUrl += "?"
   116  			} else {
   117  				requestUrl += "&"
   118  			}
   119  			delete(headers, HEADER_DATE_CAMEL)
   120  
   121  			if obsClient.conf.signature != SignatureObs {
   122  				requestUrl += "AWS"
   123  			}
   124  			requestUrl += fmt.Sprintf("AccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(obsClient.conf.securityProvider.ak, false), expires, signature)
   125  		}
   126  	}
   127  
   128  	return
   129  }
   130  
   131  func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string,
   132  	headers map[string][]string, hostName string) (requestUrl string, err error) {
   133  	isAkSkEmpty := obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == ""
   134  	if isAkSkEmpty == false && obsClient.conf.securityProvider.securityToken != "" {
   135  		if obsClient.conf.signature == SignatureObs {
   136  			headers[HEADER_STS_TOKEN_OBS] = []string{obsClient.conf.securityProvider.securityToken}
   137  		} else {
   138  			headers[HEADER_STS_TOKEN_AMZ] = []string{obsClient.conf.securityProvider.securityToken}
   139  		}
   140  	}
   141  	isObs := obsClient.conf.signature == SignatureObs
   142  	requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
   143  	parsedRequestUrl, err := url.Parse(requestUrl)
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  	encodeHeaders(headers)
   148  
   149  	if hostName == "" {
   150  		hostName = parsedRequestUrl.Host
   151  	}
   152  
   153  	isV4 := obsClient.conf.signature == SignatureV4
   154  	prepareHostAndDate(headers, hostName, isV4)
   155  
   156  	if isAkSkEmpty {
   157  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
   158  	} else {
   159  		ak := obsClient.conf.securityProvider.ak
   160  		sk := obsClient.conf.securityProvider.sk
   161  		var authorization string
   162  		if isV4 {
   163  			headers[HEADER_CONTENT_SHA256_AMZ] = []string{EMPTY_CONTENT_SHA256}
   164  			ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, headers)
   165  			authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   166  		} else {
   167  			ret := v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs)
   168  			hashPrefix := V2_HASH_PREFIX
   169  			if isObs {
   170  				hashPrefix = OBS_HASH_PREFIX
   171  			}
   172  			authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"])
   173  		}
   174  		headers[HEADER_AUTH_CAMEL] = []string{authorization}
   175  	}
   176  	return
   177  }
   178  
   179  func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) {
   180  	headers[HEADER_HOST_CAMEL] = []string{hostName}
   181  	if date, ok := headers[HEADER_DATE_AMZ]; ok {
   182  		flag := false
   183  		if len(date) == 1 {
   184  			if isV4 {
   185  				if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil {
   186  					headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)}
   187  					flag = true
   188  				}
   189  			} else {
   190  				if strings.HasSuffix(date[0], "GMT") {
   191  					headers[HEADER_DATE_CAMEL] = []string{date[0]}
   192  					flag = true
   193  				}
   194  			}
   195  		}
   196  		if !flag {
   197  			delete(headers, HEADER_DATE_AMZ)
   198  		}
   199  	}
   200  	if _, ok := headers[HEADER_DATE_CAMEL]; !ok {
   201  		headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())}
   202  	}
   203  }
   204  
   205  func encodeHeaders(headers map[string][]string) {
   206  	for key, values := range headers {
   207  		for index, value := range values {
   208  			values[index] = UrlEncode(value, true)
   209  		}
   210  		headers[key] = values
   211  	}
   212  }
   213  
   214  func attachHeaders(headers map[string][]string, isObs bool) string {
   215  	length := len(headers)
   216  	_headers := make(map[string][]string, length)
   217  	keys := make([]string, 0, length)
   218  
   219  	for key, value := range headers {
   220  		_key := strings.ToLower(strings.TrimSpace(key))
   221  		if _key != "" {
   222  			prefixheader := HEADER_PREFIX
   223  			if isObs {
   224  				prefixheader = HEADER_PREFIX_OBS
   225  			}
   226  			if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) {
   227  				keys = append(keys, _key)
   228  				_headers[_key] = value
   229  			}
   230  		} else {
   231  			delete(headers, key)
   232  		}
   233  	}
   234  
   235  	for _, interestedHeader := range interested_headers {
   236  		if _, ok := _headers[interestedHeader]; !ok {
   237  			_headers[interestedHeader] = []string{""}
   238  			keys = append(keys, interestedHeader)
   239  		}
   240  	}
   241  	dateCamelHeader := PARAM_DATE_AMZ_CAMEL
   242  	dataHeader := HEADER_DATE_AMZ
   243  	if isObs {
   244  		dateCamelHeader = PARAM_DATE_OBS_CAMEL
   245  		dataHeader = HEADER_DATE_OBS
   246  	}
   247  	if _, ok := _headers[HEADER_DATE_CAMEL]; ok {
   248  		if _, ok := _headers[dataHeader]; ok {
   249  			_headers[HEADER_DATE_CAMEL] = []string{""}
   250  		} else if _, ok := headers[dateCamelHeader]; ok {
   251  			_headers[HEADER_DATE_CAMEL] = []string{""}
   252  		}
   253  	} else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
   254  		if _, ok := _headers[dataHeader]; ok {
   255  			_headers[HEADER_DATE_CAMEL] = []string{""}
   256  		} else if _, ok := headers[dateCamelHeader]; ok {
   257  			_headers[HEADER_DATE_CAMEL] = []string{""}
   258  		}
   259  	}
   260  
   261  	sort.Strings(keys)
   262  
   263  	stringToSign := make([]string, 0, len(keys))
   264  	for _, key := range keys {
   265  		var value string
   266  		prefixHeader := HEADER_PREFIX
   267  		prefixMetaHeader := HEADER_PREFIX_META
   268  		if isObs {
   269  			prefixHeader = HEADER_PREFIX_OBS
   270  			prefixMetaHeader = HEADER_PREFIX_META_OBS
   271  		}
   272  		if strings.HasPrefix(key, prefixHeader) {
   273  			if strings.HasPrefix(key, prefixMetaHeader) {
   274  				for index, v := range _headers[key] {
   275  					value += strings.TrimSpace(v)
   276  					if index != len(_headers[key])-1 {
   277  						value += ","
   278  					}
   279  				}
   280  			} else {
   281  				value = strings.Join(_headers[key], ",")
   282  			}
   283  			value = fmt.Sprintf("%s:%s", key, value)
   284  		} else {
   285  			value = strings.Join(_headers[key], ",")
   286  		}
   287  		stringToSign = append(stringToSign, value)
   288  	}
   289  	return strings.Join(stringToSign, "\n")
   290  }
   291  
   292  func getV2StringToSign(method, canonicalizedUrl string, headers map[string][]string, isObs bool) string {
   293  	stringToSign := strings.Join([]string{method, "\n", attachHeaders(headers, isObs), "\n", canonicalizedUrl}, "")
   294  	doLog(LEVEL_DEBUG, "The v2 auth stringToSign:\n%s", stringToSign)
   295  	return stringToSign
   296  }
   297  
   298  func v2Auth(ak, sk, method, canonicalizedUrl string, headers map[string][]string, isObs bool) map[string]string {
   299  	stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, isObs)
   300  	return map[string]string{"Signature": Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign)))}
   301  }
   302  
   303  func getScope(region, shortDate string) string {
   304  	return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX)
   305  }
   306  
   307  func getCredential(ak, region, shortDate string) (string, string) {
   308  	scope := getScope(region, shortDate)
   309  	return fmt.Sprintf("%s/%s", ak, scope), scope
   310  }
   311  
   312  func getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload string, signedHeaders []string, headers map[string][]string) string {
   313  	canonicalRequest := make([]string, 0, 10+len(signedHeaders)*4)
   314  	canonicalRequest = append(canonicalRequest, method)
   315  	canonicalRequest = append(canonicalRequest, "\n")
   316  	canonicalRequest = append(canonicalRequest, canonicalizedUrl)
   317  	canonicalRequest = append(canonicalRequest, "\n")
   318  	canonicalRequest = append(canonicalRequest, queryUrl)
   319  	canonicalRequest = append(canonicalRequest, "\n")
   320  
   321  	for _, signedHeader := range signedHeaders {
   322  		values, _ := headers[signedHeader]
   323  		for _, value := range values {
   324  			canonicalRequest = append(canonicalRequest, signedHeader)
   325  			canonicalRequest = append(canonicalRequest, ":")
   326  			canonicalRequest = append(canonicalRequest, value)
   327  			canonicalRequest = append(canonicalRequest, "\n")
   328  		}
   329  	}
   330  	canonicalRequest = append(canonicalRequest, "\n")
   331  	canonicalRequest = append(canonicalRequest, strings.Join(signedHeaders, ";"))
   332  	canonicalRequest = append(canonicalRequest, "\n")
   333  	canonicalRequest = append(canonicalRequest, payload)
   334  
   335  	_canonicalRequest := strings.Join(canonicalRequest, "")
   336  
   337  	doLog(LEVEL_DEBUG, "The v4 auth canonicalRequest:\n%s", _canonicalRequest)
   338  
   339  	stringToSign := make([]string, 0, 7)
   340  	stringToSign = append(stringToSign, V4_HASH_PREFIX)
   341  	stringToSign = append(stringToSign, "\n")
   342  	stringToSign = append(stringToSign, longDate)
   343  	stringToSign = append(stringToSign, "\n")
   344  	stringToSign = append(stringToSign, scope)
   345  	stringToSign = append(stringToSign, "\n")
   346  	stringToSign = append(stringToSign, HexSha256([]byte(_canonicalRequest)))
   347  
   348  	_stringToSign := strings.Join(stringToSign, "")
   349  
   350  	doLog(LEVEL_DEBUG, "The v4 auth stringToSign:\n%s", _stringToSign)
   351  	return _stringToSign
   352  }
   353  
   354  func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) {
   355  	length := len(headers)
   356  	_headers := make(map[string][]string, length)
   357  	signedHeaders := make([]string, 0, length)
   358  	for key, value := range headers {
   359  		_key := strings.ToLower(strings.TrimSpace(key))
   360  		if _key != "" {
   361  			signedHeaders = append(signedHeaders, _key)
   362  			_headers[_key] = value
   363  		} else {
   364  			delete(headers, key)
   365  		}
   366  	}
   367  	sort.Strings(signedHeaders)
   368  	return signedHeaders, _headers
   369  }
   370  
   371  func getSignature(stringToSign, sk, region, shortDate string) string {
   372  	key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate))
   373  	key = HmacSha256(key, []byte(region))
   374  	key = HmacSha256(key, []byte(V4_SERVICE_NAME))
   375  	key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX))
   376  	return Hex(HmacSha256(key, []byte(stringToSign)))
   377  }
   378  
   379  func V4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
   380  	return v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl, headers)
   381  }
   382  
   383  func v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
   384  	var t time.Time
   385  	if val, ok := headers[HEADER_DATE_AMZ]; ok {
   386  		var err error
   387  		t, err = time.Parse(LONG_DATE_FORMAT, val[0])
   388  		if err != nil {
   389  			t = time.Now().UTC()
   390  		}
   391  	} else if val, ok := headers[PARAM_DATE_AMZ_CAMEL]; ok {
   392  		var err error
   393  		t, err = time.Parse(LONG_DATE_FORMAT, val[0])
   394  		if err != nil {
   395  			t = time.Now().UTC()
   396  		}
   397  	} else if val, ok := headers[HEADER_DATE_CAMEL]; ok {
   398  		var err error
   399  		t, err = time.Parse(RFC1123_FORMAT, val[0])
   400  		if err != nil {
   401  			t = time.Now().UTC()
   402  		}
   403  	} else if val, ok := headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
   404  		var err error
   405  		t, err = time.Parse(RFC1123_FORMAT, val[0])
   406  		if err != nil {
   407  			t = time.Now().UTC()
   408  		}
   409  	} else {
   410  		t = time.Now().UTC()
   411  	}
   412  	shortDate := t.Format(SHORT_DATE_FORMAT)
   413  	longDate := t.Format(LONG_DATE_FORMAT)
   414  
   415  	signedHeaders, _headers := getSignedHeaders(headers)
   416  
   417  	credential, scope := getCredential(ak, region, shortDate)
   418  
   419  	payload := EMPTY_CONTENT_SHA256
   420  	if val, ok := headers[HEADER_CONTENT_SHA256_AMZ]; ok {
   421  		payload = val[0]
   422  	}
   423  	stringToSign := getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload, signedHeaders, _headers)
   424  
   425  	signature := getSignature(stringToSign, sk, region, shortDate)
   426  
   427  	ret := make(map[string]string, 3)
   428  	ret["Credential"] = credential
   429  	ret["SignedHeaders"] = strings.Join(signedHeaders, ";")
   430  	ret["Signature"] = signature
   431  	return ret
   432  }