Skip to content

Commit

Permalink
add new auth for tencentcloud cos backend
Browse files Browse the repository at this point in the history
  • Loading branch information
SevenEarth committed Oct 31, 2024
1 parent 3d4dab0 commit dba4dbd
Show file tree
Hide file tree
Showing 3 changed files with 373 additions and 13 deletions.
283 changes: 274 additions & 9 deletions internal/backend/remote-state/cos/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ package cos

import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"

"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
"github.com/mitchellh/go-homedir"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
sts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813"
Expand All @@ -35,6 +40,14 @@ const (
PROVIDER_ASSUME_ROLE_ARN = "TENCENTCLOUD_ASSUME_ROLE_ARN"
PROVIDER_ASSUME_ROLE_SESSION_NAME = "TENCENTCLOUD_ASSUME_ROLE_SESSION_NAME"
PROVIDER_ASSUME_ROLE_SESSION_DURATION = "TENCENTCLOUD_ASSUME_ROLE_SESSION_DURATION"
PROVIDER_ASSUME_ROLE_EXTERNAL_ID = "TENCENTCLOUD_ASSUME_ROLE_EXTERNAL_ID"
PROVIDER_SHARED_CREDENTIALS_DIR = "TENCENTCLOUD_SHARED_CREDENTIALS_DIR"
PROVIDER_PROFILE = "TENCENTCLOUD_PROFILE"
PROVIDER_CAM_ROLE_NAME = "TENCENTCLOUD_CAM_ROLE_NAME"
)

const (
DEFAULT_PROFILE = "default"
)

// Backend implements "backend".Backend for tencentCloud cos
Expand All @@ -56,6 +69,15 @@ type Backend struct {
domain 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"`
}

// New creates a new backend for TencentCloud cos remote state.
func New() backend.Backend {
s := &schema.Backend{
Expand Down Expand Up @@ -191,9 +213,33 @@ func New() backend.Backend {
Optional: true,
Description: "A more restrictive policy when making the AssumeRole call. Its content must not contains `principal` elements. Notice: more syntax references, please refer to: [policies syntax logic](https://intl.cloud.tencent.com/document/product/598/10603).",
},
"external_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc(PROVIDER_ASSUME_ROLE_EXTERNAL_ID, nil),
Description: "External role ID, which can be obtained by clicking the role name in the CAM console. It can contain 2-128 letters, digits, and symbols (=,.@:/-). Regex: [\\w+=,.@:/-]*. It can be sourced from the `TENCENTCLOUD_ASSUME_ROLE_EXTERNAL_ID`.",
},
},
},
},
"shared_credentials_dir": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SHARED_CREDENTIALS_DIR, nil),
Description: "The directory of the shared credentials. It can also be sourced from the `TENCENTCLOUD_SHARED_CREDENTIALS_DIR` environment variable. If not set this defaults to ~/.tccli.",
},
"profile": {
Type: schema.TypeString,
Optional: true,
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.",
},
},
}

Expand Down Expand Up @@ -275,9 +321,56 @@ func (b *Backend) configure(ctx context.Context) error {
return err
}

secretId := data.Get("secret_id").(string)
secretKey := data.Get("secret_key").(string)
securityToken := data.Get("security_token").(string)
var getProviderConfig = func(key string) string {
var str string
value, err := getConfigFromProfile(data, key)
if err == nil && value != nil {
str = value.(string)
}

return str
}

var (
secretId string
secretKey string
securityToken string
)

// get auth from tf/env
if v, ok := data.GetOk("secret_id"); ok {
secretId = v.(string)
}

if v, ok := data.GetOk("secret_key"); ok {
secretKey = v.(string)
}

if v, ok := data.GetOk("security_token"); ok {
securityToken = v.(string)
}

// get auth from tccli
if secretId == "" && secretKey == "" && securityToken == "" {
secretId = getProviderConfig("secretId")
secretKey = getProviderConfig("secretKey")
securityToken = getProviderConfig("token")
}

// get auth from CAM role name
if v, ok := data.GetOk("cam_role_name"); ok {
camRoleName := v.(string)
if camRoleName != "" {
camResp, err := getAuthFromCAM(camRoleName)
if err != nil {
return err
}

secretId = camResp.TmpSecretId
secretKey = camResp.TmpSecretKey
securityToken = camResp.Token
}
}

// init credential by AKSK & TOKEN
b.credential = common.NewTokenCredential(secretId, secretKey, securityToken)
Expand All @@ -304,23 +397,70 @@ func (b *Backend) configure(ctx context.Context) error {
}

