255 lines
7.7 KiB
Go
255 lines
7.7 KiB
Go
package oss
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"lone-services/pkg/utils"
|
|
|
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
|
sts20150401 "github.com/alibabacloud-go/sts-20150401/v2/client"
|
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
"github.com/alibabacloud-go/tea/tea"
|
|
)
|
|
|
|
const ossProduct = "oss"
|
|
|
|
type Config struct {
|
|
AccessKeyId string
|
|
AccessKeySecret string
|
|
RoleArn string
|
|
Region string
|
|
BucketName string
|
|
Endpoint string
|
|
StsEndpoint string
|
|
Dir string
|
|
ExpireSeconds int64
|
|
CallbackUrl string
|
|
RoleSessionName string
|
|
}
|
|
|
|
type Credentials struct {
|
|
AccessKeyId string
|
|
AccessKeySecret string
|
|
SecurityToken string
|
|
Expiration string
|
|
}
|
|
|
|
type UploadPolicy struct {
|
|
TaskId string `json:"task_id"`
|
|
Policy string `json:"policy"`
|
|
SecurityToken string `json:"security_token"`
|
|
SignatureVersion string `json:"x_oss_signature_version"`
|
|
Credential string `json:"x_oss_credential"`
|
|
Date string `json:"x_oss_date"`
|
|
Signature string `json:"signature"`
|
|
Host string `json:"host"`
|
|
Dir string `json:"dir"`
|
|
DateDir string `json:"date_dir"`
|
|
Callback string `json:"callback"`
|
|
CallbackVar string `json:"callback_var"`
|
|
ExpireSeconds int64 `json:"expire_seconds"`
|
|
}
|
|
|
|
func NewConfig(roleSessionName string) *Config {
|
|
dir := utils.GetConfigString("oss.dir")
|
|
if dir != utils.StringEmpty && !strings.HasSuffix(dir, "/") {
|
|
dir += "/"
|
|
}
|
|
expire := int64(utils.GetConfigInt("oss.expireSeconds"))
|
|
if expire <= 0 {
|
|
expire = 3600
|
|
}
|
|
region := utils.GetConfigString("oss.region")
|
|
if region == utils.StringEmpty {
|
|
region = "oss-cn-hangzhou"
|
|
}
|
|
stsEndpoint := utils.GetConfigString("oss.stsEndpoint")
|
|
if stsEndpoint == utils.StringEmpty {
|
|
stsEndpoint = "sts." + strings.TrimPrefix(region, "oss-") + ".aliyuncs.com"
|
|
}
|
|
endpoint := utils.GetConfigString("oss.endpoint")
|
|
if endpoint == utils.StringEmpty {
|
|
endpoint = "https://oss-accelerate.aliyuncs.com"
|
|
}
|
|
bucket := utils.GetConfigString("oss.bucketName")
|
|
if bucket == utils.StringEmpty {
|
|
bucket = "lone-images"
|
|
}
|
|
|
|
return &Config{
|
|
AccessKeyId: utils.GetConfigString("oss.accessKeyId"),
|
|
AccessKeySecret: utils.GetConfigString("oss.accessKeySecret"),
|
|
RoleArn: utils.GetConfigString("oss.roleArn"),
|
|
Region: region,
|
|
BucketName: bucket,
|
|
Endpoint: endpoint,
|
|
StsEndpoint: stsEndpoint,
|
|
Dir: dir,
|
|
ExpireSeconds: expire,
|
|
CallbackUrl: utils.GetConfigString("oss.callbackUrl"),
|
|
RoleSessionName: roleSessionName,
|
|
}
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
if c.AccessKeyId == utils.StringEmpty || c.AccessKeySecret == utils.StringEmpty || c.RoleArn == utils.StringEmpty {
|
|
return fmt.Errorf("oss policy config incomplete")
|
|
}
|
|
if c.CallbackUrl == utils.StringEmpty {
|
|
return fmt.Errorf("oss callbackUrl empty")
|
|
}
|
|
if c.BucketName == utils.StringEmpty {
|
|
return fmt.Errorf("oss bucketName empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func AssumeRole(cfg *Config) (*Credentials, error) {
|
|
client, err := sts20150401.NewClient(&openapi.Config{
|
|
AccessKeyId: tea.String(cfg.AccessKeyId),
|
|
AccessKeySecret: tea.String(cfg.AccessKeySecret),
|
|
Endpoint: tea.String(cfg.StsEndpoint),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create sts client: %w", err)
|
|
}
|
|
|
|
response, err := client.AssumeRoleWithOptions(&sts20150401.AssumeRoleRequest{
|
|
DurationSeconds: tea.Int64(cfg.ExpireSeconds),
|
|
RoleArn: tea.String(cfg.RoleArn),
|
|
RoleSessionName: tea.String(cfg.RoleSessionName),
|
|
}, &util.RuntimeOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("assume role: %w", err)
|
|
}
|
|
if response == nil || response.Body == nil || response.Body.Credentials == nil {
|
|
return nil, fmt.Errorf("assume role: empty credentials")
|
|
}
|
|
|
|
cred := response.Body.Credentials
|
|
return &Credentials{
|
|
AccessKeyId: tea.StringValue(cred.AccessKeyId),
|
|
AccessKeySecret: tea.StringValue(cred.AccessKeySecret),
|
|
SecurityToken: tea.StringValue(cred.SecurityToken),
|
|
Expiration: tea.StringValue(cred.Expiration),
|
|
}, nil
|
|
}
|
|
|
|
func BuildUploadPolicy(cfg *Config, taskId string, cred *Credentials) (*UploadPolicy, error) {
|
|
region := strings.TrimPrefix(strings.TrimSpace(cfg.Region), "oss-")
|
|
dateDir := time.Now().In(utils.AsiaShanghai).Format("20060102")
|
|
objectPrefix := fmt.Sprintf("%s%s/%s/", cfg.Dir, dateDir, taskId)
|
|
|
|
utcTime := time.Now().UTC()
|
|
date := utcTime.Format("20060102")
|
|
ossDate := utcTime.Format("20060102T150405Z")
|
|
expiration := utcTime.Add(time.Duration(cfg.ExpireSeconds) * time.Second)
|
|
credential := fmt.Sprintf("%s/%s/%s/%s/aliyun_v4_request", cred.AccessKeyId, date, region, ossProduct)
|
|
|
|
policyMap := map[string]any{
|
|
"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
|
|
"conditions": []any{
|
|
map[string]string{"bucket": cfg.BucketName},
|
|
map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
|
|
map[string]string{"x-oss-credential": credential},
|
|
map[string]string{"x-oss-date": ossDate},
|
|
map[string]string{"x-oss-security-token": cred.SecurityToken},
|
|
[]any{"starts-with", "$key", objectPrefix},
|
|
[]any{"content-length-range", 1, 512 * 1024 * 1024},
|
|
},
|
|
}
|
|
|
|
policyJSON, err := json.Marshal(policyMap)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal policy: %w", err)
|
|
}
|
|
policyBase64 := base64.StdEncoding.EncodeToString(policyJSON)
|
|
signature, err := signPolicyV4(cred.AccessKeySecret, date, region, policyBase64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
callbackBody := `{"bucket":${bucket},"object":${object},"etag":${etag},"size":${size},"mimeType":${mimeType},"task_id":${x:task_id}}`
|
|
callbackVars := map[string]string{"x:task_id": taskId}
|
|
|
|
return &UploadPolicy{
|
|
TaskId: taskId,
|
|
Policy: policyBase64,
|
|
SecurityToken: cred.SecurityToken,
|
|
SignatureVersion: "OSS4-HMAC-SHA256",
|
|
Credential: credential,
|
|
Date: ossDate,
|
|
Signature: signature,
|
|
Host: cfg.Endpoint,
|
|
Dir: objectPrefix,
|
|
DateDir: dateDir,
|
|
Callback: encodeCallbackBase64(cfg.CallbackUrl, callbackBody, "application/json"),
|
|
CallbackVar: encodeCallbackVarBase64(callbackVars),
|
|
ExpireSeconds: cfg.ExpireSeconds,
|
|
}, nil
|
|
}
|
|
|
|
func signPolicyV4(accessKeySecret, date, region, policyBase64 string) (string, error) {
|
|
hmacHash := func() hash.Hash { return sha256.New() }
|
|
|
|
h1 := hmac.New(hmacHash, []byte("aliyun_v4"+accessKeySecret))
|
|
if _, err := io.WriteString(h1, date); err != nil {
|
|
return "", err
|
|
}
|
|
h2 := hmac.New(hmacHash, h1.Sum(nil))
|
|
if _, err := io.WriteString(h2, region); err != nil {
|
|
return "", err
|
|
}
|
|
h3 := hmac.New(hmacHash, h2.Sum(nil))
|
|
if _, err := io.WriteString(h3, ossProduct); err != nil {
|
|
return "", err
|
|
}
|
|
h4 := hmac.New(hmacHash, h3.Sum(nil))
|
|
if _, err := io.WriteString(h4, "aliyun_v4_request"); err != nil {
|
|
return "", err
|
|
}
|
|
h := hmac.New(hmacHash, h4.Sum(nil))
|
|
if _, err := io.WriteString(h, policyBase64); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
type callbackParam struct {
|
|
CallbackUrl string `json:"callbackUrl"`
|
|
CallbackBody string `json:"callbackBody"`
|
|
CallbackBodyType string `json:"callbackBodyType"`
|
|
}
|
|
|
|
func encodeCallbackBase64(url, body, bodyType string) string {
|
|
raw, err := json.Marshal(callbackParam{
|
|
CallbackUrl: url,
|
|
CallbackBody: body,
|
|
CallbackBodyType: bodyType,
|
|
})
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return base64.StdEncoding.EncodeToString(raw)
|
|
}
|
|
|
|
func encodeCallbackVarBase64(vars map[string]string) string {
|
|
if len(vars) == 0 {
|
|
return ""
|
|
}
|
|
raw, err := json.Marshal(vars)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return base64.StdEncoding.EncodeToString(raw)
|
|
}
|