diff --git a/.changelog/3030.txt b/.changelog/3030.txt new file mode 100644 index 0000000000..66ad16d53e --- /dev/null +++ b/.changelog/3030.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +provider: support `allowed_account_ids` and `forbidden_account_ids` +``` \ No newline at end of file diff --git a/tencentcloud/provider.go b/tencentcloud/provider.go index b3981b7787..787c183bc0 100644 --- a/tencentcloud/provider.go +++ b/tencentcloud/provider.go @@ -14,6 +14,7 @@ import ( "github.com/mitchellh/go-homedir" sdkcommon "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" commonJson "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/json" + sdkprofile "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" sdksts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813" tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" @@ -370,6 +371,20 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc(PROVIDER_CAM_ROLE_NAME, nil), Description: "The name of the CVM instance CAM role. It can be sourced from the `TENCENTCLOUD_CAM_ROLE_NAME` environment variable.", }, + "allowed_account_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ConflictsWith: []string{"forbidden_account_ids", "assume_role_with_saml", "assume_role_with_web_identity"}, + Description: "List of allowed TencentCloud account IDs to prevent you from mistakenly using the wrong one (and potentially end up destroying a live environment). Conflicts with `forbidden_account_ids`, If use `assume_role_with_saml` or `assume_role_with_web_identity`, it is not supported.", + }, + "forbidden_account_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ConflictsWith: []string{"allowed_account_ids", "assume_role_with_saml", "assume_role_with_web_identity"}, + Description: "List of forbidden TencentCloud account IDs to prevent you from mistakenly using the wrong one (and potentially end up destroying a live environment). Conflicts with `allowed_account_ids`, If use `assume_role_with_saml` or `assume_role_with_web_identity`, it is not supported.", + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -2211,17 +2226,20 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { } var ( - secretId string - secretKey string - securityToken string - region string - protocol string - domain string - cosDomain string - camRoleName string + secretId string + secretKey string + securityToken string + region string + protocol string + domain string + cosDomain string + camRoleName string + allowedAccountIds []string + forbiddenAccountIds []string + needSecret = true + needAccountFilter = false ) - needSecret := true if v, ok := d.GetOk("secret_id"); ok { secretId = v.(string) } @@ -2281,6 +2299,22 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { CosDomain: cosDomain, } + if v, ok := d.GetOk("allowed_account_ids"); ok && v.(*schema.Set).Len() > 0 { + for _, v := range v.(*schema.Set).List() { + allowedAccountIds = append(allowedAccountIds, v.(string)) + } + + needAccountFilter = true + } + + if v, ok := d.GetOk("forbidden_account_ids"); ok && v.(*schema.Set).Len() > 0 { + for _, v := range v.(*schema.Set).List() { + forbiddenAccountIds = append(forbiddenAccountIds, v.(string)) + } + + needAccountFilter = true + } + // get auth from CAM role name if camRoleName != "" { needSecret = false @@ -2424,6 +2458,20 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, fmt.Errorf("Please set your `secret_id` and `secret_key`.\n") } + if needAccountFilter { + // get indentity + indentity, err := getCallerIdentity(&tcClient) + if err != nil { + return nil, err + } + + // account filter + err = verifyAccountIDAllowed(indentity, allowedAccountIds, forbiddenAccountIds) + if err != nil { + return nil, err + } + } + return &tcClient, nil } @@ -2635,3 +2683,53 @@ func genClientWithPodOidc(tcClient *TencentCloudClient) error { return nil } + +func getCallerIdentity(tcClient *TencentCloudClient) (indentity *sdksts.GetCallerIdentityResponseParams, err error) { + ak := tcClient.apiV3Conn.Credential.SecretId + sk := tcClient.apiV3Conn.Credential.SecretKey + token := tcClient.apiV3Conn.Credential.Token + region := tcClient.apiV3Conn.Region + credential := sdkcommon.NewTokenCredential(ak, sk, token) + cpf := sdkprofile.NewClientProfile() + cpf.HttpProfile.Endpoint = "sts.tencentcloudapi.com" + client, _ := sdksts.NewClient(credential, region, cpf) + request := sdksts.NewGetCallerIdentityRequest() + response, err := client.GetCallerIdentity(request) + if err != nil { + return + } + + if response == nil || response.Response == nil { + return nil, fmt.Errorf("get GetCallerIdentity failed") + } + + indentity = response.Response + return +} + +func verifyAccountIDAllowed(indentity *sdksts.GetCallerIdentityResponseParams, allowedAccountIds, forbiddenAccountIds []string) error { + accountId := *indentity.AccountId + if len(allowedAccountIds) > 0 { + found := false + for _, allowedAccountID := range allowedAccountIds { + if accountId == allowedAccountID { + found = true + break + } + } + + if !found { + return fmt.Errorf("TencentCloud account ID not allowed: %s", accountId) + } + } + + if len(forbiddenAccountIds) > 0 { + for _, forbiddenAccountID := range forbiddenAccountIds { + if accountId == forbiddenAccountID { + return fmt.Errorf("TencentCloud account ID not allowed: %s", accountId) + } + } + } + + return nil +} diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index be98dff0f1..8807e93529 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -121,6 +121,28 @@ provider "tencentcloud" { } ``` +Use `allowed_account_ids` or `forbidden_account_ids` + +```hcl +provider "tencentcloud" { + secret_id = "my-secret-id" + secret_key = "my-secret-key" + region = "ap-guangzhou" + + allowed_account_ids = ["100023201586", "100023201349"] +} +``` + +```hcl +provider "tencentcloud" { + secret_id = "my-secret-id" + secret_key = "my-secret-key" + region = "ap-guangzhou" + + forbidden_account_ids = ["100023201223"] +} +``` + ### Environment variables You can provide your credentials via `TENCENTCLOUD_SECRET_ID` and `TENCENTCLOUD_SECRET_KEY` environment variables, @@ -359,6 +381,8 @@ In addition to generic provider arguments (e.g. alias and version), the followin * `protocol` - (Optional, Available in 1.37.0+) The protocol of the API request. Valid values: `HTTP` and `HTTPS`. Default is `HTTPS`. * `domain` - (Optional, Available in 1.37.0+) The root domain of the API request, Default is `tencentcloudapi.com`. * `cam_role_name` - (Optional, Available in 1.81.117+) The name of the CVM instance CAM role. It can be sourced from the `TENCENTCLOUD_CAM_ROLE_NAME` environment variable. +* `allowed_account_ids` - (Optional) List of allowed TencentCloud account IDs to prevent you from mistakenly using the wrong one (and potentially end up destroying a live environment). Conflicts with `forbidden_account_ids`, If use `assume_role_with_saml` or `assume_role_with_web_identity`, it is not supported. +* `forbidden_account_ids` - (Optional) List of forbidden TencentCloud account IDs to prevent you from mistakenly using the wrong one (and potentially end up destroying a live environment). Conflicts with `allowed_account_ids`, If use `assume_role_with_saml` or `assume_role_with_web_identity`, it is not supported. The nested `assume_role` block supports the following: * `role_arn` - (Required) The ARN of the role to assume. It can also be sourced from the `TENCENTCLOUD_ASSUME_ROLE_ARN` environment variable.