You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
6.7 KiB
244 lines
6.7 KiB
// Copyright 2019 Huawei Technologies Co.,Ltd.
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
// this file except in compliance with the License. You may obtain a copy of the
|
|
// License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software distributed
|
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations under the License.
|
|
|
|
package obs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
accessKeyEnv = "OBS_ACCESS_KEY_ID"
|
|
securityKeyEnv = "OBS_SECRET_ACCESS_KEY"
|
|
securityTokenEnv = "OBS_SECURITY_TOKEN"
|
|
ecsRequestURL = "http://169.254.169.254/openstack/latest/securitykey"
|
|
)
|
|
|
|
type securityHolder struct {
|
|
ak string
|
|
sk string
|
|
securityToken string
|
|
}
|
|
|
|
var emptySecurityHolder = securityHolder{}
|
|
|
|
type securityProvider interface {
|
|
getSecurity() securityHolder
|
|
}
|
|
|
|
type BasicSecurityProvider struct {
|
|
val atomic.Value
|
|
}
|
|
|
|
func (bsp *BasicSecurityProvider) getSecurity() securityHolder {
|
|
if sh, ok := bsp.val.Load().(securityHolder); ok {
|
|
return sh
|
|
}
|
|
return emptySecurityHolder
|
|
}
|
|
|
|
func (bsp *BasicSecurityProvider) refresh(ak, sk, securityToken string) {
|
|
bsp.val.Store(securityHolder{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)})
|
|
}
|
|
|
|
func NewBasicSecurityProvider(ak, sk, securityToken string) *BasicSecurityProvider {
|
|
bsp := &BasicSecurityProvider{}
|
|
bsp.refresh(ak, sk, securityToken)
|
|
return bsp
|
|
}
|
|
|
|
type EnvSecurityProvider struct {
|
|
sh securityHolder
|
|
suffix string
|
|
once sync.Once
|
|
}
|
|
|
|
func (esp *EnvSecurityProvider) getSecurity() securityHolder {
|
|
//ensure run only once
|
|
esp.once.Do(func() {
|
|
esp.sh = securityHolder{
|
|
ak: strings.TrimSpace(os.Getenv(accessKeyEnv + esp.suffix)),
|
|
sk: strings.TrimSpace(os.Getenv(securityKeyEnv + esp.suffix)),
|
|
securityToken: strings.TrimSpace(os.Getenv(securityTokenEnv + esp.suffix)),
|
|
}
|
|
})
|
|
|
|
return esp.sh
|
|
}
|
|
|
|
func NewEnvSecurityProvider(suffix string) *EnvSecurityProvider {
|
|
if suffix != "" {
|
|
suffix = "_" + suffix
|
|
}
|
|
esp := &EnvSecurityProvider{
|
|
suffix: suffix,
|
|
}
|
|
return esp
|
|
}
|
|
|
|
type TemporarySecurityHolder struct {
|
|
securityHolder
|
|
expireDate time.Time
|
|
}
|
|
|
|
var emptyTemporarySecurityHolder = TemporarySecurityHolder{}
|
|
|
|
type EcsSecurityProvider struct {
|
|
val atomic.Value
|
|
lock sync.Mutex
|
|
httpClient *http.Client
|
|
prefetch int32
|
|
retryCount int
|
|
}
|
|
|
|
func (ecsSp *EcsSecurityProvider) loadTemporarySecurityHolder() (TemporarySecurityHolder, bool) {
|
|
if sh := ecsSp.val.Load(); sh == nil {
|
|
return emptyTemporarySecurityHolder, false
|
|
} else if _sh, ok := sh.(TemporarySecurityHolder); !ok {
|
|
return emptyTemporarySecurityHolder, false
|
|
} else {
|
|
return _sh, true
|
|
}
|
|
}
|
|
|
|
func (ecsSp *EcsSecurityProvider) getAndSetSecurityWithOutLock() securityHolder {
|
|
_sh := TemporarySecurityHolder{}
|
|
_sh.expireDate = time.Now().Add(time.Minute * 5)
|
|
retryCount := 0
|
|
for {
|
|
if req, err := http.NewRequest("GET", ecsRequestURL, nil); err == nil {
|
|
start := GetCurrentTimestamp()
|
|
res, err := ecsSp.httpClient.Do(req)
|
|
if err == nil {
|
|
if data, _err := ioutil.ReadAll(res.Body); _err == nil {
|
|
temp := &struct {
|
|
Credential struct {
|
|
AK string `json:"access,omitempty"`
|
|
SK string `json:"secret,omitempty"`
|
|
SecurityToken string `json:"securitytoken,omitempty"`
|
|
ExpireDate time.Time `json:"expires_at,omitempty"`
|
|
} `json:"credential"`
|
|
}{}
|
|
|
|
doLog(LEVEL_DEBUG, "Get the json data from ecs succeed")
|
|
|
|
if jsonErr := json.Unmarshal(data, temp); jsonErr == nil {
|
|
_sh.ak = temp.Credential.AK
|
|
_sh.sk = temp.Credential.SK
|
|
_sh.securityToken = temp.Credential.SecurityToken
|
|
_sh.expireDate = temp.Credential.ExpireDate.Add(time.Minute * -1)
|
|
|
|
doLog(LEVEL_INFO, "Get security from ecs succeed, AK:xxxx, SK:xxxx, SecurityToken:xxxx, ExprireDate %s", _sh.expireDate)
|
|
|
|
doLog(LEVEL_INFO, "Get security from ecs succeed, cost %d ms", (GetCurrentTimestamp() - start))
|
|
break
|
|
} else {
|
|
err = jsonErr
|
|
}
|
|
} else {
|
|
err = _err
|
|
}
|
|
}
|
|
|
|
doLog(LEVEL_WARN, "Try to get security from ecs failed, cost %d ms, err %s", (GetCurrentTimestamp() - start), err.Error())
|
|
}
|
|
|
|
if retryCount >= ecsSp.retryCount {
|
|
doLog(LEVEL_WARN, "Try to get security from ecs failed and exceed the max retry count")
|
|
break
|
|
}
|
|
sleepTime := float64(retryCount+2) * rand.Float64()
|
|
if sleepTime > 10 {
|
|
sleepTime = 10
|
|
}
|
|
time.Sleep(time.Duration(sleepTime * float64(time.Second)))
|
|
retryCount++
|
|
}
|
|
|
|
ecsSp.val.Store(_sh)
|
|
return _sh.securityHolder
|
|
}
|
|
|
|
func (ecsSp *EcsSecurityProvider) getAndSetSecurity() securityHolder {
|
|
ecsSp.lock.Lock()
|
|
defer ecsSp.lock.Unlock()
|
|
tsh, succeed := ecsSp.loadTemporarySecurityHolder()
|
|
if !succeed || time.Now().After(tsh.expireDate) {
|
|
return ecsSp.getAndSetSecurityWithOutLock()
|
|
}
|
|
return tsh.securityHolder
|
|
}
|
|
|
|
func (ecsSp *EcsSecurityProvider) getSecurity() securityHolder {
|
|
if tsh, succeed := ecsSp.loadTemporarySecurityHolder(); succeed {
|
|
if time.Now().Before(tsh.expireDate) {
|
|
//not expire
|
|
if time.Now().Add(time.Minute*5).After(tsh.expireDate) && atomic.CompareAndSwapInt32(&ecsSp.prefetch, 0, 1) {
|
|
//do prefetch
|
|
sh := ecsSp.getAndSetSecurityWithOutLock()
|
|
atomic.CompareAndSwapInt32(&ecsSp.prefetch, 1, 0)
|
|
return sh
|
|
}
|
|
return tsh.securityHolder
|
|
}
|
|
return ecsSp.getAndSetSecurity()
|
|
}
|
|
|
|
return ecsSp.getAndSetSecurity()
|
|
}
|
|
|
|
func getInternalTransport() *http.Transport {
|
|
|
|
timeout := 10
|
|
transport := &http.Transport{
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
start := GetCurrentTimestamp()
|
|
conn, err := (&net.Dialer{
|
|
Timeout: time.Second * time.Duration(timeout),
|
|
Resolver: net.DefaultResolver,
|
|
}).Dial(network, addr)
|
|
|
|
if isInfoLogEnabled() {
|
|
doLog(LEVEL_INFO, "Do http dial cost %d ms", (GetCurrentTimestamp() - start))
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return getConnDelegate(conn, timeout, timeout*10), nil
|
|
},
|
|
MaxIdleConns: 10,
|
|
MaxIdleConnsPerHost: 10,
|
|
ResponseHeaderTimeout: time.Second * time.Duration(timeout),
|
|
IdleConnTimeout: time.Second * time.Duration(DEFAULT_IDLE_CONN_TIMEOUT),
|
|
DisableCompression: true,
|
|
}
|
|
|
|
return transport
|
|
}
|
|
|
|
func NewEcsSecurityProvider(retryCount int) *EcsSecurityProvider {
|
|
ecsSp := &EcsSecurityProvider{
|
|
retryCount: retryCount,
|
|
}
|
|
ecsSp.httpClient = &http.Client{Transport: getInternalTransport(), CheckRedirect: checkRedirectFunc}
|
|
return ecsSp
|
|
}
|