Skip to content

Commit a6253d6

Browse files
authored
Merge pull request #3 from tencentcloudstack/fix/oauth
feat: support tccli oauth
2 parents 57cc7ec + eb27ac3 commit a6253d6

File tree

3 files changed

+341
-133
lines changed

3 files changed

+341
-133
lines changed

builder/tencentcloud/cvm/access_config.go

+11-133
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,11 @@ package cvm
88

99
import (
1010
"context"
11-
"encoding/json"
1211
"fmt"
13-
"io/ioutil"
1412
"os"
15-
"runtime"
1613
"strconv"
17-
"strings"
1814

1915
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
20-
"github.com/mitchellh/go-homedir"
2116
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
2217
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
2318
)
@@ -222,35 +217,23 @@ func (cf *TencentCloudAccessConfig) Config() error {
222217
cf.SharedCredentialsDir = os.Getenv(PACKER_SHARED_CREDENTIALS_DIR)
223218
}
224219

225-
var value map[string]interface{}
226-
var err error
227-
getProviderConfig := func(key string) string {
228-
var str string
229-
if value != nil {
230-
if v, ok := value[key]; ok {
231-
str = v.(string)
232-
}
233-
}
234-
return str
235-
}
236-
237220
if cf.SecretId == "" || cf.SecretKey == "" {
238-
value, err = loadConfigProfile(cf)
221+
profile, err := loadConfigProfile(cf)
239222
if err != nil {
240223
return err
241224
}
242225

243226
if cf.SecretId == "" {
244-
cf.SecretId = getProviderConfig("secretId")
227+
cf.SecretId = profile.SecretId
245228
}
246229
if cf.SecretKey == "" {
247-
cf.SecretKey = getProviderConfig("secretKey")
230+
cf.SecretKey = profile.SecretKey
248231
}
249232
if cf.SecurityToken == "" {
250-
cf.SecurityToken = getProviderConfig("securityToken")
233+
cf.SecurityToken = profile.Token
251234
}
252235
if cf.Region == "" {
253-
cf.Region = getProviderConfig("region")
236+
cf.Region = profile.Region
254237
}
255238
}
256239

@@ -293,33 +276,29 @@ func (cf *TencentCloudAccessConfig) Config() error {
293276
}
294277

