mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
refactor: re-implement ksyun services with custom sdk, to lightweight package size
This commit is contained in:
@@ -7,7 +7,6 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.5.0
|
||||
github.com/G-Core/gcorelabscdn-go v1.0.37
|
||||
github.com/KscSDK/ksc-sdk-go v0.22.0
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.2.0
|
||||
github.com/alibabacloud-go/alb-20200616/v2 v2.3.2
|
||||
github.com/alibabacloud-go/apig-20240327/v7 v7.0.5
|
||||
@@ -107,7 +106,6 @@ require (
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.8 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
|
||||
@@ -75,8 +75,6 @@ github.com/G-Core/gcorelabscdn-go v1.0.37/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpO
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/KscSDK/ksc-sdk-go v0.22.0 h1:wIstq/89k+5nexhPLvhQLJaLl6gL3u+I+N01CK/PPaA=
|
||||
github.com/KscSDK/ksc-sdk-go v0.22.0/go.mod h1:isHlJZi429ff5JLemSc10h7nznNgzJAY4MmNM8u7SBo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
|
||||
@@ -193,10 +191,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.12 h1:DIKX2c31ekm9RA2D9FBj1EWXx++9AdAqRw+e78Tq2Ck=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.12/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||
@@ -622,7 +617,6 @@ github.com/jdcloud-api/jdcloud-sdk-go v1.66.0 h1:mWxIv+mnWMZ/+a/VpAkzC9BqqmK185w
|
||||
github.com/jdcloud-api/jdcloud-sdk-go v1.66.0/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
|
||||
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
|
||||
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
|
||||
@@ -19,6 +19,7 @@ func init() {
|
||||
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
|
||||
AccessKeyId: credentials.AccessKeyId,
|
||||
SecretAccessKey: credentials.SecretAccessKey,
|
||||
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
|
||||
DeployTarget: xmaps.GetString(options.ProviderExtendedConfig, "deployTarget"),
|
||||
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
|
||||
})
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package ksyunkcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
ksyunkcmsdk "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/kcm"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Certmgr
|
||||
UploadResult = core.CertmgrUploadResult
|
||||
ReplaceResult = core.CertmgrReplaceResult
|
||||
)
|
||||
|
||||
type CertmgrConfig struct {
|
||||
// 金山云 AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 金山云 SecretAccessKey。
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
// 金山云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
}
|
||||
|
||||
type Certmgr struct {
|
||||
config *CertmgrConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *ksyunkcmsdk.Client
|
||||
}
|
||||
|
||||
var _ Provider = (*Certmgr)(nil)
|
||||
|
||||
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the certmgr provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
return &Certmgr{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
c.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
c.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
|
||||
// 避免重复上传
|
||||
if upres, upok, err := c.tryGetResultIfCertExists(ctx, certPEM); err != nil {
|
||||
return nil, err
|
||||
} else if upok {
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return upres, nil
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
uploadCertificateReq := &ksyunkcmsdk.UploadCertificateRequest{
|
||||
ProjectId: lo.ToPtr(c.config.ProjectId),
|
||||
CertName: lo.ToPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||
CertFile: lo.ToPtr(certPEM),
|
||||
CertKey: lo.ToPtr(privkeyPEM),
|
||||
}
|
||||
uploadCertificateResp, err := c.sdkClient.UploadCertificateWithContext(ctx, uploadCertificateReq)
|
||||
c.logger.Debug("sdk request 'kcm.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp))
|
||||
if err != nil {
|
||||
if uploadCertificateResp != nil &&
|
||||
uploadCertificateResp.Error != nil && uploadCertificateResp.Error.Message == "重复的证书文件" {
|
||||
if upres, upok, err := c.tryGetResultIfCertExists(ctx, certPEM); err != nil {
|
||||
return nil, err
|
||||
} else if !upok {
|
||||
return nil, fmt.Errorf("could not find ssl certificate, may be upload failed")
|
||||
} else {
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return upres, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'kcm.UploadCertificate': %w", err)
|
||||
}
|
||||
|
||||
return &UploadResult{
|
||||
CertId: uploadCertificateResp.Ret.CertId,
|
||||
CertName: uploadCertificateResp.Ret.CertName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
|
||||
return nil, core.ErrUnsupported
|
||||
}
|
||||
|
||||
func (c *Certmgr) tryGetResultIfCertExists(ctx context.Context, certPEM string) (*UploadResult, bool, error) {
|
||||
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
listUserCertificatesPage := 1
|
||||
listUserCertificatesPageSize := 100
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, false, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
listUserCertificatesReq := &ksyunkcmsdk.ListUserCertificatesRequest{
|
||||
Page: lo.ToPtr(int32(listUserCertificatesPage)),
|
||||
PageSize: lo.ToPtr(int32(listUserCertificatesPageSize)),
|
||||
}
|
||||
listUserCertificatesResp, err := c.sdkClient.ListUserCertificatesWithContext(ctx, listUserCertificatesReq)
|
||||
c.logger.Debug("sdk request 'kcm.ListUserCertificates'", slog.Any("request", listUserCertificatesReq), slog.Any("response", listUserCertificatesResp))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to execute sdk request 'kcm.ListUserCertificates': %w", err)
|
||||
}
|
||||
|
||||
if listUserCertificatesResp.Ret == nil || listUserCertificatesResp.Ret.Certs == nil {
|
||||
break
|
||||
}
|
||||
|
||||
for _, certItem := range listUserCertificatesResp.Ret.Certs {
|
||||
// 对比证书多域名
|
||||
if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), strings.Join(certItem.Domains, ",")) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比证书颁发者
|
||||
if certX509.Issuer.CommonName != certItem.CA {
|
||||
continue
|
||||
}
|
||||
|
||||
// 对比证书指纹
|
||||
fingerprint := sha1.Sum(certX509.Raw)
|
||||
fingerprintHex := hex.EncodeToString(fingerprint[:])
|
||||
if !strings.EqualFold(fingerprintHex, certItem.FingerPrint) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果以上信息都一致,则视为已存在相同证书,直接返回
|
||||
return &UploadResult{
|
||||
CertId: certItem.CertId,
|
||||
CertName: certItem.CertName,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
if len(listUserCertificatesResp.Ret.Certs) < listUserCertificatesPageSize {
|
||||
break
|
||||
}
|
||||
|
||||
listUserCertificatesPage++
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey string) (*ksyunkcmsdk.Client, error) {
|
||||
client, err := ksyunkcmsdk.NewClient(
|
||||
ksyunkcmsdk.WithAkSk(accessKeyId, secretAccessKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ksyunkcm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/certmgr/internal/tester"
|
||||
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/ksyun-kcm"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("KSYUNKCM_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKeyId string
|
||||
fSecretAccessKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
|
||||
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./ksyun_kcm_test.go -args \
|
||||
--KSYUNKCM_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--KSYUNKCM_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--KSYUNKCM_ACCESSKEYID="your-access-key-id" \
|
||||
--KSYUNKCM_SECRETACCESSKEY="your-secret-access-key"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
SecretAccessKey: fSecretAccessKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestUpload(t, provider, tester.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package ksyunslb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/ksyun-kcm"
|
||||
ksyunkcmsdk "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/kcm"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
)
|
||||
|
||||
type (
|
||||
Provider = core.Certmgr
|
||||
UploadResult = core.CertmgrUploadResult
|
||||
ReplaceResult = core.CertmgrReplaceResult
|
||||
)
|
||||
|
||||
type CertmgrConfig struct {
|
||||
// 金山云 AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 金山云 SecretAccessKey。
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
// 金山云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
// 金山云地域。
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
type Certmgr struct {
|
||||
config *CertmgrConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *ksyunkcmsdk.Client
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Certmgr)(nil)
|
||||
|
||||
func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the certmgr provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
}
|
||||
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKeyId: config.AccessKeyId,
|
||||
SecretAccessKey: config.SecretAccessKey,
|
||||
ProjectId: config.ProjectId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Certmgr{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) SetLogger(logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
c.logger = slog.New(slog.DiscardHandler)
|
||||
} else {
|
||||
c.logger = logger
|
||||
}
|
||||
|
||||
c.sdkCertmgr.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*UploadResult, error) {
|
||||
// 描述证书,避免重复上传
|
||||
describeCertificatesPage := 1
|
||||
describeCertificatesPageSize := 100
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
describeCertificatesReq := &ksyunkcmsdk.DescribeCertificatesRequest{
|
||||
Region: lo.ToPtr(c.config.Region),
|
||||
Page: lo.ToPtr(int32(describeCertificatesPage)),
|
||||
PageSize: lo.ToPtr(int32(describeCertificatesPageSize)),
|
||||
}
|
||||
describeCertificatesResp, err := c.sdkClient.DescribeCertificatesWithContext(ctx, describeCertificatesReq)
|
||||
c.logger.Debug("sdk request 'kcm.DescribeCertificates'", slog.Any("request", describeCertificatesReq), slog.Any("response", describeCertificatesResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'kcm.DescribeCertificates': %w", err)
|
||||
}
|
||||
|
||||
if describeCertificatesResp.CertificateSet == nil {
|
||||
break
|
||||
}
|
||||
|
||||
for _, certItem := range describeCertificatesResp.CertificateSet {
|
||||
// 如果已存在相同证书,直接返回
|
||||
if xcert.EqualCertificatesFromPEM(certPEM, certItem.PublicKey) {
|
||||
c.logger.Info("ssl certificate already exists")
|
||||
return &UploadResult{
|
||||
CertId: certItem.CertificateId,
|
||||
CertName: certItem.CertificateName,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(describeCertificatesResp.CertificateSet) < describeCertificatesPageSize {
|
||||
break
|
||||
}
|
||||
|
||||
describeCertificatesPage++
|
||||
}
|
||||
|
||||
// 托管证书到 KCM
|
||||
upres, err := c.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
|
||||
// 创建证书
|
||||
// REF: https://apiexplorer.ksyun.com/#/api/96/CreateCertificate/2016-03-04/1013
|
||||
createCertificateReq := &ksyunkcmsdk.CreateCertificateRequest{
|
||||
Region: lo.ToPtr(c.config.Region),
|
||||
CertificateName: lo.ToPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||
Description: lo.ToPtr("upload from certimate"),
|
||||
Source: lo.ToPtr("kcm"),
|
||||
SSLCertificateId: lo.ToPtr(upres.CertId),
|
||||
}
|
||||
createCertificateResp, err := c.sdkClient.CreateCertificateWithContext(ctx, createCertificateReq)
|
||||
c.logger.Debug("sdk request 'kcm.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'kcm.CreateCertificate': %w", err)
|
||||
}
|
||||
|
||||
return &UploadResult{
|
||||
CertId: createCertificateResp.Certificate.CertificateId,
|
||||
CertName: createCertificateResp.Certificate.CertificateName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, privkeyPEM string) (*ReplaceResult, error) {
|
||||
// 托管证书到 KCM
|
||||
upres, err := c.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
|
||||
// 更新证书
|
||||
// REF: https://apiexplorer.ksyun.com/#/api/96/ModifyCertificate/2016-03-04/1013
|
||||
modifyCertificateReq := &ksyunkcmsdk.ModifyCertificateRequest{
|
||||
Region: lo.ToPtr(c.config.Region),
|
||||
Description: lo.ToPtr("upload from certimate"),
|
||||
SSLCertificateId: lo.ToPtr(upres.CertId),
|
||||
}
|
||||
modifyCertificateResp, err := c.sdkClient.ModifyCertificateWithContext(ctx, modifyCertificateReq)
|
||||
c.logger.Debug("sdk request 'kcm.ModifyCertificate'", slog.Any("request", modifyCertificateReq), slog.Any("response", modifyCertificateResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'kcm.ModifyCertificate': %w", err)
|
||||
}
|
||||
|
||||
return &ReplaceResult{}, nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey string) (*ksyunkcmsdk.Client, error) {
|
||||
client, err := ksyunkcmsdk.NewClient(
|
||||
ksyunkcmsdk.WithAkSk(accessKeyId, secretAccessKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package ksyunslb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/certmgr/internal/tester"
|
||||
impl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/ksyun-slb"
|
||||
)
|
||||
|
||||
var (
|
||||
fp = tester.Args("KSYUNSLB_")
|
||||
fTestCertPath string
|
||||
fTestKeyPath string
|
||||
fAccessKeyId string
|
||||
fSecretAccessKey string
|
||||
fRegion string
|
||||
)
|
||||
|
||||
func init() {
|
||||
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
|
||||
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
|
||||
fp.DefineString(&fRegion, "REGION")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./ksyun_slb_test.go -args \
|
||||
--KSYUNSLB_TESTCERTPATH="/path/to/your-test-cert.pem" \
|
||||
--KSYUNSLB_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--KSYUNSLB_ACCESSKEYID="your-access-key-id" \
|
||||
--KSYUNSLB_SECRETACCESSKEY="your-secret-access-key" \
|
||||
--KSYUNSLB_REGION="cn-beijing-6"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
fp.Parse()
|
||||
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
provider, err := impl.NewCertmgr(&impl.CertmgrConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
SecretAccessKey: fSecretAccessKey,
|
||||
Region: fRegion,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tester.TestUpload(t, provider, tester.TestUploadArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
|
||||
})
|
||||
}
|
||||
@@ -5,15 +5,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/KscSDK/ksc-sdk-go/ksc"
|
||||
ksccdnv1 "github.com/KscSDK/ksc-sdk-go/service/cdnv1"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
ksyuncdnsdk "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/cdn"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
)
|
||||
|
||||
@@ -27,6 +24,8 @@ type DeployerConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 金山云 SecretAccessKey。
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
// 金山云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
// 部署目标。
|
||||
DeployTarget string `json:"deployTarget"`
|
||||
// 域名匹配模式。
|
||||
@@ -42,7 +41,7 @@ type DeployerConfig struct {
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *ksccdnv1.Cdnv1
|
||||
sdkClient *ksyuncdnsdk.Client
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
@@ -93,8 +92,13 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
|
||||
}
|
||||
|
||||
func (d *Deployer) deployToDomain(ctx context.Context, certPEM, privkeyPEM string) error {
|
||||
_, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取待部署的域名列表
|
||||
var domains []string
|
||||
var domainIds []string
|
||||
switch d.config.DomainMatchPattern {
|
||||
case "", DOMAIN_MATCH_PATTERN_EXACT:
|
||||
{
|
||||
@@ -102,7 +106,20 @@ func (d *Deployer) deployToDomain(ctx context.Context, certPEM, privkeyPEM strin
|
||||
return fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
domains = []string{d.config.Domain}
|
||||
domainCandidates, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domains := lo.Filter(domainCandidates, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) bool {
|
||||
return d.config.Domain == domainItem.DomainName
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return fmt.Errorf("could not find domain")
|
||||
}
|
||||
|
||||
domainIds = lo.Map(domains, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) string {
|
||||
return domainItem.DomainId
|
||||
})
|
||||
}
|
||||
|
||||
case DOMAIN_MATCH_PATTERN_WILDCARD:
|
||||
@@ -111,21 +128,21 @@ func (d *Deployer) deployToDomain(ctx context.Context, certPEM, privkeyPEM strin
|
||||
return fmt.Errorf("config `domain` is required")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||
domainCandidates, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return xcerthostname.IsMatch(d.config.Domain, domain)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
} else {
|
||||
domains = []string{d.config.Domain}
|
||||
domainCandidates, err := d.getAllDomains(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domains := lo.Filter(domainCandidates, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) bool {
|
||||
return xcerthostname.IsMatch(d.config.Domain, domainItem.DomainName)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return fmt.Errorf("could not find any domains matched by wildcard")
|
||||
}
|
||||
|
||||
domainIds = lo.Map(domains, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) string {
|
||||
return domainItem.DomainId
|
||||
})
|
||||
}
|
||||
|
||||
case DOMAIN_MATCH_PATTERN_CERTSAN:
|
||||
@@ -135,12 +152,16 @@ func (d *Deployer) deployToDomain(ctx context.Context, certPEM, privkeyPEM strin
|
||||
return err
|
||||
}
|
||||
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return xcerthostname.IsMatchByCertificatePEM(certPEM, domain)
|
||||
domains := lo.Filter(domainCandidates, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) bool {
|
||||
return xcerthostname.IsMatchByCertificatePEM(certPEM, domainItem.DomainName)
|
||||
})
|
||||
if len(domains) == 0 {
|
||||
return fmt.Errorf("could not find any domains matched by certificate")
|
||||
}
|
||||
|
||||
domainIds = lo.Map(domains, func(domainItem *ksyuncdnsdk.CDNDomain, _ int) string {
|
||||
return domainItem.DomainId
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -148,18 +169,18 @@ func (d *Deployer) deployToDomain(ctx context.Context, certPEM, privkeyPEM strin
|
||||
}
|
||||
|
||||
// 遍历更新域名证书
|
||||
if len(domains) == 0 {
|
||||
if len(domainIds) == 0 {
|
||||
d.logger.Info("no cdn domains to deploy")
|
||||
} else {
|
||||
d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains))
|
||||
d.logger.Info("found cdn domains to deploy", slog.Any("domainIds", domainIds))
|
||||
var errs []error
|
||||
|
||||
for _, domain := range domains {
|
||||
for _, domainId := range domainIds {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil {
|
||||
if err := d.updateDomainCertificate(ctx, domainId, certPEM, privkeyPEM); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -180,14 +201,14 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
|
||||
// 更新证书
|
||||
// REF: https://docs.ksyun.com/documents/259
|
||||
setCertificateInput := map[string]any{
|
||||
"CertificateId": d.config.CertificateId,
|
||||
"CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
|
||||
"ServerCertificate": certPEM,
|
||||
"PrivateKey": privkeyPEM,
|
||||
setCertificateReq := &ksyuncdnsdk.SetCertificateRequest{
|
||||
CertificateId: lo.ToPtr(d.config.CertificateId),
|
||||
CertificateName: lo.ToPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||
ServerCertificate: lo.ToPtr(certPEM),
|
||||
PrivateKey: lo.ToPtr(privkeyPEM),
|
||||
}
|
||||
setCertificateOutput, err := d.sdkClient.SetCertificatePostWithContext(ctx, &setCertificateInput)
|
||||
d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateInput), slog.Any("response", setCertificateOutput))
|
||||
setCertificateResp, err := d.sdkClient.SetCertificateWithContext(ctx, setCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.SetCertificate'", slog.Any("request", setCertificateReq), slog.Any("response", setCertificateResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'cdn.SetCertificate': %w", err)
|
||||
}
|
||||
@@ -195,8 +216,8 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) {
|
||||
domains := make([]string, 0)
|
||||
func (d *Deployer) getAllDomains(ctx context.Context) ([]*ksyuncdnsdk.CDNDomain, error) {
|
||||
domains := make([]*ksyuncdnsdk.CDNDomain, 0)
|
||||
|
||||
// 查询域名列表
|
||||
// REF: https://docs.ksyun.com/documents/198
|
||||
@@ -209,38 +230,28 @@ func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) {
|
||||
default:
|
||||
}
|
||||
|
||||
getCdnDomainsInput := map[string]any{
|
||||
"PageNumber": getCdnDomainsPageNumber,
|
||||
"PageSize": getCdnDomainsPageSize,
|
||||
getCdnDomainsReq := &ksyuncdnsdk.GetCDNDomainsRequest{
|
||||
ProjectId: lo.IfF(d.config.ProjectId != 0, func() *int64 { return lo.ToPtr(d.config.ProjectId) }).Else(nil),
|
||||
PageNumber: lo.ToPtr(int32(getCdnDomainsPageNumber)),
|
||||
PageSize: lo.ToPtr(int32(getCdnDomainsPageSize)),
|
||||
}
|
||||
getCdnDomainsOutput, err := d.sdkClient.GetCdnDomainsPostWithContext(ctx, &getCdnDomainsInput)
|
||||
d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput))
|
||||
getCdnDomainsResp, err := d.sdkClient.GetCDNDomainsWithContext(ctx, getCdnDomainsReq)
|
||||
d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsReq), slog.Any("response", getCdnDomainsResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", err)
|
||||
}
|
||||
|
||||
type GetCdnDomainsResponse struct {
|
||||
PageNumber int32 `json:"PageNumber"`
|
||||
PageSize int32 `json:"PageSize"`
|
||||
TotalCount int32 `json:"TotalCount"`
|
||||
Domains []*struct {
|
||||
DomainId string `json:"DomainId"`
|
||||
DomainName string `json:"DomainName"`
|
||||
Cname string `json:"Cname"`
|
||||
CdnType string `json:"CdnType"`
|
||||
CreatedTime string `json:"CreatedTime"`
|
||||
ModifiedTime string `json:"ModifiedTime"`
|
||||
Region string `json:"Region"`
|
||||
} `json:"Domains"`
|
||||
}
|
||||
var getCdnDomainsResp *GetCdnDomainsResponse
|
||||
mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp)
|
||||
if getCdnDomainsResp == nil {
|
||||
if getCdnDomainsResp.Domains == nil {
|
||||
break
|
||||
}
|
||||
|
||||
ignoredStatuses := []string{"offline", "icp_checking", "icp_check_failed", "locking", "locked"}
|
||||
for _, domainItem := range getCdnDomainsResp.Domains {
|
||||
domains = append(domains, domainItem.DomainName)
|
||||
if lo.Contains(ignoredStatuses, domainItem.DomainStatus) {
|
||||
continue
|
||||
}
|
||||
|
||||
domains = append(domains, domainItem)
|
||||
}
|
||||
|
||||
if len(getCdnDomainsResp.Domains) < getCdnDomainsPageSize {
|
||||
@@ -253,84 +264,18 @@ func (d *Deployer) getAllDomains(ctx context.Context) ([]string, error) {
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) findDomainIdByDomain(ctx context.Context, domain string) (string, error) {
|
||||
// 查询域名列表
|
||||
// REF: https://docs.ksyun.com/documents/198
|
||||
getCdnDomainsPageNumber := 1
|
||||
getCdnDomainsPageSize := 100
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
getCdnDomainsInput := map[string]any{
|
||||
"PageNumber": getCdnDomainsPageNumber,
|
||||
"PageSize": getCdnDomainsPageSize,
|
||||
"DomainName": domain,
|
||||
"FuzzyMatch": "off",
|
||||
}
|
||||
getCdnDomainsOutput, err := d.sdkClient.GetCdnDomainsPostWithContext(ctx, &getCdnDomainsInput)
|
||||
d.logger.Debug("sdk request 'cdn.GetCdnDomains'", slog.Any("request", getCdnDomainsInput), slog.Any("response", getCdnDomainsOutput))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute sdk request 'cdn.GetCdnDomains': %w", err)
|
||||
}
|
||||
|
||||
type GetCdnDomainsResponse struct {
|
||||
PageNumber int32 `json:"PageNumber"`
|
||||
PageSize int32 `json:"PageSize"`
|
||||
TotalCount int32 `json:"TotalCount"`
|
||||
Domains []*struct {
|
||||
DomainId string `json:"DomainId"`
|
||||
DomainName string `json:"DomainName"`
|
||||
Cname string `json:"Cname"`
|
||||
CdnType string `json:"CdnType"`
|
||||
CreatedTime string `json:"CreatedTime"`
|
||||
ModifiedTime string `json:"ModifiedTime"`
|
||||
Region string `json:"Region"`
|
||||
} `json:"Domains"`
|
||||
}
|
||||
var getCdnDomainsResp *GetCdnDomainsResponse
|
||||
mapstructure.Decode(getCdnDomainsOutput, &getCdnDomainsResp)
|
||||
if getCdnDomainsResp == nil {
|
||||
break
|
||||
}
|
||||
|
||||
for _, domainItem := range getCdnDomainsResp.Domains {
|
||||
if strings.EqualFold(domainItem.DomainName, domain) {
|
||||
return domainItem.DomainId, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(getCdnDomainsResp.Domains) < getCdnDomainsPageSize {
|
||||
break
|
||||
}
|
||||
|
||||
getCdnDomainsPageNumber++
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find domain '%s'", domain)
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error {
|
||||
// 获取域名 ID
|
||||
domainId, err := d.findDomainIdByDomain(ctx, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Deployer) updateDomainCertificate(ctx context.Context, cloudDomainId string, certPEM, privkeyPEM string) error {
|
||||
// 为加速域名配置证书接口
|
||||
// REF: https://docs.ksyun.com/documents/261
|
||||
configCertificateInput := map[string]any{
|
||||
"Enable": "on",
|
||||
"DomainIds": domainId,
|
||||
"CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
|
||||
"ServerCertificate": certPEM,
|
||||
"PrivateKey": privkeyPEM,
|
||||
configCertificateReq := &ksyuncdnsdk.ConfigCertificateRequest{
|
||||
Enable: lo.ToPtr("on"),
|
||||
DomainIds: lo.ToPtr(cloudDomainId),
|
||||
CertificateName: lo.ToPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||
ServerCertificate: lo.ToPtr(certPEM),
|
||||
PrivateKey: lo.ToPtr(privkeyPEM),
|
||||
}
|
||||
configCertificateOutput, err := d.sdkClient.ConfigCertificatePostWithContext(ctx, &configCertificateInput)
|
||||
d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateInput), slog.Any("response", configCertificateOutput))
|
||||
configCertificateResp, err := d.sdkClient.ConfigCertificateWithContext(ctx, configCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.ConfigCertificate'", slog.Any("request", configCertificateReq), slog.Any("response", configCertificateResp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'cdn.ConfigCertificate': %w", err)
|
||||
}
|
||||
@@ -338,8 +283,13 @@ func (d *Deployer) updateDomainCertificate(ctx context.Context, domain string, c
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey string) (*ksccdnv1.Cdnv1, error) {
|
||||
region := "cn-beijing-6"
|
||||
client := ksccdnv1.SdkNew(ksc.NewClient(accessKeyId, secretAccessKey), &ksc.Config{Region: ®ion})
|
||||
func createSDKClient(accessKeyId, secretAccessKey string) (*ksyuncdnsdk.Client, error) {
|
||||
client, err := ksyuncdnsdk.NewClient(
|
||||
ksyuncdnsdk.WithAkSk(accessKeyId, secretAccessKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -4,12 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/KscSDK/ksc-sdk-go/ksc"
|
||||
ksckcm "github.com/KscSDK/ksc-sdk-go/service/kcm"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/ksyun-slb"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -22,6 +19,10 @@ type DeployerConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 金山云 SecretAccessKey。
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
// 金山云项目 ID。
|
||||
ProjectId int64 `json:"projectId,omitempty"`
|
||||
// 金山云地域。
|
||||
Region string `json:"region"`
|
||||
// 部署目标。
|
||||
DeployTarget string `json:"deployTarget"`
|
||||
// 证书 ID。
|
||||
@@ -30,9 +31,9 @@ type DeployerConfig struct {
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *ksckcm.Kcm
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkCertmgr core.Certmgr
|
||||
}
|
||||
|
||||
var _ Provider = (*Deployer)(nil)
|
||||
@@ -42,15 +43,20 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
|
||||
}
|
||||
|
||||
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
|
||||
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
|
||||
AccessKeyId: config.AccessKeyId,
|
||||
SecretAccessKey: config.SecretAccessKey,
|
||||
ProjectId: config.ProjectId,
|
||||
Region: config.Region,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create client: %w", err)
|
||||
return nil, fmt.Errorf("could not create certmgr: %w", err)
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkCertmgr: pcertmgr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -60,6 +66,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) {
|
||||
@@ -82,25 +90,13 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
|
||||
return fmt.Errorf("config `certificateId` is required")
|
||||
}
|
||||
|
||||
// 更新证书信息
|
||||
// REF: https://docs.ksyun.com/documents/2121
|
||||
modifyCertificateInput := map[string]any{
|
||||
"CertificateId": d.config.CertificateId,
|
||||
"CertificateName": fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
|
||||
"PublicKey": certPEM,
|
||||
"PrivateKey": privkeyPEM,
|
||||
}
|
||||
modifyCertificateOutput, err := d.sdkClient.ModifyCertificateWithContext(ctx, &modifyCertificateInput)
|
||||
d.logger.Debug("sdk request 'kcm.ModifyCertificate'", slog.Any("request", modifyCertificateInput), slog.Any("response", modifyCertificateOutput))
|
||||
// 替换证书
|
||||
rplres, err := d.sdkCertmgr.Replace(ctx, d.config.CertificateId, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute sdk request 'kcm.ModifyCertificate': %w", err)
|
||||
return fmt.Errorf("failed to replace certificate file: %w", err)
|
||||
} else {
|
||||
d.logger.Info("ssl certificate replaced", slog.Any("result", rplres))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSDKClient(accessKeyId, secretAccessKey string) (*ksckcm.Kcm, error) {
|
||||
region := "cn-beijing-6"
|
||||
client := ksckcm.SdkNew(ksc.NewClient(accessKeyId, secretAccessKey), &ksc.Config{Region: ®ion})
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
fTestKeyPath string
|
||||
fAccessKeyId string
|
||||
fSecretAccessKey string
|
||||
fRegion string
|
||||
fCertificateId string
|
||||
)
|
||||
|
||||
@@ -21,6 +22,7 @@ func init() {
|
||||
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
|
||||
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
|
||||
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
|
||||
fp.DefineString(&fRegion, "REGION")
|
||||
fp.DefineString(&fCertificateId, "CERTIFICATEID")
|
||||
}
|
||||
|
||||
@@ -32,6 +34,7 @@ Shell command to run this test:
|
||||
--KSYUNSLB_TESTKEYPATH="/path/to/your-test-key.pem" \
|
||||
--KSYUNSLB_ACCESSKEYID="your-access-key-id" \
|
||||
--KSYUNSLB_SECRETACCESSKEY="your-secret-access-key" \
|
||||
--KSYUNSLB_REGION="cn-beijing-6" \
|
||||
--KSYUNSLB_CERTIFICATEID="your-certificate-id"
|
||||
*/
|
||||
func TestProvider(t *testing.T) {
|
||||
@@ -41,6 +44,7 @@ func TestProvider(t *testing.T) {
|
||||
provider, err := impl.NewDeployer(&impl.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
SecretAccessKey: fSecretAccessKey,
|
||||
Region: fRegion,
|
||||
DeployTarget: impl.DEPLOY_TARGET_CERTIFICATE,
|
||||
CertificateId: fCertificateId,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ConfigCertificateRequest struct {
|
||||
Enable *string `json:"Enable,omitempty"`
|
||||
DomainIds *string `json:"DomainIds,omitempty"`
|
||||
CertificateId *string `json:"CertificateId,omitempty"`
|
||||
CertificateName *string `json:"CertificateName,omitempty"`
|
||||
ServerCertificate *string `json:"ServerCertificate,omitempty"`
|
||||
PrivateKey *string `json:"PrivateKey,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
CertificateId string `json:"CertificateId,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) ConfigCertificate(req *ConfigCertificateRequest) (*ConfigCertificateResponse, error) {
|
||||
return c.ConfigCertificateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) ConfigCertificateWithContext(ctx context.Context, req *ConfigCertificateRequest) (*ConfigCertificateResponse, error) {
|
||||
params := &struct {
|
||||
ConfigCertificateRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
ConfigCertificateRequest: *req,
|
||||
Action: "ConfigCertificate",
|
||||
Version: "2016-09-01",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/2016-09-01/cert/ConfigCertificate", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &ConfigCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GetCDNDomainsRequest struct {
|
||||
ProjectId *int64 `json:"ProjectId,omitempty"`
|
||||
DomainName *string `json:"DomainName,omitempty"`
|
||||
DomainStatus *string `json:"DomainStatus,omitempty"`
|
||||
FuzzyMatch *bool `json:"FuzzyMatch,omitempty"`
|
||||
CdnType *bool `json:"CdnType,omitempty"`
|
||||
PageNumber *int32 `json:"PageNumber,omitempty"`
|
||||
PageSize *int32 `json:"PageSize,omitempty"`
|
||||
}
|
||||
|
||||
type GetCdnDomainsResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Domains []*CDNDomain `json:"Domains"`
|
||||
PageNumber int32 `json:"PageNumber,omitempty"`
|
||||
PageSize int32 `json:"PageSize,omitempty"`
|
||||
TotalCount int32 `json:"TotalCount,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) GetCDNDomains(req *GetCDNDomainsRequest) (*GetCdnDomainsResponse, error) {
|
||||
return c.GetCDNDomainsWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) GetCDNDomainsWithContext(ctx context.Context, req *GetCDNDomainsRequest) (*GetCdnDomainsResponse, error) {
|
||||
params := &struct {
|
||||
GetCDNDomainsRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
GetCDNDomainsRequest: *req,
|
||||
Action: "GetCdnDomains",
|
||||
Version: "2019-06-01",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodGet, "/2019-06-01/GetCdnDomains", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &GetCdnDomainsResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SetCertificateRequest struct {
|
||||
CertificateId *string `json:"CertificateId,omitempty"`
|
||||
CertificateName *string `json:"CertificateName,omitempty"`
|
||||
ServerCertificate *string `json:"ServerCertificate,omitempty"`
|
||||
PrivateKey *string `json:"PrivateKey,omitempty"`
|
||||
}
|
||||
|
||||
type SetCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
CertificateId string `json:"CertificateId,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) SetCertificate(req *SetCertificateRequest) (*SetCertificateResponse, error) {
|
||||
return c.SetCertificateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) SetCertificateWithContext(ctx context.Context, req *SetCertificateRequest) (*SetCertificateResponse, error) {
|
||||
params := &struct {
|
||||
SetCertificateRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
SetCertificateRequest: *req,
|
||||
Action: "SetCertificate",
|
||||
Version: "2016-09-01",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/2016-09-01/cert/SetCertificate", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &SetCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// A simple SDK client for KingsoftCloud CDN.
|
||||
// API documentation: https://apiexplorer.ksyun.com/#/api/home
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
common "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/zz-shared-common"
|
||||
)
|
||||
|
||||
const (
|
||||
service = "cdn"
|
||||
endpoint = "https://" + service + ".api.ksyun.com"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *common.Client
|
||||
}
|
||||
|
||||
func NewClient(optFns ...common.OptionsFunc) (*Client, error) {
|
||||
client, err := common.NewClient(endpoint, service, optFns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{client: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string, params any) (*resty.Request, error) {
|
||||
return c.client.NewRequest(method, path, params)
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
return c.client.DoRequest(req)
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
resp, err := c.client.DoRequestWithResult(req, res)
|
||||
if err == nil {
|
||||
if err := res.GetAPIError(); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: api error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cdn
|
||||
|
||||
type CDNDomain struct {
|
||||
Region string `json:"Region"`
|
||||
DomainId string `json:"DomainId"`
|
||||
DomainName string `json:"DomainName"`
|
||||
Description string `json:"Description"`
|
||||
Cname string `json:"Cname"`
|
||||
CdnType string `json:"CdnType"`
|
||||
CdnSubType string `json:"CdnSubType"`
|
||||
DomainStatus string `json:"DomainStatus"`
|
||||
CreatedTime string `json:"CreatedTime"`
|
||||
ModifiedTime string `json:"ModifiedTime"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
common "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/zz-shared-common"
|
||||
)
|
||||
|
||||
func WithAkSk(ak, sk string) common.OptionsFunc {
|
||||
return common.WithAkSk(ak, sk)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cdn
|
||||
|
||||
import "fmt"
|
||||
|
||||
type sdkResponse interface {
|
||||
GetAPIError() error
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
RequestId *string `json:"RequestId,omitempty"`
|
||||
Error *sdkAPIError `json:"Error,omitempty"`
|
||||
}
|
||||
|
||||
type sdkAPIError struct {
|
||||
Type string `json:"Type,omitempty"`
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
func (e sdkAPIError) Error() string {
|
||||
if e.Type == "" {
|
||||
return fmt.Sprintf("[%s] %s ", e.Code, e.Message)
|
||||
}
|
||||
return fmt.Sprintf("[%s_%s] %s ", e.Type, e.Code, e.Message)
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetAPIError() error {
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -0,0 +1,61 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
qs "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
type CreateCertificateRequest struct {
|
||||
Region *string `json:"Region,omitempty" url:"Region,omitempty"`
|
||||
CertificateName *string `json:"CertificateName,omitempty" url:"CertificateName,omitempty"`
|
||||
Description *string `json:"Description,omitempty" url:"Description,omitempty"`
|
||||
PublicKey *string `json:"PublicKey,omitempty" url:"-"`
|
||||
PrivateKey *string `json:"PrivateKey,omitempty" url:"-"`
|
||||
CertificateType *string `json:"CertificateType,omitempty" url:"CertificateType,omitempty"`
|
||||
Source *string `json:"Source,omitempty" url:"Source,omitempty"`
|
||||
SSLCertificateId *string `json:"SslCertificateId,omitempty" url:"SslCertificateId,omitempty"`
|
||||
}
|
||||
|
||||
type CreateCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Certificate *LBCertificate `json:"Certificate,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
|
||||
return c.CreateCertificateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateCertificateWithContext(ctx context.Context, req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
|
||||
params := &struct {
|
||||
CreateCertificateRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
CreateCertificateRequest: *req,
|
||||
Action: "CreateCertificate",
|
||||
Version: "2016-03-04",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
values, err := qs.Values(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq.SetQueryParamsFromValues(values)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &CreateCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DescribeCertificatesRequest struct {
|
||||
Region *string `json:"Region,omitempty"`
|
||||
Page *int32 `json:"Page,omitempty"`
|
||||
PageSize *int32 `json:"PageSize,omitempty"`
|
||||
}
|
||||
|
||||
type DescribeCertificatesResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
CertificateSet []*LBCertificate `json:"CertificateSet"`
|
||||
}
|
||||
|
||||
func (c *Client) DescribeCertificates(req *DescribeCertificatesRequest) (*DescribeCertificatesResponse, error) {
|
||||
return c.DescribeCertificatesWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) DescribeCertificatesWithContext(ctx context.Context, req *DescribeCertificatesRequest) (*DescribeCertificatesResponse, error) {
|
||||
params := &struct {
|
||||
DescribeCertificatesRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
DescribeCertificatesRequest: *req,
|
||||
Action: "DescribeCertificates",
|
||||
Version: "2016-03-04",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodGet, "/", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &DescribeCertificatesResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ListUserCertificatesRequest struct {
|
||||
Page *int32 `json:"Page,omitempty"`
|
||||
PageSize *int32 `json:"PageSize,omitempty"`
|
||||
}
|
||||
|
||||
type ListUserCertificatesResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Success bool `json:"Success"`
|
||||
Ret *struct {
|
||||
Certs []*UserCertificate `json:"Certs"`
|
||||
} `json:"Ret,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) ListUserCertificates(req *ListUserCertificatesRequest) (*ListUserCertificatesResponse, error) {
|
||||
return c.ListUserCertificatesWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) ListUserCertificatesWithContext(ctx context.Context, req *ListUserCertificatesRequest) (*ListUserCertificatesResponse, error) {
|
||||
params := &struct {
|
||||
ListUserCertificatesRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
ListUserCertificatesRequest: *req,
|
||||
Action: "ListUserCertificates",
|
||||
Version: "2016-03-04",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodGet, "/", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &ListUserCertificatesResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
qs "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
type ModifyCertificateRequest struct {
|
||||
Region *string `json:"Region,omitempty" url:"Region,omitempty"`
|
||||
CertificateId *string `json:"CertificateId,omitempty" url:"CertificateId,omitempty"`
|
||||
CertificateName *string `json:"CertificateName,omitempty" url:"CertificateName,omitempty"`
|
||||
Description *string `json:"Description,omitempty" url:"Description,omitempty"`
|
||||
PublicKey *string `json:"PublicKey,omitempty" url:"-"`
|
||||
PrivateKey *string `json:"PrivateKey,omitempty" url:"-"`
|
||||
SSLCertificateId *string `json:"SslCertificateId,omitempty" url:"SslCertificateId,omitempty"`
|
||||
}
|
||||
|
||||
type ModifyCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Certificate *LBCertificate `json:"Certificate,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) ModifyCertificate(req *ModifyCertificateRequest) (*ModifyCertificateResponse, error) {
|
||||
return c.ModifyCertificateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) ModifyCertificateWithContext(ctx context.Context, req *ModifyCertificateRequest) (*ModifyCertificateResponse, error) {
|
||||
params := &struct {
|
||||
ModifyCertificateRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
ModifyCertificateRequest: *req,
|
||||
Action: "ModifyCertificate",
|
||||
Version: "2016-03-04",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
values, err := qs.Values(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpreq.SetQueryParamsFromValues(values)
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &ModifyCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UploadCertificateRequest struct {
|
||||
ProjectId *int64 `json:"ProjectId,omitempty"`
|
||||
CertName *string `json:"CertName,omitempty"`
|
||||
CertFile *string `json:"CertFile,omitempty"`
|
||||
CertKey *string `json:"CertKey,omitempty"`
|
||||
}
|
||||
|
||||
type UploadCertificateResponse struct {
|
||||
sdkResponseBase
|
||||
|
||||
Success bool `json:"Success"`
|
||||
Ret *UserCertificate `json:"Ret,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) UploadCertificate(req *UploadCertificateRequest) (*UploadCertificateResponse, error) {
|
||||
return c.UploadCertificateWithContext(context.Background(), req)
|
||||
}
|
||||
|
||||
func (c *Client) UploadCertificateWithContext(ctx context.Context, req *UploadCertificateRequest) (*UploadCertificateResponse, error) {
|
||||
params := &struct {
|
||||
UploadCertificateRequest `json:",inline"`
|
||||
Action string
|
||||
Version string
|
||||
}{
|
||||
UploadCertificateRequest: *req,
|
||||
Action: "UploadCertificate",
|
||||
Version: "2016-03-04",
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, "/", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &UploadCertificateResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// A simple SDK client for KingsoftCloud KCM.
|
||||
// API documentation: https://apiexplorer.ksyun.com/#/api/home
|
||||
package kcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
common "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/zz-shared-common"
|
||||
)
|
||||
|
||||
const (
|
||||
service = "kcm"
|
||||
endpoint = "https://" + service + ".api.ksyun.com"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *common.Client
|
||||
}
|
||||
|
||||
func NewClient(optFns ...common.OptionsFunc) (*Client, error) {
|
||||
client, err := common.NewClient(endpoint, service, optFns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{client: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string, params any) (*resty.Request, error) {
|
||||
return c.client.NewRequest(method, path, params)
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
return c.client.DoRequest(req)
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
resp, err := c.client.DoRequestWithResult(req, res)
|
||||
if err == nil {
|
||||
if err := res.GetAPIError(); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: api error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package kcm
|
||||
|
||||
type UserCertificate struct {
|
||||
CertId string `json:"CertID"`
|
||||
CertName string `json:"CertName"`
|
||||
MainDomain string `json:"MainDomain"`
|
||||
Domains []string `json:"Domains"`
|
||||
AdditionalDomains []string `json:"AdditionalDomains"`
|
||||
Brand string `json:"Brand"`
|
||||
CA string `json:"CA"`
|
||||
Level string `json:"Level"`
|
||||
FingerPrint string `json:"FingerPrint"`
|
||||
DomainCount int32 `json:"DomainCount"`
|
||||
WildcardCount int32 `json:"WildcardCount"`
|
||||
IssueTime string `json:"IssueTime"`
|
||||
ExpireTime string `json:"ExpireTime"`
|
||||
Source string `json:"Source"`
|
||||
}
|
||||
|
||||
type LBCertificate struct {
|
||||
CertificateId string `json:"CertificateId"`
|
||||
CertificateName string `json:"CertificateName"`
|
||||
CommonName string `json:"CommonName"`
|
||||
CertAuthority string `json:"CertAuthority"`
|
||||
CertType string `json:"CertType"`
|
||||
CertificateType string `json:"CertificateType"`
|
||||
PublicKey string `json:"PublicKey"`
|
||||
ExpireTime string `json:"ExpireTime"`
|
||||
CreateTime string `json:"CreateTime"`
|
||||
Source string `json:"Source"`
|
||||
SSLCertificateId string `json:"SslCertificateId,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package kcm
|
||||
|
||||
import (
|
||||
common "github.com/certimate-go/certimate/pkg/sdk3rd/ksyun/zz-shared-common"
|
||||
)
|
||||
|
||||
func WithAkSk(ak, sk string) common.OptionsFunc {
|
||||
return common.WithAkSk(ak, sk)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package kcm
|
||||
|
||||
import "fmt"
|
||||
|
||||
type sdkResponse interface {
|
||||
GetAPIError() error
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
RequestId *string `json:"RequestId,omitempty"`
|
||||
Error *sdkAPIError `json:"Error,omitempty"`
|
||||
}
|
||||
|
||||
type sdkAPIError struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
func (e sdkAPIError) Error() string {
|
||||
return fmt.Sprintf("[%s] %s ", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetAPIError() error {
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
@@ -0,0 +1,152 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
rc *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(baseUrl string, service string, optFns ...OptionsFunc) (*Client, error) {
|
||||
options := &Options{}
|
||||
for _, fn := range optFns {
|
||||
fn(options)
|
||||
}
|
||||
|
||||
if baseUrl == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset baseUrl")
|
||||
}
|
||||
if _, err := url.Parse(baseUrl); err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: invalid baseUrl: %w", err)
|
||||
}
|
||||
if service == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset service")
|
||||
}
|
||||
if options.AccessKeyId == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
|
||||
}
|
||||
if options.SecretAccessKey == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
|
||||
}
|
||||
|
||||
signer := &signer{
|
||||
accessKeyId: options.AccessKeyId,
|
||||
secretAccessKey: options.SecretAccessKey,
|
||||
service: service,
|
||||
}
|
||||
httper := resty.New().
|
||||
SetBaseURL(baseUrl).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent).
|
||||
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
|
||||
if err := signer.Sign(req); err != nil {
|
||||
return fmt.Errorf("sdkerr: sign error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &Client{rc: httper}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.rc.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) NewRequest(method string, path string, params any) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
paramsMap := make(map[string]string)
|
||||
if params != nil {
|
||||
temp := make(map[string]any)
|
||||
jsonb, _ := json.Marshal(params)
|
||||
json.Unmarshal(jsonb, &temp)
|
||||
for k, v := range temp {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch reflect.Indirect(reflect.ValueOf(v)).Kind() {
|
||||
case reflect.String:
|
||||
paramsMap[k] = v.(string)
|
||||
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
paramsMap[k] = fmt.Sprintf("%v", v)
|
||||
|
||||
default:
|
||||
jsonb, _ := json.Marshal(v)
|
||||
paramsMap[k] = string(jsonb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req := c.rc.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
if strings.ToUpper(method) == http.MethodGet {
|
||||
req.SetQueryParams(paramsMap)
|
||||
} else {
|
||||
req.SetBody(paramsMap)
|
||||
}
|
||||
|
||||
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` or `req.SetFormData` 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 any) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package openapi
|
||||
|
||||
type Options struct {
|
||||
AccessKeyId string
|
||||
SecretAccessKey string
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
func WithAkSk(ak, sk string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.AccessKeyId = ak
|
||||
o.SecretAccessKey = sk
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type signer struct {
|
||||
accessKeyId string
|
||||
secretAccessKey string
|
||||
service string
|
||||
}
|
||||
|
||||
func (s *signer) Sign(req *http.Request) error {
|
||||
// API 签名机制:
|
||||
// https://docs.ksyun.com/documents/40298
|
||||
|
||||
method := strings.ToUpper(req.Method)
|
||||
|
||||
query := url.Values{}
|
||||
if req.URL != nil {
|
||||
query = req.URL.Query()
|
||||
}
|
||||
|
||||
payload := make(map[string]string)
|
||||
if method != http.MethodGet && req.Body != nil {
|
||||
payloadb, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(payloadb, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Body = io.NopCloser(bytes.NewReader(payloadb))
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
for k := range query {
|
||||
params[k] = query.Get(k)
|
||||
}
|
||||
for k, v := range payload {
|
||||
params[k] = v
|
||||
}
|
||||
|
||||
nowUtc := time.Now().UTC()
|
||||
timestamp := nowUtc.Format("2006-01-02T15:04:05Z")
|
||||
|
||||
params["Accesskey"] = s.accessKeyId
|
||||
params["Service"] = s.service
|
||||
params["Timestamp"] = timestamp
|
||||
params["SignatureVersion"] = "1.0"
|
||||
params["SignatureMethod"] = "HMAC-SHA256"
|
||||
paramsKeys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
paramsKeys = append(paramsKeys, k)
|
||||
}
|
||||
sort.Strings(paramsKeys)
|
||||
|
||||
stringToSign := ""
|
||||
for i, k := range paramsKeys {
|
||||
if i > 0 {
|
||||
stringToSign += "&"
|
||||
}
|
||||
|
||||
stringToSign += escapeQuery(k) + "=" + escapeQuery(params[k])
|
||||
}
|
||||
|
||||
h := hmac.New(sha256.New, []byte(s.secretAccessKey))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
if method == http.MethodGet {
|
||||
query.Set("Accesskey", params["Accesskey"])
|
||||
query.Set("Service", params["Service"])
|
||||
query.Set("Timestamp", params["Timestamp"])
|
||||
query.Set("SignatureVersion", params["SignatureVersion"])
|
||||
query.Set("SignatureMethod", params["SignatureMethod"])
|
||||
|
||||
req.URL.RawQuery = query.Encode() + "&Signature=" + signature
|
||||
} else {
|
||||
if _, ok := payload["Action"]; ok {
|
||||
query.Set("Action", payload["Action"])
|
||||
delete(payload, "Action")
|
||||
}
|
||||
|
||||
if _, ok := payload["Version"]; ok {
|
||||
query.Set("Version", payload["Version"])
|
||||
delete(payload, "Version")
|
||||
}
|
||||
|
||||
payload["Accesskey"] = params["Accesskey"]
|
||||
payload["Service"] = params["Service"]
|
||||
payload["Timestamp"] = params["Timestamp"]
|
||||
payload["SignatureVersion"] = params["SignatureVersion"]
|
||||
payload["SignatureMethod"] = params["SignatureMethod"]
|
||||
payload["Signature"] = signature
|
||||
|
||||
jsonb, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.URL.RawQuery = query.Encode()
|
||||
req.Body = io.NopCloser(bytes.NewReader(jsonb))
|
||||
req.ContentLength = int64(len(jsonb))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeQuery(str string) string {
|
||||
res := url.QueryEscape(str)
|
||||
res = strings.ReplaceAll(res, "+", "%20")
|
||||
return res
|
||||
}
|
||||
+12
@@ -24,6 +24,16 @@ const BizDeployNodeConfigFieldsProviderKsyunSLB = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "region"]}
|
||||
initialValue={initialValues.region}
|
||||
label={t("workflow_node.deploy.form.ksyun_slb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ksyun_slb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ksyun_slb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "deployTarget"]}
|
||||
initialValue={initialValues.deployTarget}
|
||||
@@ -56,6 +66,7 @@ const BizDeployNodeConfigFieldsProviderKsyunSLB = () => {
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
region: "",
|
||||
deployTarget: DEPLOY_TARGET_CERTIFICATE,
|
||||
certificateId: "",
|
||||
};
|
||||
@@ -66,6 +77,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
|
||||
return z
|
||||
.object({
|
||||
region: z.string().nonempty(),
|
||||
deployTarget: z.enum([DEPLOY_TARGET_CERTIFICATE]),
|
||||
certificateId: z.string().nullish(),
|
||||
})
|
||||
|
||||
@@ -1836,6 +1836,11 @@
|
||||
"placeholder": "Please enter Kingsoft Cloud CDN certificate ID",
|
||||
"tooltip": "For more information, see <a href=\"https://cdn.console.ksyun.com/\" target=\"_blank\">https://cdn.console.ksyun.com/</a>"
|
||||
},
|
||||
"ksyun_slb_region": {
|
||||
"label": "Kingsoft Cloud region",
|
||||
"placeholder": "Please enter Kingsoft Cloud SLB region (e.g. cn-beijing-6)",
|
||||
"tooltip": "For more information, see <a href=\"https://endocs.ksyun.com/directories/1164\" target=\"_blank\">https://endocs.ksyun.com/directories/1164</a>"
|
||||
},
|
||||
"ksyun_slb_deploy_target": {
|
||||
"option": {
|
||||
"certificate": {
|
||||
|
||||
@@ -1835,6 +1835,11 @@
|
||||
"placeholder": "请输入金山云 CDN 证书 ID",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://cdn.console.ksyun.com/\" target=\"_blank\">https://cdn.console.ksyun.com/</a>"
|
||||
},
|
||||
"ksyun_slb_region": {
|
||||
"label": "金山云服务地域",
|
||||
"placeholder": "请输入金山云 SLB 服务地域(例如:cn-beijing-6)",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://docs.ksyun.com/documents/1164\" target=\"_blank\">https://docs.ksyun.com/documents/1164</a>"
|
||||
},
|
||||
"ksyun_slb_deploy_target": {
|
||||
"option": {
|
||||
"certificate": {
|
||||
|
||||
Reference in New Issue
Block a user