github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/openstack/obs/auth.go (about) 1 // Copyright 2019 Huawei Technologies Co.,Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 // this file except in compliance with the License. You may obtain a copy of the 4 // License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software distributed 9 // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 // specific language governing permissions and limitations under the License. 12 13 package obs 14 15 import ( 16 "fmt" 17 "net/url" 18 "sort" 19 "strings" 20 "time" 21 ) 22 23 func setURLWithPolicy(bucketName, canonicalizedUrl string) string { 24 if strings.HasPrefix(canonicalizedUrl, "/"+bucketName+"/") { 25 canonicalizedUrl = canonicalizedUrl[len("/"+bucketName+"/"):] 26 } else if strings.HasPrefix(canonicalizedUrl, "/"+bucketName) { 27 canonicalizedUrl = canonicalizedUrl[len("/"+bucketName):] 28 } 29 return canonicalizedUrl 30 } 31 32 func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, policy string, params map[string]string, 33 headers map[string][]string, expires int64) (requestURL string, err error) { 34 sh := obsClient.getSecurity() 35 isAkSkEmpty := sh.ak == "" || sh.sk == "" 36 if isAkSkEmpty == false && sh.securityToken != "" { 37 if obsClient.conf.signature == SignatureObs { 38 params[HEADER_STS_TOKEN_OBS] = sh.securityToken 39 } else { 40 params[HEADER_STS_TOKEN_AMZ] = sh.securityToken 41 } 42 } 43 44 if policy != "" { 45 objectKey = "" 46 } 47 48 requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true) 49 parsedRequestURL, err := url.Parse(requestURL) 50 if err != nil { 51 return "", err 52 } 53 encodeHeaders(headers) 54 hostName := parsedRequestURL.Host 55 56 isV4 := obsClient.conf.signature == SignatureV4 57 prepareHostAndDate(headers, hostName, isV4) 58 59 if isAkSkEmpty { 60 doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization") 61 } else { 62 if isV4 { 63 date, parseDateErr := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0]) 64 if parseDateErr != nil { 65 doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr) 66 return "", parseDateErr 67 } 68 delete(headers, HEADER_DATE_CAMEL) 69 shortDate := date.Format(SHORT_DATE_FORMAT) 70 longDate := date.Format(LONG_DATE_FORMAT) 71 if len(headers[HEADER_HOST_CAMEL]) != 0 { 72 index := strings.LastIndex(headers[HEADER_HOST_CAMEL][0], ":") 73 if index != -1 { 74 port := headers[HEADER_HOST_CAMEL][0][index+1:] 75 if port == "80" || port == "443" { 76 headers[HEADER_HOST_CAMEL] = []string{headers[HEADER_HOST_CAMEL][0][:index]} 77 } 78 } 79 80 } 81 82 signedHeaders, _headers := getSignedHeaders(headers) 83 84 credential, scope := getCredential(sh.ak, obsClient.conf.region, shortDate) 85 params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX 86 params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential 87 params[PARAM_DATE_AMZ_CAMEL] = longDate 88 params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires) 89 params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";") 90 91 requestURL, canonicalizedURL = obsClient.conf.formatUrls(bucketName, objectKey, params, true) 92 parsedRequestURL, _err := url.Parse(requestURL) 93 if _err != nil { 94 return "", _err 95 } 96 97 stringToSign := getV4StringToSign(method, canonicalizedURL, parsedRequestURL.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers) 98 signature := getSignature(stringToSign, sh.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 if policy == "" { 111 headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)} 112 } else { 113 policy = Base64Encode([]byte(policy)) 114 headers[HEADER_DATE_CAMEL] = []string{policy} 115 canonicalizedURL = setURLWithPolicy(bucketName, canonicalizedURL) 116 } 117 118 stringToSign := getV2StringToSign(method, canonicalizedURL, headers, obsClient.conf.signature == SignatureObs) 119 signature := UrlEncode(Base64Encode(HmacSha1([]byte(sh.sk), []byte(stringToSign))), false) 120 if strings.Index(requestURL, "?") < 0 { 121 requestURL += "?" 122 } else { 123 requestURL += "&" 124 } 125 delete(headers, HEADER_DATE_CAMEL) 126 127 if obsClient.conf.signature != SignatureObs { 128 requestURL += "AWS" 129 } 130 if policy == "" { 131 requestURL += fmt.Sprintf("AccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(sh.ak, false), 132 expires, signature) 133 return 134 135 } 136 requestURL += fmt.Sprintf("AccessKeyId=%s&Policy=%s&Signature=%s", UrlEncode(sh.ak, false), 137 UrlEncode(policy, false), signature) 138 } 139 } 140 141 return 142 } 143 144 func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string, 145 headers map[string][]string, hostName string) (requestURL string, err error) { 146 sh := obsClient.getSecurity() 147 isAkSkEmpty := sh.ak == "" || sh.sk == "" 148 if isAkSkEmpty == false && sh.securityToken != "" { 149 if obsClient.conf.signature == SignatureObs { 150 headers[HEADER_STS_TOKEN_OBS] = []string{sh.securityToken} 151 } else { 152 headers[HEADER_STS_TOKEN_AMZ] = []string{sh.securityToken} 153 } 154 } 155 isObs := obsClient.conf.signature == SignatureObs 156 requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true) 157 parsedRequestURL, err := url.Parse(requestURL) 158 if err != nil { 159 return "", err 160 } 161 encodeHeaders(headers) 162 163 if hostName == "" { 164 hostName = parsedRequestURL.Host 165 } 166 167 isV4 := obsClient.conf.signature == SignatureV4 168 prepareHostAndDate(headers, hostName, isV4) 169 170 if isAkSkEmpty { 171 doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization") 172 } else { 173 ak := sh.ak 174 sk := sh.sk 175 var authorization string 176 if isV4 { 177 headers[HEADER_CONTENT_SHA256_AMZ] = []string{UNSIGNED_PAYLOAD} 178 ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedURL, parsedRequestURL.RawQuery, headers) 179 authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"]) 180 } else { 181 ret := v2Auth(ak, sk, method, canonicalizedURL, headers, isObs) 182 hashPrefix := V2_HASH_PREFIX 183 if isObs { 184 hashPrefix = OBS_HASH_PREFIX 185 } 186 authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"]) 187 } 188 headers[HEADER_AUTH_CAMEL] = []string{authorization} 189 } 190 return 191 } 192 193 func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) { 194 headers[HEADER_HOST_CAMEL] = []string{hostName} 195 if date, ok := headers[HEADER_DATE_AMZ]; ok { 196 flag := false 197 if len(date) == 1 { 198 if isV4 { 199 if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil { 200 headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)} 201 flag = true 202 } 203 } else { 204 if strings.HasSuffix(date[0], "GMT") { 205 headers[HEADER_DATE_CAMEL] = []string{date[0]} 206 flag = true 207 } 208 } 209 } 210 if !flag { 211 delete(headers, HEADER_DATE_AMZ) 212 } 213 } 214 if _, ok := headers[HEADER_DATE_CAMEL]; !ok { 215 headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())} 216 } 217 218 } 219 220 func encodeHeaders(headers map[string][]string) { 221 for key, values := range headers { 222 for index, value := range values { 223 values[index] = UrlEncode(value, true) 224 } 225 headers[key] = values 226 } 227 } 228 229 func prepareDateHeader(dataHeader, dateCamelHeader string, headers, _headers map[string][]string) { 230 if _, ok := _headers[HEADER_DATE_CAMEL]; ok { 231 if _, ok := _headers[dataHeader]; ok { 232 _headers[HEADER_DATE_CAMEL] = []string{""} 233 } else if _, ok := headers[dateCamelHeader]; ok { 234 _headers[HEADER_DATE_CAMEL] = []string{""} 235 } 236 } else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok { 237 if _, ok := _headers[dataHeader]; ok { 238 _headers[HEADER_DATE_CAMEL] = []string{""} 239 } else if _, ok := headers[dateCamelHeader]; ok { 240 _headers[HEADER_DATE_CAMEL] = []string{""} 241 } 242 } 243 } 244 245 func getStringToSign(keys []string, isObs bool, _headers map[string][]string) []string { 246 stringToSign := make([]string, 0, len(keys)) 247 for _, key := range keys { 248 var value string 249 prefixHeader := HEADER_PREFIX 250 prefixMetaHeader := HEADER_PREFIX_META 251 if isObs { 252 prefixHeader = HEADER_PREFIX_OBS 253 prefixMetaHeader = HEADER_PREFIX_META_OBS 254 } 255 if strings.HasPrefix(key, prefixHeader) { 256 if strings.HasPrefix(key, prefixMetaHeader) { 257 for index, v := range _headers[key] { 258 value += strings.TrimSpace(v) 259 if index != len(_headers[key])-1 { 260 value += "," 261 } 262 } 263 } else { 264 value = strings.Join(_headers[key], ",") 265 } 266 value = fmt.Sprintf("%s:%s", key, value) 267 } else { 268 value = strings.Join(_headers[key], ",") 269 } 270 stringToSign = append(stringToSign, value) 271 } 272 return stringToSign 273 } 274 275 func attachHeaders(headers map[string][]string, isObs bool) string { 276 length := len(headers) 277 _headers := make(map[string][]string, length) 278 keys := make([]string, 0, length) 279 280 for key, value := range headers { 281 _key := strings.ToLower(strings.TrimSpace(key)) 282 if _key != "" { 283 prefixheader := HEADER_PREFIX 284 if isObs { 285 prefixheader = HEADER_PREFIX_OBS 286 } 287 if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) { 288 keys = append(keys, _key) 289 _headers[_key] = value 290 } 291 } else { 292 delete(headers, key) 293 } 294 } 295 296 for _, interestedHeader := range interestedHeaders { 297 if _, ok := _headers[interestedHeader]; !ok { 298 _headers[interestedHeader] = []string{""} 299 keys = append(keys, interestedHeader) 300 } 301 } 302 dateCamelHeader := PARAM_DATE_AMZ_CAMEL 303 dataHeader := HEADER_DATE_AMZ 304 if isObs { 305 dateCamelHeader = PARAM_DATE_OBS_CAMEL 306 dataHeader = HEADER_DATE_OBS 307 } 308 prepareDateHeader(dataHeader, dateCamelHeader, headers, _headers) 309 310 sort.Strings(keys) 311 stringToSign := getStringToSign(keys, isObs, _headers) 312 return strings.Join(stringToSign, "\n") 313 } 314 315 func getScope(region, shortDate string) string { 316 return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX) 317 } 318 319 func getCredential(ak, region, shortDate string) (string, string) { 320 scope := getScope(region, shortDate) 321 return fmt.Sprintf("%s/%s", ak, scope), scope 322 } 323 324 func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) { 325 length := len(headers) 326 _headers := make(map[string][]string, length) 327 signedHeaders := make([]string, 0, length) 328 for key, value := range headers { 329 _key := strings.ToLower(strings.TrimSpace(key)) 330 if _key != "" { 331 signedHeaders = append(signedHeaders, _key) 332 _headers[_key] = value 333 } else { 334 delete(headers, key) 335 } 336 } 337 sort.Strings(signedHeaders) 338 return signedHeaders, _headers 339 } 340 341 func getSignature(stringToSign, sk, region, shortDate string) string { 342 key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate)) 343 key = HmacSha256(key, []byte(region)) 344 key = HmacSha256(key, []byte(V4_SERVICE_NAME)) 345 key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX)) 346 return Hex(HmacSha256(key, []byte(stringToSign))) 347 }