yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/conf.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 "context" 31 "crypto/tls" 32 "crypto/x509" 33 "errors" 34 "fmt" 35 "net" 36 "net/http" 37 "net/url" 38 "sort" 39 "strconv" 40 "strings" 41 "time" 42 ) 43 44 type securityProvider struct { 45 ak string 46 sk string 47 securityToken string 48 } 49 50 type urlHolder struct { 51 scheme string 52 host string 53 port int 54 } 55 56 type config struct { 57 securityProvider *securityProvider 58 urlHolder *urlHolder 59 pathStyle bool 60 cname bool 61 sslVerify bool 62 endpoint string 63 signature SignatureType 64 region string 65 connectTimeout int 66 socketTimeout int 67 headerTimeout int 68 idleConnTimeout int 69 finalTimeout int 70 maxRetryCount int 71 proxyUrl string 72 maxConnsPerHost int 73 pemCerts []byte 74 transport *http.Transport 75 ctx context.Context 76 maxRedirectCount int 77 } 78 79 func (conf config) String() string { 80 return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+ 81 "\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+ 82 "\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, proxyUrl:%s, maxRedirectCount:%d]", 83 conf.endpoint, conf.signature, conf.pathStyle, conf.region, 84 conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout, 85 conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.proxyUrl, conf.maxRedirectCount, 86 ) 87 } 88 89 type configurer func(conf *config) 90 91 func WithSslVerify(sslVerify bool) configurer { 92 return WithSslVerifyAndPemCerts(sslVerify, nil) 93 } 94 95 func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer { 96 return func(conf *config) { 97 conf.sslVerify = sslVerify 98 conf.pemCerts = pemCerts 99 } 100 } 101 102 func WithHeaderTimeout(headerTimeout int) configurer { 103 return func(conf *config) { 104 conf.headerTimeout = headerTimeout 105 } 106 } 107 108 func WithProxyUrl(proxyUrl string) configurer { 109 return func(conf *config) { 110 conf.proxyUrl = proxyUrl 111 } 112 } 113 114 func WithMaxConnections(maxConnsPerHost int) configurer { 115 return func(conf *config) { 116 conf.maxConnsPerHost = maxConnsPerHost 117 } 118 } 119 120 func WithPathStyle(pathStyle bool) configurer { 121 return func(conf *config) { 122 conf.pathStyle = pathStyle 123 } 124 } 125 126 func WithSignature(signature SignatureType) configurer { 127 return func(conf *config) { 128 conf.signature = signature 129 } 130 } 131 132 func WithRegion(region string) configurer { 133 return func(conf *config) { 134 conf.region = region 135 } 136 } 137 138 func WithConnectTimeout(connectTimeout int) configurer { 139 return func(conf *config) { 140 conf.connectTimeout = connectTimeout 141 } 142 } 143 144 func WithSocketTimeout(socketTimeout int) configurer { 145 return func(conf *config) { 146 conf.socketTimeout = socketTimeout 147 } 148 } 149 150 func WithIdleConnTimeout(idleConnTimeout int) configurer { 151 return func(conf *config) { 152 conf.idleConnTimeout = idleConnTimeout 153 } 154 } 155 156 func WithMaxRetryCount(maxRetryCount int) configurer { 157 return func(conf *config) { 158 conf.maxRetryCount = maxRetryCount 159 } 160 } 161 162 func WithSecurityToken(securityToken string) configurer { 163 return func(conf *config) { 164 conf.securityProvider.securityToken = securityToken 165 } 166 } 167 168 func WithHttpTransport(transport *http.Transport) configurer { 169 return func(conf *config) { 170 conf.transport = transport 171 } 172 } 173 174 func WithRequestContext(ctx context.Context) configurer { 175 return func(conf *config) { 176 conf.ctx = ctx 177 } 178 } 179 180 func WithCustomDomainName(cname bool) configurer { 181 return func(conf *config) { 182 conf.cname = cname 183 } 184 } 185 186 func WithMaxRedirectCount(maxRedirectCount int) configurer { 187 return func(conf *config) { 188 conf.maxRedirectCount = maxRedirectCount 189 } 190 } 191 192 func (conf *config) initConfigWithDefault() error { 193 conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak) 194 conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk) 195 conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken) 196 conf.endpoint = strings.TrimSpace(conf.endpoint) 197 if conf.endpoint == "" { 198 return errors.New("endpoint is not set") 199 } 200 201 if index := strings.Index(conf.endpoint, "?"); index > 0 { 202 conf.endpoint = conf.endpoint[:index] 203 } 204 205 for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 { 206 conf.endpoint = conf.endpoint[:len(conf.endpoint)-1] 207 } 208 209 if conf.signature == "" { 210 conf.signature = DEFAULT_SIGNATURE 211 } 212 213 urlHolder := &urlHolder{} 214 var address string 215 if strings.HasPrefix(conf.endpoint, "https://") { 216 urlHolder.scheme = "https" 217 address = conf.endpoint[len("https://"):] 218 } else if strings.HasPrefix(conf.endpoint, "http://") { 219 urlHolder.scheme = "http" 220 address = conf.endpoint[len("http://"):] 221 } else { 222 urlHolder.scheme = "https" 223 address = conf.endpoint 224 } 225 226 addr := strings.Split(address, ":") 227 if len(addr) == 2 { 228 if port, err := strconv.Atoi(addr[1]); err == nil { 229 urlHolder.port = port 230 } 231 } 232 urlHolder.host = addr[0] 233 if urlHolder.port == 0 { 234 if urlHolder.scheme == "https" { 235 urlHolder.port = 443 236 } else { 237 urlHolder.port = 80 238 } 239 } 240 241 if IsIP(urlHolder.host) { 242 conf.pathStyle = true 243 } 244 245 conf.urlHolder = urlHolder 246 247 conf.region = strings.TrimSpace(conf.region) 248 if conf.region == "" { 249 conf.region = DEFAULT_REGION 250 } 251 252 if conf.connectTimeout <= 0 { 253 conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT 254 } 255 256 if conf.socketTimeout <= 0 { 257 conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT 258 } 259 260 conf.finalTimeout = conf.socketTimeout * 10 261 262 if conf.headerTimeout <= 0 { 263 conf.headerTimeout = DEFAULT_HEADER_TIMEOUT 264 } 265 266 if conf.idleConnTimeout < 0 { 267 conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT 268 } 269 270 if conf.maxRetryCount < 0 { 271 conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT 272 } 273 274 if conf.maxConnsPerHost <= 0 { 275 conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST 276 } 277 278 if conf.maxRedirectCount < 0 { 279 conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT 280 } 281 282 conf.proxyUrl = strings.TrimSpace(conf.proxyUrl) 283 return nil 284 } 285 286 func (conf *config) getTransport() error { 287 if conf.transport == nil { 288 conf.transport = &http.Transport{ 289 Dial: func(network, addr string) (net.Conn, error) { 290 conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout)) 291 if err != nil { 292 return nil, err 293 } 294 return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil 295 }, 296 MaxIdleConns: conf.maxConnsPerHost, 297 MaxIdleConnsPerHost: conf.maxConnsPerHost, 298 ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout), 299 IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout), 300 } 301 302 if conf.proxyUrl != "" { 303 proxyUrl, err := url.Parse(conf.proxyUrl) 304 if err != nil { 305 return err 306 } 307 conf.transport.Proxy = http.ProxyURL(proxyUrl) 308 } 309 310 tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify} 311 if conf.sslVerify && conf.pemCerts != nil { 312 pool := x509.NewCertPool() 313 pool.AppendCertsFromPEM(conf.pemCerts) 314 tlsConfig.RootCAs = pool 315 } 316 317 conf.transport.TLSClientConfig = tlsConfig 318 } 319 320 return nil 321 } 322 323 func checkRedirectFunc(req *http.Request, via []*http.Request) error { 324 return http.ErrUseLastResponse 325 } 326 327 func DummyQueryEscape(s string) string { 328 return s 329 } 330 331 func (conf *config) requestUrl(bucket string) string { 332 urlHolder := conf.urlHolder 333 requestUrl := fmt.Sprintf("%s://%s", urlHolder.scheme, urlHolder.host) 334 if len(bucket) > 0 { 335 requestUrl = fmt.Sprintf("%s://%s.%s", urlHolder.scheme, bucket, urlHolder.host) 336 } 337 if urlHolder.scheme == "https" && urlHolder.port != 443 { 338 requestUrl += fmt.Sprintf(":%d", urlHolder.port) 339 } 340 if urlHolder.scheme == "http" && urlHolder.port != 80 { 341 requestUrl += fmt.Sprintf(":%d", urlHolder.port) 342 } 343 return requestUrl 344 } 345 346 func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestUrl string, canonicalizedUrl string) { 347 urlHolder := conf.urlHolder 348 if conf.cname { 349 requestUrl = conf.requestUrl("") 350 if conf.signature == "v4" { 351 canonicalizedUrl = "/" 352 } else { 353 canonicalizedUrl = "/" + urlHolder.host + "/" 354 } 355 } else { 356 if bucketName == "" { 357 requestUrl = conf.requestUrl("") 358 canonicalizedUrl = "/" 359 } else { 360 if conf.pathStyle { 361 requestUrl = fmt.Sprintf("%s/%s", conf.requestUrl(""), bucketName) 362 canonicalizedUrl = "/" + bucketName 363 } else { 364 requestUrl = conf.requestUrl(bucketName) 365 if conf.signature == "v2" || conf.signature == "OBS" { 366 canonicalizedUrl = "/" + bucketName + "/" 367 } else { 368 canonicalizedUrl = "/" 369 } 370 } 371 } 372 } 373 var escapeFunc func(s string) string 374 if escape { 375 escapeFunc = url.QueryEscape 376 } else { 377 escapeFunc = DummyQueryEscape 378 } 379 380 if objectKey != "" { 381 var encodeObjectKey string 382 if escape { 383 tempKey := []rune(objectKey) 384 result := make([]string, 0, len(tempKey)) 385 for _, value := range tempKey { 386 if string(value) == "/" { 387 result = append(result, string(value)) 388 } else { 389 result = append(result, url.QueryEscape(string(value))) 390 } 391 } 392 encodeObjectKey = strings.Join(result, "") 393 } else { 394 encodeObjectKey = escapeFunc(objectKey) 395 } 396 requestUrl += "/" + encodeObjectKey 397 if !strings.HasSuffix(canonicalizedUrl, "/") { 398 canonicalizedUrl += "/" 399 } 400 canonicalizedUrl += encodeObjectKey 401 } 402 403 keys := make([]string, 0, len(params)) 404 for key := range params { 405 keys = append(keys, strings.TrimSpace(key)) 406 } 407 sort.Strings(keys) 408 i := 0 409 410 for index, key := range keys { 411 if index == 0 { 412 requestUrl += "?" 413 } else { 414 requestUrl += "&" 415 } 416 _key := url.QueryEscape(key) 417 requestUrl += _key 418 419 _value := params[key] 420 if conf.signature == "v4" { 421 requestUrl += "=" + url.QueryEscape(_value) 422 } else { 423 if _value != "" { 424 requestUrl += "=" + url.QueryEscape(_value) 425 _value = "=" + _value 426 } else { 427 _value = "" 428 } 429 lowerKey := strings.ToLower(key) 430 _, ok := allowed_resource_parameter_names[lowerKey] 431 prefixHeader := HEADER_PREFIX 432 isObs := conf.signature == SignatureObs 433 if isObs { 434 prefixHeader = HEADER_PREFIX_OBS 435 } 436 ok = ok || strings.HasPrefix(lowerKey, prefixHeader) 437 if ok { 438 if i == 0 { 439 canonicalizedUrl += "?" 440 } else { 441 canonicalizedUrl += "&" 442 } 443 canonicalizedUrl += getQueryUrl(_key, _value) 444 i++ 445 } 446 } 447 } 448 return 449 } 450 451 func getQueryUrl(key, value string) string { 452 queryUrl := "" 453 queryUrl += key 454 queryUrl += value 455 return queryUrl 456 }