295278
if cf.AssumeRole.RoleArn == "" || cf.AssumeRole.SessionName == "" {
296-
value, err = loadConfigProfile(cf)
279+
profile, err := loadConfigProfile(cf)
297280
if err != nil {
298281
return err
299282
}
300283

301284
if cf.AssumeRole.RoleArn == "" {
302-
roleArn := getProviderConfig("role-arn")
285+
roleArn := profile.RoleArn
303286
if roleArn != "" {
304287
cf.AssumeRole.RoleArn = roleArn
305288
}
306289
}
307290

308291
if cf.AssumeRole.SessionName == "" {
309-
sessionName := getProviderConfig("role-session-name")
292+
sessionName := profile.RoleSessionName
310293
if sessionName != "" {
311294
cf.AssumeRole.SessionName = sessionName
312295
}
313296
}
314297

315298
if cf.AssumeRole.SessionDuration == 0 {
316-
duration := getProviderConfig("role-session-duration")
317-
if duration != "" {
318-
durationInt, err := strconv.Atoi(duration)
319-
if err != nil {
320-
return err
321-
}
322-
cf.AssumeRole.SessionDuration = durationInt
299+
duration := profile.RoleSessionDuration
300+
if duration != 0 {
301+
cf.AssumeRole.SessionDuration = int(duration)
323302
}
324303
}
325304
}
@@ -344,104 +323,3 @@ func validRegion(region string) error {
344323

345324
return fmt.Errorf("unknown region: %s", region)
346325
}
347-
348-
func getProfilePatch(cf *TencentCloudAccessConfig) (string, string, error) {
349-
var (
350-
profile string
351-
sharedCredentialsDir string
352-
credentialPath string
353-
configurePath string
354-
)
355-
356-
if cf.Profile != "" {
357-
profile = cf.Profile
358-
} else {
359-
profile = DEFAULT_PROFILE
360-
}
361-
362-
if cf.SharedCredentialsDir != "" {
363-
sharedCredentialsDir = cf.SharedCredentialsDir
364-
}
365-
366-
tmpSharedCredentialsDir, err := homedir.Expand(sharedCredentialsDir)
367-
if err != nil {
368-
return "", "", err
369-
}
370-
371-
if tmpSharedCredentialsDir == "" {
372-
credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("HOME"), profile)
373-
configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("HOME"), profile)
374-
if runtime.GOOS == "windows" {
375-
credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("USERPROFILE"), profile)
376-
configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("USERPROFILE"), profile)
377-
}
378-
} else {
379-
credentialPath = fmt.Sprintf("%s/%s.credential", tmpSharedCredentialsDir, profile)
380-
configurePath = fmt.Sprintf("%s/%s.configure", tmpSharedCredentialsDir, profile)
381-
}
382-
383-
return credentialPath, configurePath, nil
384-
}
385-
386-
func loadConfigProfile(cf *TencentCloudAccessConfig) (map[string]interface{}, error) {
387-
var (
388-
credentialPath string
389-
configurePath string
390-
)
391-
392-
credentialPath, configurePath, err := getProfilePatch(cf)
393-
if err != nil {
394-
return nil, err
395-
}
396-
397-
packerConfig := make(map[string]interface{})
398-
_, err = os.Stat(credentialPath)
399-
if !os.IsNotExist(err) {
400-
data, err := ioutil.ReadFile(credentialPath)
401-
if err != nil {
402-
return nil, err
403-
}
404-
405-
config := map[string]interface{}{}
406-
err = json.Unmarshal(data, &config)
407-
if err != nil {
408-
return nil, fmt.Errorf("credential file unmarshal failed, %s", err)
409-
}
410-
411-
for k, v := range config {
412-
packerConfig[k] = strings.TrimSpace(v.(string))
413-
}
414-
} else {
415-
return nil, fmt.Errorf("please set a valid secret_id and secret_key or shared_credentials_dir, %s", err)
416-
}
417-
_, err = os.Stat(configurePath)
418-
if !os.IsNotExist(err) {
419-
data, err := ioutil.ReadFile(configurePath)
420-
if err != nil {
421-
return nil, err
422-
}
423-
424-
config := map[string]interface{}{}
425-
err = json.Unmarshal(data, &config)
426-
if err != nil {
427-
return nil, fmt.Errorf("configure file unmarshal failed, %s", err)
428-
}
429-
430-
outerLoop:
431-
for k, v := range config {
432-
if k == "_sys_param" {
433-
tmpMap := v.(map[string]interface{})
434-
for tmpK, tmpV := range tmpMap {
435-
if tmpK == "region" {
436-
packerConfig[tmpK] = strings.TrimSpace(tmpV.(string))
437-
break outerLoop
438-
}
439-
}
440-
}
441-
}
442-
} else {
443-
return nil, fmt.Errorf("please set a valid region or shared_credentials_dir, %s", err)
444-
}
445-
446-
return packerConfig, nil
447-
}

