diff --git a/.changelog/2767.txt b/.changelog/2767.txt new file mode 100644 index 0000000000..63e7b1c933 --- /dev/null +++ b/.changelog/2767.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +provider: support cam role name auth +``` diff --git a/tencentcloud/common/common.go b/tencentcloud/common/common.go index 019337d5ee..53f87c4a09 100644 --- a/tencentcloud/common/common.go +++ b/tencentcloud/common/common.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" "io/ioutil" "log" + "net/http" "os" "os/user" "path/filepath" @@ -32,6 +34,15 @@ var ContextNil context.Context = nil type contextLogId string +type CAMResponse struct { + TmpSecretId string `json:"TmpSecretId"` + TmpSecretKey string `json:"TmpSecretKey"` + ExpiredTime int64 `json:"ExpiredTime"` + Expiration string `json:"Expiration"` + Token string `json:"Token"` + Code string `json:"Code"` +} + const LogIdKey = contextLogId("logId") const ( @@ -612,3 +623,36 @@ func ShortRegionNameParse(shortRegion string) string { } return regionMap[shortRegion] } + +func GetAuthFromCAM(roleName string) (camResp *CAMResponse, err error) { + url := fmt.Sprintf("http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/%s", roleName) + log.Printf("[CRITAL] Request CAM security credentials url: %s\n", url) + // maximum waiting time + client := &http.Client{Timeout: 2 * time.Second} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + resp, err := client.Do(req) + if err != nil { + log.Printf("[CRITAL] Request CAM security credentials resp err: %s", err.Error()) + return + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("[CRITAL] Request CAM security credentials body read err: %s", err.Error()) + return + } + + err = json.Unmarshal(body, &camResp) + if err != nil { + log.Printf("[CRITAL] Request CAM security credentials resp json err: %s", err.Error()) + return + } + + return +} diff --git a/tencentcloud/provider.go b/tencentcloud/provider.go index d0b8b12816..04aa735619 100644 --- a/tencentcloud/provider.go +++ b/tencentcloud/provider.go @@ -132,6 +132,7 @@ const ( PROVIDER_ASSUME_ROLE_WEB_IDENTITY_TOKEN = "TENCENTCLOUD_ASSUME_ROLE_WEB_IDENTITY_TOKEN" PROVIDER_SHARED_CREDENTIALS_DIR = "TENCENTCLOUD_SHARED_CREDENTIALS_DIR" PROVIDER_PROFILE = "TENCENTCLOUD_PROFILE" + PROVIDER_CAM_ROLE_NAME = "TENCENTCLOUD_CAM_ROLE_NAME" ) const ( @@ -161,13 +162,13 @@ func Provider() *schema.Provider { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_ID, nil), - Description: "This is the TencentCloud access key. It must be provided, but it can also be sourced from the `TENCENTCLOUD_SECRET_ID` environment variable.", + Description: "This is the TencentCloud access key. It can also be sourced from the `TENCENTCLOUD_SECRET_ID` environment variable.", }, "secret_key": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_KEY, nil), - Description: "This is the TencentCloud secret key. It must be provided, but it can also be sourced from the `TENCENTCLOUD_SECRET_KEY` environment variable.", + Description: "This is the TencentCloud secret key. It can also be sourced from the `TENCENTCLOUD_SECRET_KEY` environment variable.", Sensitive: true, }, "security_token": { @@ -181,7 +182,7 @@ func Provider() *schema.Provider { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc(PROVIDER_REGION, nil), - Description: "This is the TencentCloud region. It must be provided, but it can also be sourced from the `TENCENTCLOUD_REGION` environment variables. The default input value is ap-guangzhou.", + Description: "This is the TencentCloud region. It can also be sourced from the `TENCENTCLOUD_REGION` environment variables. The default input value is ap-guangzhou.", }, "protocol": { Type: schema.TypeString, @@ -337,6 +338,12 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc(PROVIDER_PROFILE, nil), Description: "The profile name as set in the shared credentials. It can also be sourced from the `TENCENTCLOUD_PROFILE` environment variable. If not set, the default profile created with `tccli configure` will be used.", }, + "cam_role_name": { + Type: schema.TypeString, + Optional: true, + 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.", + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -2109,6 +2116,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { region string protocol string domain string + camRoleName string ) needSecret := true @@ -2147,6 +2155,10 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { domain = v.(string) } + if v, ok := d.GetOk("cam_role_name"); ok { + camRoleName = v.(string) + } + // standard client var tcClient TencentCloudClient tcClient.apiV3Conn = &connectivity.TencentCloudClient{ @@ -2160,6 +2172,12 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { Domain: domain, } + // get auth from CAM role name + if camRoleName != "" { + needSecret = false + _ = genClientWithCAM(&tcClient, camRoleName) + } + var ( assumeRoleArn string assumeRoleSessionName string @@ -2281,6 +2299,22 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return &tcClient, nil } +func genClientWithCAM(tcClient *TencentCloudClient, roleName string) error { + camResp, err := tccommon.GetAuthFromCAM(roleName) + if err != nil { + return err + } + + // using STS credentials + tcClient.apiV3Conn.Credential = sdkcommon.NewTokenCredential( + camResp.TmpSecretId, + camResp.TmpSecretKey, + camResp.Token, + ) + + return nil +} + func genClientWithSTS(tcClient *TencentCloudClient, assumeRoleArn, assumeRoleSessionName string, assumeRoleSessionDuration int, assumeRolePolicy string) error { // applying STS credentials request := sdksts.NewAssumeRoleRequest()