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