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  }