util/token.go (167 lines of code) (raw):
package util
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
const (
aliyunECSRamURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/"
expirationTimeFormat = "2006-01-02T15:04:05Z"
)
type UpdateTokenFunction = func() (accessKeyID, accessKeySecret, securityToken string, expireTime time.Time, err error)
var errNoFile = errors.New("no secret file")
// AKInfo ...
type AKInfo struct {
AccessKeyId string `json:"access.key.id"`
AccessKeySecret string `json:"access.key.secret"`
SecurityToken string `json:"security.token"`
Expiration string `json:"expiration"`
Keyring string `json:"keyring"`
}
// SecurityTokenResult ...
type SecurityTokenResult struct {
AccessKeyId string
AccessKeySecret string
Expiration string
SecurityToken string
Code string
LastUpdated string
}
func getToken() (result []byte, err error) {
client := http.Client{
Timeout: time.Second * 3,
}
var respList *http.Response
respList, err = client.Get(aliyunECSRamURL)
if err != nil {
return nil, err
}
defer respList.Body.Close()
var body []byte
body, err = ioutil.ReadAll(respList.Body)
if err != nil {
return nil, err
}
bodyStr := string(body)
bodyStr = strings.TrimSpace(bodyStr)
roles := strings.Split(bodyStr, "\n")
role := roles[0]
var respGet *http.Response
respGet, err = client.Get(aliyunECSRamURL + role)
if err != nil {
return nil, err
}
defer respGet.Body.Close()
body, err = ioutil.ReadAll(respGet.Body)
if err != nil {
return nil, err
}
return body, nil
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func decrypt(s string, keyring []byte) ([]byte, error) {
cdata, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(keyring)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
iv := cdata[:blockSize]
blockMode := cipher.NewCBCDecrypter(block, iv)
origData := make([]byte, len(cdata)-blockSize)
blockMode.CryptBlocks(origData, cdata[blockSize:])
origData = pkcs5UnPadding(origData)
return origData, nil
}
func getAKFromLocalFile(configFilePath string) (accessKeyID, accessKeySecret, securityToken string, expireTime time.Time, err error) {
if _, err = os.Stat(configFilePath); err == nil {
var akInfo AKInfo
//获取token config json
encodeTokenCfg, err := ioutil.ReadFile(configFilePath)
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
err = json.Unmarshal(encodeTokenCfg, &akInfo)
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
keyring := akInfo.Keyring
ak, err := decrypt(akInfo.AccessKeyId, []byte(keyring))
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
sk, err := decrypt(akInfo.AccessKeySecret, []byte(keyring))
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
token, err := decrypt(akInfo.SecurityToken, []byte(keyring))
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
layout := "2006-01-02T15:04:05Z"
t, err := time.Parse(layout, akInfo.Expiration)
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
if t.Before(time.Now()) {
err = errors.New("invalid token which is expired")
}
akInfo.AccessKeyId = string(ak)
akInfo.AccessKeySecret = string(sk)
akInfo.SecurityToken = string(token)
if err != nil {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
return akInfo.AccessKeyId, akInfo.AccessKeySecret, akInfo.SecurityToken, t, nil
}
return accessKeyID, accessKeySecret, securityToken, expireTime, errNoFile
}
func updateTokenFunction(configFilePath string) (accessKeyID, accessKeySecret, securityToken string, expireTime time.Time, err error) {
if configFilePath != "" {
accessKeyID, accessKeySecret, securityToken, expireTime, err = getAKFromLocalFile(configFilePath)
if err != errNoFile {
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
}
var tokenResultBuffer []byte
for tryTime := 0; tryTime < 3; tryTime++ {
tokenResultBuffer, err = getToken()
if err != nil {
continue
}
var tokenResult SecurityTokenResult
err = json.Unmarshal(tokenResultBuffer, &tokenResult)
if err != nil {
continue
}
if strings.ToLower(tokenResult.Code) != "success" {
tokenResult.AccessKeySecret = "x"
tokenResult.SecurityToken = "x"
continue
}
expireTime, err := time.Parse(expirationTimeFormat, tokenResult.Expiration)
if err != nil {
tokenResult.AccessKeySecret = "x"
tokenResult.SecurityToken = "x"
continue
}
return tokenResult.AccessKeyId, tokenResult.AccessKeySecret, tokenResult.SecurityToken, expireTime, nil
}
return accessKeyID, accessKeySecret, securityToken, expireTime, err
}
// NewTokenUpdateFunc create a token update function for ACK or ECS
func NewTokenUpdateFunc(role string, configFilePath string) (tokenUpdateFunc UpdateTokenFunction, shutdown chan struct{}) {
return func() (accessKeyID string, accessKeySecret string, securityToken string, expireTime time.Time, err error) {
return updateTokenFunction(configFilePath)
}, make(chan struct{})
}