hyapp-server/services/wallet-service/internal/app/outbox_archive_credentials.go

103 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package app
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
cos "github.com/tencentyun/cos-go-sdk-v5"
)
const cvmCAMCredentialMetadataBaseURL = "http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials"
type cvmCAMCredential struct {
TmpSecretID string `json:"TmpSecretId"`
TmpSecretKey string `json:"TmpSecretKey"`
Token string `json:"Token"`
ExpiredTime int64 `json:"ExpiredTime"`
Code string `json:"Code"`
}
// cvmCAMCredentialProvider 在凭证过期前 5 分钟刷新,整个结构不暴露凭证方法,避免误打日志。
type cvmCAMCredentialProvider struct {
roleName string
client *http.Client
mu sync.Mutex
cached cvmCAMCredential
}
func newCVMCAMCredentialProvider(roleName string) *cvmCAMCredentialProvider {
return &cvmCAMCredentialProvider{
roleName: strings.TrimSpace(roleName),
// metadata 是启动/请求链路的外部依赖,必须在 2 秒内失败,不能把 COS 归档 goroutine 长时间卡死。
client: &http.Client{Timeout: 2 * time.Second},
}
}
func (p *cvmCAMCredentialProvider) get(ctx context.Context) (string, string, string, error) {
p.mu.Lock()
defer p.mu.Unlock()
now := time.Now().UTC().Unix()
if p.cached.ExpiredTime > now+int64((5*time.Minute).Seconds()) {
return p.cached.TmpSecretID, p.cached.TmpSecretKey, p.cached.Token, nil
}
previous := p.cached
if p.roleName == "" {
return "", "", "", fmt.Errorf("wallet outbox archive CVM CAM role name is empty")
}
metadataURL := cvmCAMCredentialMetadataBaseURL + "/" + url.PathEscape(p.roleName)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil)
if err != nil {
return "", "", "", err
}
response, err := p.client.Do(request)
if err != nil {
if previous.ExpiredTime > now {
return previous.TmpSecretID, previous.TmpSecretKey, previous.Token, nil
}
return "", "", "", fmt.Errorf("load wallet outbox archive CVM role credential: %w", err)
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode > 299 {
// 不读取/回传 metadata 错误体,避免平台异常响应携带临时凭证。
if previous.ExpiredTime > now {
return previous.TmpSecretID, previous.TmpSecretKey, previous.Token, nil
}
return "", "", "", fmt.Errorf("load wallet outbox archive CVM role credential: metadata status %d", response.StatusCode)
}
var credential cvmCAMCredential
if err := json.NewDecoder(response.Body).Decode(&credential); err != nil {
return "", "", "", fmt.Errorf("decode wallet outbox archive CVM role credential: %w", err)
}
if credential.Code != "Success" || strings.TrimSpace(credential.TmpSecretID) == "" || strings.TrimSpace(credential.TmpSecretKey) == "" || strings.TrimSpace(credential.Token) == "" || credential.ExpiredTime <= now+30 {
return "", "", "", fmt.Errorf("wallet outbox archive CVM role returned incomplete or expired credential")
}
p.cached = credential
return credential.TmpSecretID, credential.TmpSecretKey, credential.Token, nil
}
// cvmRoleAuthorizationTransport 每次签名前从缓存 provider 取 STS只有刷新窗口才访问 metadata。
type cvmRoleAuthorizationTransport struct {
provider *cvmCAMCredentialProvider
transport http.RoundTripper
}
func (t *cvmRoleAuthorizationTransport) GetCredential() (string, string, string, error) {
return t.provider.get(context.Background())
}
func (t *cvmRoleAuthorizationTransport) RoundTrip(request *http.Request) (*http.Response, error) {
secretID, secretKey, token, err := t.provider.get(request.Context())
if err != nil {
return nil, err
}
return (&cos.AuthorizationTransport{
SecretID: secretID, SecretKey: secretKey, SessionToken: token, Transport: t.transport,
}).RoundTrip(request)
}