builder/tencentcloud/cvm/oauth.go

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package cvm
5+
6+
import (
7+
"bytes"
8+
"encoding/json"
9+
"fmt"
10+
"net/http"
11+
"time"
12+
13+
"github.com/google/uuid"
14+
)
15+
16+
const _API_ENDPOINT = "https://cli.cloud.tencent.com"
17+
18+
func GetOauthConfig(p *Profile) error {
19+
if p.Oauth == nil || p.Oauth.RefreshToken == "" || p.Oauth.OpenId == "" {
20+
return fmt.Errorf("Oauth authentication information is not configured correctly")
21+
}
22+
client := NewAPIClient()
23+
24+
expired := false
25+
if p.Oauth.ExpiresAt != 0 {
26+
now := time.Now()
27+
futureTime := now.Add(30 * time.Second)
28+
targetTime := time.Unix(p.Oauth.ExpiresAt, 0)
29+
if targetTime.After(futureTime) {
30+
expired = true
31+
}
32+
}
33+
34+
if expired {
35+
response, err := client.RefreshUserToken(p.Oauth.RefreshToken, p.Oauth.OpenId, p.Oauth.Site)
36+
if err != nil {
37+
return err
38+
}
39+
if response != nil {
40+
if response.AccessToken != "" {
41+
p.Oauth.AccessToken = response.AccessToken
42+
}
43+
if response.ExpiresAt != 0 {
44+
p.Oauth.ExpiresAt = response.ExpiresAt
45+
}
46+
}
47+
}
48+
49+
// 获取临时token
50+
response, err := client.GetThirdPartyFederationToken(p.Oauth.AccessToken, p.Oauth.Site)
51+
if err != nil {
52+
return err
53+
}
54+
if response != nil {
55+
if response.SecretId != "" {
56+
p.SecretId = response.SecretId
57+
}
58+
if response.SecretKey != "" {
59+
p.SecretKey = response.SecretKey
60+
}
61+
if response.Token != "" {
62+
p.Token = response.Token
63+
}
64+
}
65+
66+
return nil
67+
}
68+
69+
type APIClient struct {
70+
Client *http.Client
71+
}
72+
73+
// 创建新的APIClient
74+
func NewAPIClient() *APIClient {
75+
return &APIClient{
76+
Client: &http.Client{
77+
Timeout: 10 * time.Second,
78+
},
79+
}
80+
}
81+
82+
// GetThirdPartyFederationToken Obtaining a temporary user certificate
83+
func (c *APIClient) GetThirdPartyFederationToken(accessToken, site string) (*GetTempCredResponse, error) {
84+
apiEndpoint := _API_ENDPOINT + "/get_temp_cred"
85+
traceId := uuid.New().String()
86+
87+
body := GetTempCredRequest{
88+
TraceId: traceId,
89+
AccessToken: accessToken,
90+
Site: site,
91+
}
92+
93+
jsonData, err := json.Marshal(body)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to marshal request body: %v", err)
96+
}
97+
98+
req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonData))
99+
if err != nil {
100+
return nil, fmt.Errorf("failed to create request: %v", err)
101+
}
102+
103+
req.Header.Set("Content-Type", "application/json")
104+
105+
resp, err := c.Client.Do(req)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to send request: %v", err)
108+
}
109+
defer resp.Body.Close()
110+
111+
response := &GetTempCredResponse{}
112+
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
113+
return nil, fmt.Errorf("failed to decode response body: %v", err)
114+
}
115+
116+
if response.Error != "" {
117+
return nil, fmt.Errorf("get_temp_cred: %s", response.Error)
118+
}
119+
120+
return response, err
121+
}
122+
123+
// RefreshUserToken Refresh user third-party access_token
124+
func (c *APIClient) RefreshUserToken(refToken, openId, site string) (*RefreshTokenResponse, error) {
125+
apiEndpoint := _API_ENDPOINT + "/refresh_user_token"
126+
127+
traceId := uuid.New().String()
128+
129+
body := RefreshTokenRequest{
130+
TraceId: traceId,
131+
RefreshToken: refToken,
132+
OpenId: openId,
133+
Site: site,
134+
}
135+
136+
jsonData, err := json.Marshal(body)
137+
if err != nil {
138+
return nil, fmt.Errorf("failed to marshal request body: %v", err)
139+
}
140+
141+
// 创建POST请求
142+
req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonData))
143+
if err != nil {
144+
return nil, fmt.Errorf("failed to create request: %v", err)
145+
}
146+
147+
req.Header.Set("Content-Type", "application/json")
148+
149+
resp, err := c.Client.Do(req)
150+
if err != nil {
151+
return nil, fmt.Errorf("failed to send request: %v", err)
152+
}
153+
defer resp.Body.Close()
154+
155+
response := &RefreshTokenResponse{}
156+
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
157+
return nil, fmt.Errorf("failed to decode response body: %v", err)
158+
}
159+
160+
if response.Error != "" {
161+
return nil, fmt.Errorf("refresh_user_token: %s", response.Error)
162+
}
163+
return response, nil
164+
}
165+
166+
type RefreshTokenRequest struct {
167+
TraceId string `json:"TraceId"`
168+
RefreshToken string `json:"RefreshToken"`
169+
OpenId string `json:"OpenId"`
170+
Site string `json:"Site"`
171+
}
172+
173+
type RefreshTokenResponse struct {
174+
AccessToken string `json:"AccessToken"`
175+
ExpiresAt int64 `json:"ExpiresAt"`
176+
Error string `json:"Error"`
177+
}
178+
179+
type GetTempCredRequest struct {
180+
TraceId string `json:"TraceId"`
181+
AccessToken string `json:"AccessToken"`
182+
Site string `json:"Site"`
183+
}
184+
185+
type GetTempCredResponse struct {
186+
SecretId string `json:"SecretId"`
187+
SecretKey string `json:"SecretKey"`
188+
Token string `json:"Token"`
189+
ExpiresAt int64 `json:"ExpiresAt"`
190+
Error string `json:"Error"`
191+
}

0 commit comments

Comments
 (0)