func handleAssumeRole(data *schema.ResourceData, b *Backend) error {
var (
assumeRoleArn string
assumeRoleSessionName string
assumeRoleSessionDuration int
assumeRolePolicy string
assumeRoleExternalId string
)

// get assume role from credential
if providerConfig["role-arn"] != nil {
assumeRoleArn = providerConfig["role-arn"].(string)
}

if providerConfig["role-session-name"] != nil {
assumeRoleSessionName = providerConfig["role-session-name"].(string)
}

if assumeRoleArn != "" && assumeRoleSessionName != "" {
assumeRoleSessionDuration = 7200
}

// get assume role from env
envRoleArn := os.Getenv(PROVIDER_ASSUME_ROLE_ARN)
envSessionName := os.Getenv(PROVIDER_ASSUME_ROLE_SESSION_NAME)
if envRoleArn != "" && envSessionName != "" {
assumeRoleArn = envRoleArn
assumeRoleSessionName = envSessionName
if envSessionDuration := os.Getenv(PROVIDER_ASSUME_ROLE_SESSION_DURATION); envSessionDuration != "" {
var err error
assumeRoleSessionDuration, err = strconv.Atoi(envSessionDuration)
if err != nil {
return err
}
}

if assumeRoleSessionDuration == 0 {
assumeRoleSessionDuration = 7200
}

assumeRoleExternalId = os.Getenv(PROVIDER_ASSUME_ROLE_EXTERNAL_ID)
}

// get assume role from tf
assumeRoleList := data.Get("assume_role").(*schema.Set).List()
if len(assumeRoleList) == 1 {
assumeRole := assumeRoleList[0].(map[string]interface{})
assumeRoleArn := assumeRole["role_arn"].(string)
assumeRoleSessionName := assumeRole["session_name"].(string)
assumeRoleSessionDuration := assumeRole["session_duration"].(int)
assumeRolePolicy := assumeRole["policy"].(string)
assumeRoleArn = assumeRole["role_arn"].(string)
assumeRoleSessionName = assumeRole["session_name"].(string)
assumeRoleSessionDuration = assumeRole["session_duration"].(int)
assumeRolePolicy = assumeRole["policy"].(string)
assumeRoleExternalId = assumeRole["external_id"].(string)
}

err := b.updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName, assumeRoleSessionDuration, assumeRolePolicy)
if assumeRoleArn != "" && assumeRoleSessionName != "" {
err := b.updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName, assumeRoleSessionDuration, assumeRolePolicy, assumeRoleExternalId)
if err != nil {
return err
}
}

return nil
}

func (b *Backend) updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName string, assumeRoleSessionDuration int, assumeRolePolicy string) error {
func (b *Backend) updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName string, assumeRoleSessionDuration int, assumeRolePolicy string, assumeRoleExternalId string) error {
// assume role by STS
request := sts.NewAssumeRoleRequest()
request.RoleArn = &assumeRoleArn
Expand All @@ -332,6 +472,10 @@ func (b *Backend) updateCredentialWithSTS(assumeRoleArn, assumeRoleSessionName s
request.Policy = &policy
}

if assumeRoleExternalId != "" {
request.ExternalId = &assumeRoleExternalId
}

response, err := b.UseStsClient().AssumeRole(request)
if err != nil {
return err
Expand Down Expand Up @@ -382,3 +526,124 @@ func (b *Backend) NewClientProfile(timeout int) *profile.ClientProfile {

return cpf
}

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
}

var providerConfig map[string]interface{}

func getConfigFromProfile(d *schema.ResourceData, ProfileKey string) (interface{}, error) {
if providerConfig == nil {
var (
profile string
sharedCredentialsDir string
credentialPath string
configurePath string
)

if v, ok := d.GetOk("profile"); ok {
profile = v.(string)
} else {
profile = DEFAULT_PROFILE
}

if v, ok := d.GetOk("shared_credentials_dir"); ok {
sharedCredentialsDir = v.(string)
}

tmpSharedCredentialsDir, err := homedir.Expand(sharedCredentialsDir)
if err != nil {
return nil, err
}

if tmpSharedCredentialsDir == "" {
credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("HOME"), profile)
configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("HOME"), profile)
if runtime.GOOS == "windows" {
credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("USERPROFILE"), profile)
configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("USERPROFILE"), profile)
}
} else {
credentialPath = fmt.Sprintf("%s/%s.credential", tmpSharedCredentialsDir, profile)
configurePath = fmt.Sprintf("%s/%s.configure", tmpSharedCredentialsDir, profile)
}

providerConfig = make(map[string]interface{})
_, err = os.Stat(credentialPath)
if !os.IsNotExist(err) {
data, err := ioutil.ReadFile(credentialPath)
if err != nil {
return nil, err
}

config := map[string]interface{}{}
err = json.Unmarshal(data, &config)
if err != nil {
return nil, err
}

for k, v := range config {
providerConfig[k] = strings.TrimSpace(v.(string))
}
}

_, err = os.Stat(configurePath)
if !os.IsNotExist(err) {
data, err := ioutil.ReadFile(configurePath)
if err != nil {
return nil, err
}

config := map[string]interface{}{}
err = json.Unmarshal(data, &config)
if err != nil {
return nil, err
}

outerLoop:
for k, v := range config {
if k == "_sys_param" {
tmpMap := v.(map[string]interface{})
for tmpK, tmpV := range tmpMap {
if tmpK == "region" {
providerConfig[tmpK] = strings.TrimSpace(tmpV.(string))
break outerLoop
}
}
}
}
}
}

return providerConfig[ProfileKey], nil
}
1 change: 1 addition & 0 deletions internal/backend/remote-state/cos/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.23.1
require (
github.com/hashicorp/terraform v0.0.0-00010101000000-000000000000
github.com/hashicorp/terraform/internal/legacy v0.0.0-00010101000000-000000000000
github.com/mitchellh/go-homedir v1.1.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.588
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.588
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.233
Expand Down
Loading

0 comments on commit dba4dbd

Please sign in to comment.