@@ -3,8 +3,10 @@ package cos
33import (
44 "crypto/hmac"
55 "crypto/sha1"
6+ "encoding/json"
67 "fmt"
78 "hash"
9+ "io/ioutil"
810 "net/http"
911 "net/url"
1012 "sort"
@@ -13,9 +15,18 @@ import (
1315 "time"
1416)
1517
16- const sha1SignAlgorithm = "sha1"
17- const privateHeaderPrefix = "x-cos-"
18- const defaultAuthExpire = time .Hour
18+ const (
19+ sha1SignAlgorithm = "sha1"
20+ privateHeaderPrefix = "x-cos-"
21+ defaultAuthExpire = time .Hour
22+ )
23+
24+ var (
25+ defaultCVMAuthExpire = int64 (600 )
26+ defaultCVMSchema = "http"
27+ defaultCVMMetaHost = "metadata.tencentyun.com"
28+ defaultCVMCredURI = "latest/meta-data/cam/security-credentials"
29+ )
1930
2031// 需要校验的 Headers 列表
2132var needSignHeaders = map [string ]bool {
@@ -318,3 +329,120 @@ func (t *AuthorizationTransport) transport() http.RoundTripper {
318329 }
319330 return http .DefaultTransport
320331}
332+
333+ type CVMSecurityCredentials struct {
334+ TmpSecretId string `json:",omitempty"`
335+ TmpSecretKey string `json:",omitempty"`
336+ ExpiredTime int64 `json:",omitempty"`
337+ Expiration string `json:",omitempty"`
338+ Token string `json:",omitempty"`
339+ Code string `json:",omitempty"`
340+ }
341+
342+ type CVMCredentialsTransport struct {
343+ RoleName string
344+ Transport http.RoundTripper
345+ secretID string
346+ secretKey string
347+ sessionToken string
348+ expiredTime int64
349+ rwLocker sync.RWMutex
350+ }
351+
352+ func (t * CVMCredentialsTransport ) GetRoles () ([]string , error ) {
353+ urlname := fmt .Sprintf ("%s://%s/%s" , defaultCVMSchema , defaultCVMMetaHost , defaultCVMCredURI )
354+ resp , err := http .Get (urlname )
355+ if err != nil {
356+ return nil , err
357+ }
358+ defer resp .Body .Close ()
359+ if resp .StatusCode < 200 || resp .StatusCode > 299 {
360+ bs , _ := ioutil .ReadAll (resp .Body )
361+ return nil , fmt .Errorf ("get cvm security-credentials role failed, StatusCode: %v, Body: %v" , resp .StatusCode , string (bs ))
362+ }
363+ bs , err := ioutil .ReadAll (resp .Body )
364+ if err != nil {
365+ return nil , err
366+ }
367+ roles := strings .Split (strings .TrimSpace (string (bs )), "\n " )
368+ if len (roles ) == 0 {
369+ return nil , fmt .Errorf ("get cvm security-credentials role failed, No valid cam role was found" )
370+ }
371+ return roles , nil
372+ }
373+
374+ // https://cloud.tencent.com/document/product/213/4934
375+ func (t * CVMCredentialsTransport ) UpdateCredential (now int64 ) (string , string , string , error ) {
376+ t .rwLocker .Lock ()
377+ defer t .rwLocker .Unlock ()
378+ if t .expiredTime > now + defaultCVMAuthExpire {
379+ return t .secretID , t .secretKey , t .sessionToken , nil
380+ }
381+ roleName := t .RoleName
382+ if roleName == "" {
383+ roles , err := t .GetRoles ()
384+ if err != nil {
385+ return t .secretID , t .secretKey , t .sessionToken , err
386+ }
387+ roleName = roles [0 ]
388+ }
389+ urlname := fmt .Sprintf ("%s://%s/%s/%s" , defaultCVMSchema , defaultCVMMetaHost , defaultCVMCredURI , roleName )
390+ resp , err := http .Get (urlname )
391+ if err != nil {
392+ return t .secretID , t .secretKey , t .sessionToken , err
393+ }
394+ defer resp .Body .Close ()
395+ if resp .StatusCode < 200 || resp .StatusCode > 299 {
396+ bs , _ := ioutil .ReadAll (resp .Body )
397+ return t .secretID , t .secretKey , t .sessionToken , fmt .Errorf ("call cvm security-credentials failed, StatusCode: %v, Body: %v" , resp .StatusCode , string (bs ))
398+ }
399+ var cred CVMSecurityCredentials
400+ err = json .NewDecoder (resp .Body ).Decode (& cred )
401+ if err != nil {
402+ return t .secretID , t .secretKey , t .sessionToken , err
403+ }
404+ if cred .Code != "Success" {
405+ return t .secretID , t .secretKey , t .sessionToken , fmt .Errorf ("call cvm security-credentials failed, Code:%v" , cred .Code )
406+ }
407+ t .secretID , t .secretKey , t .sessionToken , t .expiredTime = cred .TmpSecretId , cred .TmpSecretKey , cred .Token , cred .ExpiredTime
408+ return t .secretID , t .secretKey , t .sessionToken , nil
409+ }
410+
411+ func (t * CVMCredentialsTransport ) GetCredential () (string , string , string , error ) {
412+ now := time .Now ().Unix ()
413+ t .rwLocker .RLock ()
414+ // 提前 defaultCVMAuthExpire 获取重新获取临时密钥
415+ if t .expiredTime <= now + defaultCVMAuthExpire {
416+ expiredTime := t .expiredTime
417+ t .rwLocker .RUnlock ()
418+ secretID , secretKey , secretToken , err := t .UpdateCredential (now )
419+ // 获取临时密钥失败但密钥未过期
420+ if err != nil && now < expiredTime {
421+ err = nil
422+ }
423+ return secretID , secretKey , secretToken , err
424+ }
425+ defer t .rwLocker .RUnlock ()
426+ return t .secretID , t .secretKey , t .sessionToken , nil
427+ }
428+
429+ func (t * CVMCredentialsTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
430+ ak , sk , token , err := t .GetCredential ()
431+ if err != nil {
432+ return nil , err
433+ }
434+ req = cloneRequest (req )
435+ // 增加 Authorization header
436+ authTime := NewAuthTime (defaultAuthExpire )
437+ AddAuthorizationHeader (ak , sk , token , req , authTime )
438+
439+ resp , err := t .transport ().RoundTrip (req )
440+ return resp , err
441+ }
442+
443+ func (t * CVMCredentialsTransport ) transport () http.RoundTripper {
444+ if t .Transport != nil {
445+ return t .Transport
446+ }
447+ return http .DefaultTransport
448+ }
0 commit comments