github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/oss/backend.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package oss 7 8 import ( 9 "context" 10 "encoding/json" 11 "fmt" 12 "log" 13 "net/http" 14 "net/url" 15 "os" 16 "regexp" 17 "runtime" 18 "strconv" 19 "strings" 20 "time" 21 22 "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" 23 24 "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 25 "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" 26 "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 27 "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 28 "github.com/aliyun/alibaba-cloud-sdk-go/services/location" 29 "github.com/aliyun/alibaba-cloud-sdk-go/services/sts" 30 "github.com/aliyun/aliyun-oss-go-sdk/oss" 31 "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" 32 "github.com/hashicorp/go-cleanhttp" 33 "github.com/jmespath/go-jmespath" 34 "github.com/mitchellh/go-homedir" 35 36 "github.com/opentofu/opentofu/internal/backend" 37 "github.com/opentofu/opentofu/internal/encryption" 38 "github.com/opentofu/opentofu/internal/httpclient" 39 "github.com/opentofu/opentofu/internal/legacy/helper/schema" 40 "github.com/opentofu/opentofu/version" 41 ) 42 43 // Deprecated in favor of flattening assume_role_* options 44 func deprecatedAssumeRoleSchema() *schema.Schema { 45 return &schema.Schema{ 46 Type: schema.TypeSet, 47 Optional: true, 48 MaxItems: 1, 49 //Deprecated: "use assume_role_* options instead", 50 Elem: &schema.Resource{ 51 Schema: map[string]*schema.Schema{ 52 "role_arn": { 53 Type: schema.TypeString, 54 Required: true, 55 Description: "The ARN of a RAM role to assume prior to making API calls.", 56 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""), 57 }, 58 "session_name": { 59 Type: schema.TypeString, 60 Optional: true, 61 Description: "The session name to use when assuming the role.", 62 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""), 63 }, 64 "policy": { 65 Type: schema.TypeString, 66 Optional: true, 67 Description: "The permissions applied when assuming a role. You cannot use this policy to grant permissions which exceed those of the role that is being assumed.", 68 }, 69 "session_expiration": { 70 Type: schema.TypeInt, 71 Optional: true, 72 Description: "The time after which the established session for assuming role expires.", 73 ValidateFunc: func(v interface{}, k string) ([]string, []error) { 74 min := 900 75 max := 3600 76 value, ok := v.(int) 77 if !ok { 78 return nil, []error{fmt.Errorf("expected type of %s to be int", k)} 79 } 80 81 if value < min || value > max { 82 return nil, []error{fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)} 83 } 84 85 return nil, nil 86 }, 87 }, 88 }, 89 }, 90 } 91 } 92 93 // New creates a new backend for OSS remote state. 94 func New(enc encryption.StateEncryption) backend.Backend { 95 s := &schema.Backend{ 96 Schema: map[string]*schema.Schema{ 97 "access_key": { 98 Type: schema.TypeString, 99 Optional: true, 100 Description: "Alibaba Cloud Access Key ID", 101 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ACCESS_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_ID")), 102 }, 103 104 "secret_key": { 105 Type: schema.TypeString, 106 Optional: true, 107 Description: "Alibaba Cloud Access Secret Key", 108 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECRET_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_SECRET")), 109 }, 110 111 "security_token": { 112 Type: schema.TypeString, 113 Optional: true, 114 Description: "Alibaba Cloud Security Token", 115 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECURITY_TOKEN", ""), 116 }, 117 118 "ecs_role_name": { 119 Type: schema.TypeString, 120 Optional: true, 121 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ECS_ROLE_NAME", os.Getenv("ALICLOUD_ECS_ROLE_NAME")), 122 Description: "The RAM Role Name attached on a ECS instance for API operations. You can retrieve this from the 'Access Control' section of the Alibaba Cloud console.", 123 }, 124 125 "region": { 126 Type: schema.TypeString, 127 Optional: true, 128 Description: "The region of the OSS bucket.", 129 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", os.Getenv("ALICLOUD_DEFAULT_REGION")), 130 }, 131 "sts_endpoint": { 132 Type: schema.TypeString, 133 Optional: true, 134 Description: "A custom endpoint for the STS API", 135 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_STS_ENDPOINT", ""), 136 }, 137 "tablestore_endpoint": { 138 Type: schema.TypeString, 139 Optional: true, 140 Description: "A custom endpoint for the TableStore API", 141 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_TABLESTORE_ENDPOINT", ""), 142 }, 143 "endpoint": { 144 Type: schema.TypeString, 145 Optional: true, 146 Description: "A custom endpoint for the OSS API", 147 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_OSS_ENDPOINT", os.Getenv("OSS_ENDPOINT")), 148 }, 149 150 "bucket": { 151 Type: schema.TypeString, 152 Required: true, 153 Description: "The name of the OSS bucket", 154 }, 155 156 "prefix": { 157 Type: schema.TypeString, 158 Optional: true, 159 Description: "The directory where state files will be saved inside the bucket", 160 Default: "env:", 161 ValidateFunc: func(v interface{}, s string) ([]string, []error) { 162 prefix := v.(string) 163 if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") { 164 return nil, []error{fmt.Errorf("workspace_key_prefix must not start with '/' or './'")} 165 } 166 return nil, nil 167 }, 168 }, 169 170 "key": { 171 Type: schema.TypeString, 172 Optional: true, 173 Description: "The path of the state file inside the bucket", 174 ValidateFunc: func(v interface{}, s string) ([]string, []error) { 175 if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") { 176 return nil, []error{fmt.Errorf("key can not start and end with '/'")} 177 } 178 return nil, nil 179 }, 180 Default: "terraform.tfstate", 181 }, 182 183 "tablestore_table": { 184 Type: schema.TypeString, 185 Optional: true, 186 Description: "TableStore table for state locking and consistency", 187 Default: "", 188 }, 189 190 "encrypt": { 191 Type: schema.TypeBool, 192 Optional: true, 193 Description: "Whether to enable server side encryption of the state file", 194 Default: false, 195 }, 196 197 "acl": { 198 Type: schema.TypeString, 199 Optional: true, 200 Description: "Object ACL to be applied to the state file", 201 Default: "", 202 ValidateFunc: func(v interface{}, k string) ([]string, []error) { 203 if value := v.(string); value != "" { 204 acls := oss.ACLType(value) 205 if acls != oss.ACLPrivate && acls != oss.ACLPublicRead && acls != oss.ACLPublicReadWrite { 206 return nil, []error{fmt.Errorf( 207 "%q must be a valid ACL value , expected %s, %s or %s, got %q", 208 k, oss.ACLPrivate, oss.ACLPublicRead, oss.ACLPublicReadWrite, acls)} 209 } 210 } 211 return nil, nil 212 }, 213 }, 214 "shared_credentials_file": { 215 Type: schema.TypeString, 216 Optional: true, 217 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SHARED_CREDENTIALS_FILE", ""), 218 Description: "This is the path to the shared credentials file. If this is not set and a profile is specified, `~/.aliyun/config.json` will be used.", 219 }, 220 "profile": { 221 Type: schema.TypeString, 222 Optional: true, 223 Description: "This is the Alibaba Cloud profile name as set in the shared credentials file. It can also be sourced from the `ALICLOUD_PROFILE` environment variable.", 224 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_PROFILE", ""), 225 }, 226 "assume_role": deprecatedAssumeRoleSchema(), 227 "assume_role_role_arn": { 228 Type: schema.TypeString, 229 Optional: true, 230 Description: "The ARN of a RAM role to assume prior to making API calls.", 231 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""), 232 }, 233 "assume_role_session_name": { 234 Type: schema.TypeString, 235 Optional: true, 236 Description: "The session name to use when assuming the role.", 237 DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""), 238 }, 239 "assume_role_policy": { 240 Type: schema.TypeString, 241 Optional: true, 242 Description: "The permissions applied when assuming a role. You cannot use this policy to grant permissions which exceed those of the role that is being assumed.", 243 }, 244 "assume_role_session_expiration": { 245 Type: schema.TypeInt, 246 Optional: true, 247 Description: "The time after which the established session for assuming role expires.", 248 ValidateFunc: func(v interface{}, k string) ([]string, []error) { 249 min := 900 250 max := 3600 251 value, ok := v.(int) 252 if !ok { 253 return nil, []error{fmt.Errorf("expected type of %s to be int", k)} 254 } 255 256 if value < min || value > max { 257 return nil, []error{fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)} 258 } 259 260 return nil, nil 261 }, 262 }, 263 }, 264 } 265 266 result := &Backend{Backend: s, encryption: enc} 267 result.Backend.ConfigureFunc = result.configure 268 return result 269 } 270 271 type Backend struct { 272 *schema.Backend 273 encryption encryption.StateEncryption 274 275 // The fields below are set from configure 276 ossClient *oss.Client 277 otsClient *tablestore.TableStoreClient 278 279 bucketName string 280 statePrefix string 281 stateKey string 282 serverSideEncryption bool 283 acl string 284 otsEndpoint string 285 otsTable string 286 } 287 288 func (b *Backend) configure(ctx context.Context) error { 289 if b.ossClient != nil { 290 return nil 291 } 292 293 // Grab the resource data 294 d := schema.FromContextBackendConfig(ctx) 295 296 b.bucketName = d.Get("bucket").(string) 297 b.statePrefix = strings.TrimPrefix(strings.Trim(d.Get("prefix").(string), "/"), "./") 298 b.stateKey = d.Get("key").(string) 299 b.serverSideEncryption = d.Get("encrypt").(bool) 300 b.acl = d.Get("acl").(string) 301 302 var getBackendConfig = func(str string, key string) string { 303 if str == "" { 304 value, err := getConfigFromProfile(d, key) 305 if err == nil && value != nil { 306 str = value.(string) 307 } 308 } 309 return str 310 } 311 312 accessKey := getBackendConfig(d.Get("access_key").(string), "access_key_id") 313 secretKey := getBackendConfig(d.Get("secret_key").(string), "access_key_secret") 314 securityToken := getBackendConfig(d.Get("security_token").(string), "sts_token") 315 region := getBackendConfig(d.Get("region").(string), "region_id") 316 317 stsEndpoint := d.Get("sts_endpoint").(string) 318 endpoint := d.Get("endpoint").(string) 319 schma := "https" 320 321 roleArn := getBackendConfig("", "ram_role_arn") 322 sessionName := getBackendConfig("", "ram_session_name") 323 var policy string 324 var sessionExpiration int 325 expiredSeconds, err := getConfigFromProfile(d, "expired_seconds") 326 if err == nil && expiredSeconds != nil { 327 sessionExpiration = (int)(expiredSeconds.(float64)) 328 } 329 330 if v, ok := d.GetOk("assume_role_role_arn"); ok && v.(string) != "" { 331 roleArn = v.(string) 332 if v, ok := d.GetOk("assume_role_session_name"); ok { 333 sessionName = v.(string) 334 } 335 if v, ok := d.GetOk("assume_role_policy"); ok { 336 policy = v.(string) 337 } 338 if v, ok := d.GetOk("assume_role_session_expiration"); ok { 339 sessionExpiration = v.(int) 340 } 341 } else if v, ok := d.GetOk("assume_role"); ok { 342 // deprecated assume_role block 343 for _, v := range v.(*schema.Set).List() { 344 assumeRole := v.(map[string]interface{}) 345 if assumeRole["role_arn"].(string) != "" { 346 roleArn = assumeRole["role_arn"].(string) 347 } 348 if assumeRole["session_name"].(string) != "" { 349 sessionName = assumeRole["session_name"].(string) 350 } 351 policy = assumeRole["policy"].(string) 352 sessionExpiration = assumeRole["session_expiration"].(int) 353 } 354 } 355 356 if sessionName == "" { 357 sessionName = "tofu" 358 } 359 if sessionExpiration == 0 { 360 if v := os.Getenv("ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION"); v != "" { 361 if expiredSeconds, err := strconv.Atoi(v); err == nil { 362 sessionExpiration = expiredSeconds 363 } 364 } 365 if sessionExpiration == 0 { 366 sessionExpiration = 3600 367 } 368 } 369 370 if accessKey == "" { 371 ecsRoleName := getBackendConfig(d.Get("ecs_role_name").(string), "ram_role_name") 372 subAccessKeyId, subAccessKeySecret, subSecurityToken, err := getAuthCredentialByEcsRoleName(ecsRoleName) 373 if err != nil { 374 return err 375 } 376 accessKey, secretKey, securityToken = subAccessKeyId, subAccessKeySecret, subSecurityToken 377 } 378 379 if roleArn != "" { 380 subAccessKeyId, subAccessKeySecret, subSecurityToken, err := getAssumeRoleAK(accessKey, secretKey, securityToken, region, roleArn, sessionName, policy, stsEndpoint, sessionExpiration) 381 if err != nil { 382 return err 383 } 384 accessKey, secretKey, securityToken = subAccessKeyId, subAccessKeySecret, subSecurityToken 385 } 386 387 if endpoint == "" { 388 endpointsResponse, err := b.getOSSEndpointByRegion(accessKey, secretKey, securityToken, region) 389 if err != nil { 390 log.Printf("[WARN] getting oss endpoint failed and using oss-%s.aliyuncs.com instead. Error: %#v.", region, err) 391 } else { 392 for _, endpointItem := range endpointsResponse.Endpoints.Endpoint { 393 if endpointItem.Type == "openAPI" { 394 endpoint = endpointItem.Endpoint 395 break 396 } 397 } 398 } 399 if endpoint == "" { 400 endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region) 401 } 402 } 403 if !strings.HasPrefix(endpoint, "http") { 404 endpoint = fmt.Sprintf("%s://%s", schma, endpoint) 405 } 406 log.Printf("[DEBUG] Instantiate OSS client using endpoint: %#v", endpoint) 407 var options []oss.ClientOption 408 if securityToken != "" { 409 options = append(options, oss.SecurityToken(securityToken)) 410 } 411 options = append(options, oss.UserAgent(httpclient.OpenTofuUserAgent(TerraformVersion))) 412 413 proxyUrl := getHttpProxyUrl() 414 if proxyUrl != nil { 415 options = append(options, oss.Proxy(proxyUrl.String())) 416 } 417 418 client, err := oss.New(endpoint, accessKey, secretKey, options...) 419 b.ossClient = client 420 otsEndpoint := d.Get("tablestore_endpoint").(string) 421 if otsEndpoint != "" { 422 if !strings.HasPrefix(otsEndpoint, "http") { 423 otsEndpoint = fmt.Sprintf("%s://%s", schma, otsEndpoint) 424 } 425 b.otsEndpoint = otsEndpoint 426 parts := strings.Split(strings.TrimPrefix(strings.TrimPrefix(otsEndpoint, "https://"), "http://"), ".") 427 b.otsClient = tablestore.NewClientWithConfig(otsEndpoint, parts[0], accessKey, secretKey, securityToken, tablestore.NewDefaultTableStoreConfig()) 428 } 429 b.otsTable = d.Get("tablestore_table").(string) 430 431 return err 432 } 433 434 func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, region string) (*location.DescribeEndpointsResponse, error) { 435 args := location.CreateDescribeEndpointsRequest() 436 args.ServiceCode = "oss" 437 args.Id = region 438 args.Domain = "location-readonly.aliyuncs.com" 439 440 locationClient, err := location.NewClientWithOptions(region, getSdkConfig(), credentials.NewStsTokenCredential(access_key, secret_key, security_token)) 441 if err != nil { 442 return nil, fmt.Errorf("unable to initialize the location client: %w", err) 443 444 } 445 locationClient.AppendUserAgent(httpclient.DefaultApplicationName, TerraformVersion) 446 endpointsResponse, err := locationClient.DescribeEndpoints(args) 447 if err != nil { 448 return nil, fmt.Errorf("describe oss endpoint using region: %#v got an error: %w", region, err) 449 } 450 return endpointsResponse, nil 451 } 452 453 func getAssumeRoleAK(accessKey, secretKey, stsToken, region, roleArn, sessionName, policy, stsEndpoint string, sessionExpiration int) (string, string, string, error) { 454 request := sts.CreateAssumeRoleRequest() 455 request.RoleArn = roleArn 456 request.RoleSessionName = sessionName 457 request.DurationSeconds = requests.NewInteger(sessionExpiration) 458 request.Policy = policy 459 request.Scheme = "https" 460 461 var client *sts.Client 462 var err error 463 if stsToken == "" { 464 client, err = sts.NewClientWithAccessKey(region, accessKey, secretKey) 465 } else { 466 client, err = sts.NewClientWithStsToken(region, accessKey, secretKey, stsToken) 467 } 468 if err != nil { 469 return "", "", "", err 470 } 471 if stsEndpoint != "" { 472 endpoints.AddEndpointMapping(region, "STS", stsEndpoint) 473 } 474 response, err := client.AssumeRole(request) 475 if err != nil { 476 return "", "", "", err 477 } 478 return response.Credentials.AccessKeyId, response.Credentials.AccessKeySecret, response.Credentials.SecurityToken, nil 479 } 480 481 func getSdkConfig() *sdk.Config { 482 return sdk.NewConfig(). 483 WithMaxRetryTime(5). 484 WithTimeout(time.Duration(30) * time.Second). 485 WithGoRoutinePoolSize(10). 486 WithDebug(false). 487 WithHttpTransport(getTransport()). 488 WithScheme("HTTPS") 489 } 490 491 func getTransport() *http.Transport { 492 handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) 493 if err != nil { 494 handshakeTimeout = 120 495 } 496 transport := cleanhttp.DefaultTransport() 497 transport.TLSHandshakeTimeout = time.Duration(handshakeTimeout) * time.Second 498 transport.Proxy = http.ProxyFromEnvironment 499 return transport 500 } 501 502 type Invoker struct { 503 catchers []*Catcher 504 } 505 506 type Catcher struct { 507 Reason string 508 RetryCount int 509 RetryWaitSeconds int 510 } 511 512 var TerraformVersion = strings.TrimSuffix(version.String(), "-dev") 513 var ClientErrorCatcher = Catcher{"AliyunGoClientFailure", 10, 3} 514 var ServiceBusyCatcher = Catcher{"ServiceUnavailable", 10, 3} 515 516 func NewInvoker() Invoker { 517 i := Invoker{} 518 i.AddCatcher(ClientErrorCatcher) 519 i.AddCatcher(ServiceBusyCatcher) 520 return i 521 } 522 523 func (a *Invoker) AddCatcher(catcher Catcher) { 524 a.catchers = append(a.catchers, &catcher) 525 } 526 527 func (a *Invoker) Run(f func() error) error { 528 err := f() 529 530 if err == nil { 531 return nil 532 } 533 534 for _, catcher := range a.catchers { 535 if strings.Contains(err.Error(), catcher.Reason) { 536 catcher.RetryCount-- 537 538 if catcher.RetryCount <= 0 { 539 return fmt.Errorf("retry timeout and got an error: %w", err) 540 } else { 541 time.Sleep(time.Duration(catcher.RetryWaitSeconds) * time.Second) 542 return a.Run(f) 543 } 544 } 545 } 546 return err 547 } 548 549 var providerConfig map[string]interface{} 550 551 func getConfigFromProfile(d *schema.ResourceData, ProfileKey string) (interface{}, error) { 552 553 if providerConfig == nil { 554 if v, ok := d.GetOk("profile"); !ok || v.(string) == "" { 555 return nil, nil 556 } 557 current := d.Get("profile").(string) 558 // Set CredsFilename, expanding home directory 559 profilePath, err := homedir.Expand(d.Get("shared_credentials_file").(string)) 560 if err != nil { 561 return nil, err 562 } 563 if profilePath == "" { 564 profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME")) 565 if runtime.GOOS == "windows" { 566 profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE")) 567 } 568 } 569 providerConfig = make(map[string]interface{}) 570 _, err = os.Stat(profilePath) 571 if !os.IsNotExist(err) { 572 data, err := os.ReadFile(profilePath) 573 if err != nil { 574 return nil, err 575 } 576 config := map[string]interface{}{} 577 err = json.Unmarshal(data, &config) 578 if err != nil { 579 return nil, err 580 } 581 for _, v := range config["profiles"].([]interface{}) { 582 if current == v.(map[string]interface{})["name"] { 583 providerConfig = v.(map[string]interface{}) 584 } 585 } 586 } 587 } 588 589 mode := "" 590 if v, ok := providerConfig["mode"]; ok { 591 mode = v.(string) 592 } else { 593 return v, nil 594 } 595 switch ProfileKey { 596 case "access_key_id", "access_key_secret": 597 if mode == "EcsRamRole" { 598 return "", nil 599 } 600 case "ram_role_name": 601 if mode != "EcsRamRole" { 602 return "", nil 603 } 604 case "sts_token": 605 if mode != "StsToken" { 606 return "", nil 607 } 608 case "ram_role_arn", "ram_session_name": 609 if mode != "RamRoleArn" { 610 return "", nil 611 } 612 case "expired_seconds": 613 if mode != "RamRoleArn" { 614 return float64(0), nil 615 } 616 } 617 618 return providerConfig[ProfileKey], nil 619 } 620 621 var securityCredURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/" 622 623 // getAuthCredentialByEcsRoleName aims to access meta to get sts credential 624 // Actually, the job should be done by sdk, but currently not all resources and products support alibaba-cloud-sdk-go, 625 // and their go sdk does support ecs role name. 626 // This method is a temporary solution and it should be removed after all go sdk support ecs role name 627 // The related PR: https://github.com/terraform-providers/terraform-provider-alicloud/pull/731 628 func getAuthCredentialByEcsRoleName(ecsRoleName string) (accessKey, secretKey, token string, err error) { 629 630 if ecsRoleName == "" { 631 return 632 } 633 requestUrl := securityCredURL + ecsRoleName 634 httpRequest, err := http.NewRequest(requests.GET, requestUrl, strings.NewReader("")) 635 if err != nil { 636 err = fmt.Errorf("build sts requests err: %w", err) 637 return 638 } 639 httpClient := &http.Client{} 640 httpResponse, err := httpClient.Do(httpRequest) 641 if err != nil { 642 err = fmt.Errorf("get Ecs sts token err: %w", err) 643 return 644 } 645 646 response := responses.NewCommonResponse() 647 err = responses.Unmarshal(response, httpResponse, "") 648 if err != nil { 649 err = fmt.Errorf("unmarshal Ecs sts token response err: %w", err) 650 return 651 } 652 653 if response.GetHttpStatus() != http.StatusOK { 654 err = fmt.Errorf("get Ecs sts token err, httpStatus: %d, message = %s", response.GetHttpStatus(), response.GetHttpContentString()) 655 return 656 } 657 var data interface{} 658 err = json.Unmarshal(response.GetHttpContentBytes(), &data) 659 if err != nil { 660 err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %w", err) 661 return 662 } 663 code, err := jmespath.Search("Code", data) 664 if err != nil { 665 err = fmt.Errorf("refresh Ecs sts token err, fail to get Code: %w", err) 666 return 667 } 668 if code.(string) != "Success" { 669 err = fmt.Errorf("refresh Ecs sts token err, Code is not Success") 670 return 671 } 672 accessKeyId, err := jmespath.Search("AccessKeyId", data) 673 if err != nil { 674 err = fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeyId: %w", err) 675 return 676 } 677 accessKeySecret, err := jmespath.Search("AccessKeySecret", data) 678 if err != nil { 679 err = fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeySecret: %w", err) 680 return 681 } 682 securityToken, err := jmespath.Search("SecurityToken", data) 683 if err != nil { 684 err = fmt.Errorf("refresh Ecs sts token err, fail to get SecurityToken: %w", err) 685 return 686 } 687 688 if accessKeyId == nil || accessKeySecret == nil || securityToken == nil { 689 err = fmt.Errorf("there is no any available accesskey, secret and security token for Ecs role %s", ecsRoleName) 690 return 691 } 692 693 return accessKeyId.(string), accessKeySecret.(string), securityToken.(string), nil 694 } 695 696 func getHttpProxyUrl() *url.URL { 697 for _, v := range []string{"HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"} { 698 value := strings.Trim(os.Getenv(v), " ") 699 if value != "" { 700 if !regexp.MustCompile(`^http(s)?://`).MatchString(value) { 701 value = fmt.Sprintf("https://%s", value) 702 } 703 proxyUrl, err := url.Parse(value) 704 if err == nil { 705 return proxyUrl 706 } 707 break 708 } 709 } 710 return nil 711 }