mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
refactor(provider): re-implement deployment provider of aliyun oss with custom sdk, to lightweight package size
This commit is contained in:
@@ -29,7 +29,6 @@ require (
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.11.3
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1
|
||||
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.12
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.23
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.22
|
||||
|
||||
@@ -176,8 +176,6 @@ github.com/alibabacloud-go/vod-20170321/v4 v4.11.3 h1:xF9GzlR4Qr+H2MRu558rpJUG6s
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.11.3/go.mod h1:2NX/9lVaKpd1+1GEV5zUAzQFfK9pF8Wkx81ugAnHYiw=
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1 h1:zCZIxEfoC3SmPx6x+7JzxXBPkQZ7eA19YFtWWJ80HMs=
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1/go.mod h1:oP48y3ec8gOIGFXcxKEUMiQwO9mZmlAH4TFZZvInHik=
|
||||
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1 h1:vtiFd0hhPAbyYJjztl0wYUq/PqEGkIlDmVuTIy6zw8Y=
|
||||
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
|
||||
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/aliyun-cas"
|
||||
osssdk "github.com/certimate-go/certimate/pkg/sdk3rd/alibabacloud/oss"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -33,9 +35,10 @@ type DeployerConfig struct {
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *oss.Client
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *osssdk.Client
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
@@ -45,15 +48,28 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region, config.Bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKeyId: config.AccessKeyId,
|
||||
AccessKeySecret: config.AccessKeySecret,
|
||||
ResourceGroupId: config.ResourceGroupId,
|
||||
Region: lo.
|
||||
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
|
||||
Else("ap-southeast-1"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -63,6 +79,8 @@ func (d *Deployer) SetLogger(logger *slog.Logger) {
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
|
||||
d.sdkCertmgr.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
|
||||
@@ -73,57 +91,45 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
return nil, fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
|
||||
// 为存储空间绑定自定义域名
|
||||
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
|
||||
putCnameReq := &oss.PutCnameRequest{
|
||||
Bucket: tea.String(d.config.Bucket),
|
||||
BucketCnameConfiguration: &oss.BucketCnameConfiguration{
|
||||
putBucketCnameReq := &osssdk.PutCnameRequest{
|
||||
Cname: &osssdk.PutCnameRequestCname{
|
||||
Domain: tea.String(d.config.Domain),
|
||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||
CertificateConfiguration: &osssdk.PutCnameRequestCnameCertificateConfiguration{
|
||||
CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)),
|
||||
Certificate: tea.String(certPEM),
|
||||
PrivateKey: tea.String(privkeyPEM),
|
||||
Force: tea.Bool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
putCnameResp, err := d.sdkClient.PutCname(ctx, putCnameReq)
|
||||
d.logger.Debug("sdk request 'oss.PutCname'", slog.Any("request", putCnameReq), slog.Any("response", putCnameResp))
|
||||
putBucketCnameResp, err := d.sdkClient.PutBucketCnameWithContext(ctx, putBucketCnameReq)
|
||||
d.logger.Debug("sdk request 'oss.PutBucketCname'", slog.String("params.bucket", d.config.Bucket), slog.Any("request", putBucketCnameReq), slog.Any("response", putBucketCnameResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'oss.PutCname': %w", err)
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'oss.PutBucketCname': %w", err)
|
||||
}
|
||||
|
||||
return &DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) {
|
||||
// 接入点一览 https://api.aliyun.com/product/Oss
|
||||
var endpoint string
|
||||
switch region {
|
||||
case "":
|
||||
endpoint = "oss.aliyuncs.com"
|
||||
case
|
||||
"cn-hzjbp",
|
||||
"cn-hzjbp-a",
|
||||
"cn-hzjbp-b":
|
||||
endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com"
|
||||
case
|
||||
"cn-shanghai-finance-1",
|
||||
"cn-shenzhen-finance-1",
|
||||
"cn-beijing-finance-1",
|
||||
"cn-north-2-gov-1":
|
||||
endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region)
|
||||
default:
|
||||
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
|
||||
func createSDKClient(accessKeyId, accessKeySecret, region, bucket string) (*osssdk.Client, error) {
|
||||
client, err := osssdk.NewClient("",
|
||||
osssdk.WithAkSk(accessKeyId, accessKeySecret),
|
||||
osssdk.WithRegion(region),
|
||||
osssdk.WithBucket(bucket),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider := credentials.NewStaticCredentialsProvider(accessKeyId, accessKeySecret)
|
||||
config := oss.LoadDefaultConfig().
|
||||
WithCredentialsProvider(provider).
|
||||
WithEndpoint(endpoint)
|
||||
if region != "" {
|
||||
config = config.WithRegion(region)
|
||||
}
|
||||
|
||||
client := oss.NewClient(config)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
CertificateChain: certPEM,
|
||||
PrivateKey: privkeyPEM,
|
||||
}
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, d.config.Bucket, putBucketCustomDomainReq)
|
||||
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, putBucketCustomDomainReq)
|
||||
d.logger.Debug("sdk request 'obs.PutBucketCustomDomain'", slog.String("params.bucket", d.config.Bucket), slog.String("params.customdomain", d.config.Domain), slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'obs.PutBucketCustomDomain': %w", err)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package oss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PutCnameRequest struct {
|
||||
XMLName xml.Name `json:"-" xml:"BucketCnameConfiguration"`
|
||||
Cname *PutCnameRequestCname `json:",omitempty" xml:"Cname,omitempty"`
|
||||
}
|
||||
|
||||
type PutCnameRequestCname struct {
|
||||
Domain *string `json:",omitempty" xml:"Domain,omitempty"`
|
||||
CertificateConfiguration *PutCnameRequestCnameCertificateConfiguration `json:",omitempty" xml:"CertificateConfiguration,omitempty"`
|
||||
}
|
||||
|
||||
type PutCnameRequestCnameCertificateConfiguration struct {
|
||||
CertId *string `json:",omitempty" xml:"CertId,omitempty"`
|
||||
Certificate *string `json:",omitempty" xml:"Certificate,omitempty"`
|
||||
PrivateKey *string `json:",omitempty" xml:"PrivateKey,omitempty"`
|
||||
PreviousCertId *string `json:",omitempty" xml:"PreviousCertId,omitempty"`
|
||||
Force *bool `json:",omitempty" xml:"Force,omitempty"`
|
||||
DeleteCertificate *bool `json:",omitempty" xml:"DeleteCertificate,omitempty"`
|
||||
}
|
||||
|
||||
type PutCnameResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCname(req *PutCnameRequest) (*PutCnameResponse, error) {
|
||||
return c.PutBucketCnameWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCnameWithContext(ctx context.Context, req *PutCnameRequest) (*PutCnameResponse, error) {
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/?cname&comp=add", fmt.Sprintf("/%s/", c.bucket), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &PutCnameResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"hash"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
bucket string
|
||||
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
opts := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
if opts.Region == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset region")
|
||||
}
|
||||
if opts.AccessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
|
||||
}
|
||||
if opts.AccessKeySecret == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
if opts.Bucket == "" {
|
||||
endpoint = fmt.Sprintf("https://oss-%s.aliyuncs.com", url.PathEscape(opts.Region))
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", url.PathEscape(opts.Bucket), url.PathEscape(opts.Region))
|
||||
}
|
||||
} else {
|
||||
if baseUrl, err := url.Parse(endpoint); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid endpoint: %w", err)
|
||||
} else if baseUrl.Scheme == "" {
|
||||
endpoint = "https://" + endpoint
|
||||
}
|
||||
}
|
||||
|
||||
restyClient := resty.New().
|
||||
SetBaseURL(endpoint).
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
|
||||
// API 签名机制:
|
||||
// https://help.aliyun.com/zh/oss/developer-reference/recommend-to-use-signature-version-4
|
||||
// https://www.alibabacloud.com/help/en/oss/developer-reference/recommend-to-use-signature-version-4
|
||||
|
||||
method := strings.ToUpper(req.Method)
|
||||
|
||||
nowUtc := time.Now().UTC()
|
||||
headerDateStr := nowUtc.Format(http.TimeFormat)
|
||||
requestDateStr := nowUtc.Format("20060102T150405Z")
|
||||
signDateStr := nowUtc.Format("20060102")
|
||||
|
||||
requestResStr := req.Header.Get("X-API-Resource")
|
||||
req.Header.Del("X-API-Resource")
|
||||
|
||||
canonicalUrl := escapePath(req.URL.Path)
|
||||
if canonicalUrl == "" {
|
||||
canonicalUrl = "/"
|
||||
}
|
||||
if canonicalUrl == "/" && requestResStr != "" {
|
||||
canonicalUrl = escapePath(requestResStr)
|
||||
}
|
||||
|
||||
canonicalQueryStr := ""
|
||||
if len(req.URL.Query()) > 0 {
|
||||
query := req.URL.Query()
|
||||
|
||||
keys := make([]string, 0, len(query))
|
||||
for key := range query {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
canonicalQueryStr += "&"
|
||||
}
|
||||
|
||||
value := query.Get(key)
|
||||
if value == "" {
|
||||
canonicalQueryStr += escapeQuery(key)
|
||||
} else {
|
||||
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canonicalHeaders := ""
|
||||
additionalHeaders := ""
|
||||
if len(req.Header) > 0 {
|
||||
if req.Header.Get("X-OSS-Date") == "" {
|
||||
req.Header.Set("X-OSS-Date", requestDateStr)
|
||||
}
|
||||
if req.Header.Get("X-OSS-Content-SHA256") == "" {
|
||||
req.Header.Set("X-OSS-Content-SHA256", "UNSIGNED-PAYLOAD")
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(req.Header))
|
||||
for key := range req.Header {
|
||||
key = strings.ToLower(key)
|
||||
if strings.HasPrefix(key, "x-oss-") {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if key == "content-type" || key == "content-md5" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
canonicalHeaders += "\n"
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(req.Header.Get(key))
|
||||
canonicalHeaders += key + ":" + value
|
||||
}
|
||||
|
||||
canonicalHeaders += "\n"
|
||||
}
|
||||
|
||||
hashedPayload := req.Header.Get("X-OSS-Content-SHA256")
|
||||
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, additionalHeaders, hashedPayload)
|
||||
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
|
||||
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
|
||||
|
||||
const signAlgorithmHeader = "OSS4-HMAC-SHA256"
|
||||
scope := fmt.Sprintf("%s/%s/oss/aliyun_v4_request", signDateStr, opts.Region)
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, scope, canonicalRequestHashHex)
|
||||
|
||||
var h hash.Hash
|
||||
h = hmac.New(sha256.New, []byte("aliyun_v4"+opts.AccessKeySecret))
|
||||
h.Write([]byte(signDateStr))
|
||||
kDate := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kDate)
|
||||
h.Write([]byte(opts.Region))
|
||||
kRegion := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kRegion)
|
||||
h.Write([]byte("oss"))
|
||||
kService := h.Sum(nil)
|
||||
h = hmac.New(sha256.New, kService)
|
||||
h.Write([]byte("aliyun_v4_request"))
|
||||
kSigning := h.Sum(nil)
|
||||
|
||||
h = hmac.New(sha256.New, kSigning)
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s/%s, Signature=%s", signAlgorithmHeader, opts.AccessKeyId, scope, signature))
|
||||
req.Header.Set("Date", headerDateStr)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{
|
||||
bucket: opts.Bucket,
|
||||
rc: restyClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string, resource string, params any) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
requestUrl, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid path: %w", err)
|
||||
} else if requestUrl.IsAbs() {
|
||||
return nil, fmt.Errorf("sdkerr: path should be relative")
|
||||
}
|
||||
|
||||
payloadStr := ""
|
||||
contentType := ""
|
||||
if params != nil {
|
||||
// 目前仅支持 XML 请求体,仅适用于非 S3 兼容接口
|
||||
payloadb, err := xml.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payloadStr = string(payloadb)
|
||||
contentType = "application/xml"
|
||||
}
|
||||
payloadMd5 := md5.Sum([]byte(payloadStr))
|
||||
payloadMd5Encoded := base64.StdEncoding.EncodeToString(payloadMd5[:])
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.URL = requestUrl.Path
|
||||
req.QueryParam = requestUrl.Query()
|
||||
req.SetHeader("Content-MD5", payloadMd5Encoded)
|
||||
req.SetHeader("Content-Type", contentType)
|
||||
req.SetHeader("X-API-Resource", resource)
|
||||
req.SetBody(payloadStr)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
// WARN:
|
||||
// PLEASE DO NOT USE `req.SetBody` HERE! USE `newRequest` INSTEAD.
|
||||
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
xml.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := xml.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func escapeQuery(str string) string {
|
||||
res := url.QueryEscape(str)
|
||||
res = strings.ReplaceAll(res, "+", "%20")
|
||||
return res
|
||||
}
|
||||
|
||||
func escapePath(path string) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
noEscape := (c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '-' ||
|
||||
c == '.' ||
|
||||
c == '_' ||
|
||||
c == '~' ||
|
||||
c == '/'
|
||||
if noEscape {
|
||||
buf.WriteByte(c)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%%%02X", c)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package oss
|
||||
|
||||
type Options struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
Region string
|
||||
Bucket string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithAkSk(ak, sk string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.AccessKeyId = ak
|
||||
o.AccessKeySecret = sk
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegion(region string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Region = region
|
||||
}
|
||||
}
|
||||
|
||||
func WithBucket(bucket string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.Bucket = bucket
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package oss
|
||||
|
||||
type sdkResponse interface{}
|
||||
|
||||
type sdkResponseBase struct{}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -22,14 +22,11 @@ type PutBucketCustomDomainResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCustomDomain(bucket string, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
return c.PutBucketCustomDomainWithContext(context.Background(), bucket, req)
|
||||
func (c *Client) PutBucketCustomDomain(req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
return c.PutBucketCustomDomainWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) PutBucketCustomDomainWithContext(ctx context.Context, bucket string, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
if bucket == "" {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset bucket")
|
||||
}
|
||||
func (c *Client) PutBucketCustomDomainWithContext(ctx context.Context, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
|
||||
if req.CustomDomain == "" {
|
||||
return nil, fmt.Errorf("sdkerr: bad request: unset customdomain")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package obs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
@@ -72,16 +73,16 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
|
||||
for _, key := range keys {
|
||||
value := strings.TrimSpace(req.Header.Get(key))
|
||||
canonicalizedHeaders += fmt.Sprintf("%s:%s", key, url.QueryEscape(value))
|
||||
canonicalizedHeaders += key + ":" + escapeQuery(value)
|
||||
canonicalizedHeaders += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
bucketName := opts.Bucket
|
||||
objectName := strings.Trim(req.URL.Path, "/")
|
||||
canonicalizedResource := fmt.Sprintf("/%s/%s", bucketName, objectName)
|
||||
canonicalizedResources := escapePath(fmt.Sprintf("/%s/%s", bucketName, objectName))
|
||||
if bucketName == "" && objectName == "" {
|
||||
canonicalizedResource = "/"
|
||||
canonicalizedResources = "/"
|
||||
}
|
||||
if len(req.URL.Query()) > 0 {
|
||||
query := req.URL.Query()
|
||||
@@ -94,16 +95,16 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
|
||||
for i, key := range keys {
|
||||
if i == 0 {
|
||||
canonicalizedResource += "?"
|
||||
canonicalizedResources += "?"
|
||||
} else {
|
||||
canonicalizedResource += "&"
|
||||
canonicalizedResources += "&"
|
||||
}
|
||||
|
||||
value := query.Get(key)
|
||||
if value == "" {
|
||||
canonicalizedResource += key
|
||||
canonicalizedResources += escapeQuery(key)
|
||||
} else {
|
||||
canonicalizedResource += fmt.Sprintf("%s=%s", strings.ToLower(key), url.QueryEscape(value))
|
||||
canonicalizedResources += escapeQuery(key) + "=" + escapeQuery(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,8 +116,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
contentMd5 := req.Header.Get("Content-MD5")
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResource)
|
||||
println("stringToSign:", stringToSign)
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResources)
|
||||
|
||||
h := hmac.New(sha1.New, []byte(opts.SecretAccessKey))
|
||||
h.Write([]byte(stringToSign))
|
||||
@@ -216,3 +216,32 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func escapeQuery(str string) string {
|
||||
res := url.QueryEscape(str)
|
||||
res = strings.ReplaceAll(res, "%7E", "~")
|
||||
res = strings.ReplaceAll(res, "%2F", "/")
|
||||
res = strings.ReplaceAll(res, "%20", "+")
|
||||
return res
|
||||
}
|
||||
|
||||
func escapePath(path string) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
noEscape := (c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '-' ||
|
||||
c == '.' ||
|
||||
c == '_' ||
|
||||
c == '~' ||
|
||||
c == '/'
|
||||
if noEscape {
|
||||
buf.WriteByte(c)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%%%02X", c)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tos
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
@@ -69,7 +70,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
requestDateStr := nowUtc.Format("20060102T150405Z")
|
||||
credentialDateStr := nowUtc.Format("20060102")
|
||||
|
||||
canonicalUrl := req.URL.Path
|
||||
canonicalUrl := escapePath(req.URL.Path)
|
||||
if canonicalUrl == "" {
|
||||
canonicalUrl = "/"
|
||||
}
|
||||
@@ -90,7 +91,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
}
|
||||
|
||||
value := query.Get(key)
|
||||
canonicalQueryStr += fmt.Sprintf("%s=%s", url.QueryEscape(key), url.QueryEscape(value))
|
||||
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,20 +124,20 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(req.Header.Get(key))
|
||||
canonicalHeaders += fmt.Sprintf("%s:%s", key, value)
|
||||
canonicalHeaders += key + ":" + value
|
||||
signedHeaders += key
|
||||
}
|
||||
|
||||
canonicalHeaders += "\n"
|
||||
}
|
||||
|
||||
payloadSha256Str := req.Header.Get("X-TOS-Content-SHA256")
|
||||
if payloadSha256Str == "" {
|
||||
payloadSha256 := sha256.Sum256([]byte{})
|
||||
payloadSha256Str = strings.ToLower(hex.EncodeToString(payloadSha256[:]))
|
||||
hashedPayload := req.Header.Get("X-TOS-Content-SHA256")
|
||||
if hashedPayload == "" {
|
||||
temp := sha256.Sum256([]byte{})
|
||||
hashedPayload = strings.ToLower(hex.EncodeToString(temp[:]))
|
||||
}
|
||||
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, payloadSha256Str)
|
||||
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, hashedPayload)
|
||||
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
|
||||
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
|
||||
|
||||
@@ -256,3 +257,30 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func escapeQuery(str string) string {
|
||||
res := url.QueryEscape(str)
|
||||
res = strings.ReplaceAll(res, "+", "%20")
|
||||
return res
|
||||
}
|
||||
|
||||
func escapePath(path string) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
noEscape := (c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '-' ||
|
||||
c == '.' ||
|
||||
c == '_' ||
|
||||
c == '~' ||
|
||||
c == '/'
|
||||
if noEscape {
|
||||
buf.WriteByte(c)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%%%02X", c)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user