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 }