ecs_ram_role.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package providers
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "time"
  10. httputil "github.com/aliyun/credentials-go/credentials/internal/http"
  11. )
  12. type ECSRAMRoleCredentialsProvider struct {
  13. roleName string
  14. disableIMDSv1 bool
  15. // for sts
  16. session *sessionCredentials
  17. expirationTimestamp int64
  18. // for http options
  19. httpOptions *HttpOptions
  20. }
  21. type ECSRAMRoleCredentialsProviderBuilder struct {
  22. provider *ECSRAMRoleCredentialsProvider
  23. }
  24. func NewECSRAMRoleCredentialsProviderBuilder() *ECSRAMRoleCredentialsProviderBuilder {
  25. return &ECSRAMRoleCredentialsProviderBuilder{
  26. provider: &ECSRAMRoleCredentialsProvider{},
  27. }
  28. }
  29. func (builder *ECSRAMRoleCredentialsProviderBuilder) WithRoleName(roleName string) *ECSRAMRoleCredentialsProviderBuilder {
  30. builder.provider.roleName = roleName
  31. return builder
  32. }
  33. func (builder *ECSRAMRoleCredentialsProviderBuilder) WithDisableIMDSv1(disableIMDSv1 bool) *ECSRAMRoleCredentialsProviderBuilder {
  34. builder.provider.disableIMDSv1 = disableIMDSv1
  35. return builder
  36. }
  37. func (builder *ECSRAMRoleCredentialsProviderBuilder) WithHttpOptions(httpOptions *HttpOptions) *ECSRAMRoleCredentialsProviderBuilder {
  38. builder.provider.httpOptions = httpOptions
  39. return builder
  40. }
  41. const defaultMetadataTokenDuration = 21600 // 6 hours
  42. func (builder *ECSRAMRoleCredentialsProviderBuilder) Build() (provider *ECSRAMRoleCredentialsProvider, err error) {
  43. if strings.ToLower(os.Getenv("ALIBABA_CLOUD_ECS_METADATA_DISABLED")) == "true" {
  44. err = errors.New("IMDS credentials is disabled")
  45. return
  46. }
  47. // 设置 roleName 默认值
  48. if builder.provider.roleName == "" {
  49. builder.provider.roleName = os.Getenv("ALIBABA_CLOUD_ECS_METADATA")
  50. }
  51. if !builder.provider.disableIMDSv1 {
  52. builder.provider.disableIMDSv1 = strings.ToLower(os.Getenv("ALIBABA_CLOUD_IMDSV1_DISABLED")) == "true"
  53. }
  54. provider = builder.provider
  55. return
  56. }
  57. type ecsRAMRoleResponse struct {
  58. Code *string `json:"Code"`
  59. AccessKeyId *string `json:"AccessKeyId"`
  60. AccessKeySecret *string `json:"AccessKeySecret"`
  61. SecurityToken *string `json:"SecurityToken"`
  62. LastUpdated *string `json:"LastUpdated"`
  63. Expiration *string `json:"Expiration"`
  64. }
  65. func (provider *ECSRAMRoleCredentialsProvider) needUpdateCredential() bool {
  66. if provider.expirationTimestamp == 0 {
  67. return true
  68. }
  69. return provider.expirationTimestamp-time.Now().Unix() <= 180
  70. }
  71. func (provider *ECSRAMRoleCredentialsProvider) getRoleName() (roleName string, err error) {
  72. req := &httputil.Request{
  73. Method: "GET",
  74. Protocol: "http",
  75. Host: "100.100.100.200",
  76. Path: "/latest/meta-data/ram/security-credentials/",
  77. Headers: map[string]string{},
  78. }
  79. connectTimeout := 1 * time.Second
  80. readTimeout := 1 * time.Second
  81. if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
  82. connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
  83. }
  84. if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
  85. readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
  86. }
  87. if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
  88. req.Proxy = provider.httpOptions.Proxy
  89. }
  90. req.ConnectTimeout = connectTimeout
  91. req.ReadTimeout = readTimeout
  92. metadataToken, err := provider.getMetadataToken()
  93. if err != nil {
  94. return "", err
  95. }
  96. if metadataToken != "" {
  97. req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
  98. }
  99. res, err := httpDo(req)
  100. if err != nil {
  101. err = fmt.Errorf("get role name failed: %s", err.Error())
  102. return
  103. }
  104. if res.StatusCode != 200 {
  105. err = fmt.Errorf("get role name failed: %s %d", req.BuildRequestURL(), res.StatusCode)
  106. return
  107. }
  108. roleName = strings.TrimSpace(string(res.Body))
  109. return
  110. }
  111. func (provider *ECSRAMRoleCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
  112. roleName := provider.roleName
  113. if roleName == "" {
  114. roleName, err = provider.getRoleName()
  115. if err != nil {
  116. return
  117. }
  118. }
  119. req := &httputil.Request{
  120. Method: "GET",
  121. Protocol: "http",
  122. Host: "100.100.100.200",
  123. Path: "/latest/meta-data/ram/security-credentials/" + roleName,
  124. Headers: map[string]string{},
  125. }
  126. connectTimeout := 1 * time.Second
  127. readTimeout := 1 * time.Second
  128. if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
  129. connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
  130. }
  131. if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
  132. readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
  133. }
  134. if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
  135. req.Proxy = provider.httpOptions.Proxy
  136. }
  137. req.ConnectTimeout = connectTimeout
  138. req.ReadTimeout = readTimeout
  139. metadataToken, err := provider.getMetadataToken()
  140. if err != nil {
  141. return nil, err
  142. }
  143. if metadataToken != "" {
  144. req.Headers["x-aliyun-ecs-metadata-token"] = metadataToken
  145. }
  146. res, err := httpDo(req)
  147. if err != nil {
  148. err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
  149. return
  150. }
  151. if res.StatusCode != 200 {
  152. err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
  153. return
  154. }
  155. var data ecsRAMRoleResponse
  156. err = json.Unmarshal(res.Body, &data)
  157. if err != nil {
  158. err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error())
  159. return
  160. }
  161. if data.AccessKeyId == nil || data.AccessKeySecret == nil || data.SecurityToken == nil {
  162. err = fmt.Errorf("refresh Ecs sts token err, fail to get credentials")
  163. return
  164. }
  165. if *data.Code != "Success" {
  166. err = fmt.Errorf("refresh Ecs sts token err, Code is not Success")
  167. return
  168. }
  169. session = &sessionCredentials{
  170. AccessKeyId: *data.AccessKeyId,
  171. AccessKeySecret: *data.AccessKeySecret,
  172. SecurityToken: *data.SecurityToken,
  173. Expiration: *data.Expiration,
  174. }
  175. return
  176. }
  177. func (provider *ECSRAMRoleCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
  178. if provider.session == nil || provider.needUpdateCredential() {
  179. session, err1 := provider.getCredentials()
  180. if err1 != nil {
  181. return nil, err1
  182. }
  183. provider.session = session
  184. expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", session.Expiration)
  185. if err2 != nil {
  186. return nil, err2
  187. }
  188. provider.expirationTimestamp = expirationTime.Unix()
  189. }
  190. cc = &Credentials{
  191. AccessKeyId: provider.session.AccessKeyId,
  192. AccessKeySecret: provider.session.AccessKeySecret,
  193. SecurityToken: provider.session.SecurityToken,
  194. ProviderName: provider.GetProviderName(),
  195. }
  196. return
  197. }
  198. func (provider *ECSRAMRoleCredentialsProvider) GetProviderName() string {
  199. return "ecs_ram_role"
  200. }
  201. func (provider *ECSRAMRoleCredentialsProvider) getMetadataToken() (metadataToken string, err error) {
  202. // PUT http://100.100.100.200/latest/api/token
  203. req := &httputil.Request{
  204. Method: "PUT",
  205. Protocol: "http",
  206. Host: "100.100.100.200",
  207. Path: "/latest/api/token",
  208. Headers: map[string]string{
  209. "X-aliyun-ecs-metadata-token-ttl-seconds": strconv.Itoa(defaultMetadataTokenDuration),
  210. },
  211. }
  212. connectTimeout := 1 * time.Second
  213. readTimeout := 1 * time.Second
  214. if provider.httpOptions != nil && provider.httpOptions.ConnectTimeout > 0 {
  215. connectTimeout = time.Duration(provider.httpOptions.ConnectTimeout) * time.Millisecond
  216. }
  217. if provider.httpOptions != nil && provider.httpOptions.ReadTimeout > 0 {
  218. readTimeout = time.Duration(provider.httpOptions.ReadTimeout) * time.Millisecond
  219. }
  220. if provider.httpOptions != nil && provider.httpOptions.Proxy != "" {
  221. req.Proxy = provider.httpOptions.Proxy
  222. }
  223. req.ConnectTimeout = connectTimeout
  224. req.ReadTimeout = readTimeout
  225. res, _err := httpDo(req)
  226. if _err != nil {
  227. if provider.disableIMDSv1 {
  228. err = fmt.Errorf("get metadata token failed: %s", _err.Error())
  229. }
  230. return
  231. }
  232. if res.StatusCode != 200 {
  233. if provider.disableIMDSv1 {
  234. err = fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", res.StatusCode, string(res.Body))
  235. }
  236. return
  237. }
  238. metadataToken = string(res.Body)
  239. return
  240. }