diff --git a/go.mod b/go.mod index b0f71d564..2b98c7868 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 87e5918b9..a35a50148 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/certmgmt/deployers/sp_ksyun_slb.go b/internal/certmgmt/deployers/sp_ksyun_slb.go index 68ccc5f3a..b09db2f5e 100644 --- a/internal/certmgmt/deployers/sp_ksyun_slb.go +++ b/internal/certmgmt/deployers/sp_ksyun_slb.go @@ -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"), }) diff --git a/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm.go b/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm.go new file mode 100644 index 000000000..31b9fd7cf --- /dev/null +++ b/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm.go @@ -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 +} diff --git a/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm_test.go b/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm_test.go new file mode 100644 index 000000000..8349f109d --- /dev/null +++ b/pkg/core/certmgr/providers/ksyun-kcm/ksyun_kcm_test.go @@ -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}) + }) +} diff --git a/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb.go b/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb.go new file mode 100644 index 000000000..42a431c89 --- /dev/null +++ b/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb.go @@ -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 +} diff --git a/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb_test.go b/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb_test.go new file mode 100644 index 000000000..7887a71ca --- /dev/null +++ b/pkg/core/certmgr/providers/ksyun-slb/ksyun_slb_test.go @@ -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}) + }) +} diff --git a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go index 23452e39a..d56940ef7 100644 --- a/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -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 } diff --git a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go index bc26bc78f..a9b5d6d00 100644 --- a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go +++ b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb.go @@ -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 -} diff --git a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go index 982ff9294..3470a81a3 100644 --- a/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go +++ b/pkg/core/deployer/providers/ksyun-slb/ksyun_slb_test.go @@ -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, }) diff --git a/pkg/sdk3rd/ksyun/cdn/api_config_certificate.go b/pkg/sdk3rd/ksyun/cdn/api_config_certificate.go new file mode 100644 index 000000000..e1f6471a2 --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/api_config_certificate.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/cdn/api_get_cdn_domains.go b/pkg/sdk3rd/ksyun/cdn/api_get_cdn_domains.go new file mode 100644 index 000000000..d20adab9d --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/api_get_cdn_domains.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/cdn/api_set_certificate.go b/pkg/sdk3rd/ksyun/cdn/api_set_certificate.go new file mode 100644 index 000000000..e026658bf --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/api_set_certificate.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/cdn/client.go b/pkg/sdk3rd/ksyun/cdn/client.go new file mode 100644 index 000000000..fdfb52ef6 --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/client.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/cdn/models.go b/pkg/sdk3rd/ksyun/cdn/models.go new file mode 100644 index 000000000..dc49504dd --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/models.go @@ -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"` +} diff --git a/pkg/sdk3rd/ksyun/cdn/options.go b/pkg/sdk3rd/ksyun/cdn/options.go new file mode 100644 index 000000000..2d86735a6 --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/options.go @@ -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) +} diff --git a/pkg/sdk3rd/ksyun/cdn/types.go b/pkg/sdk3rd/ksyun/cdn/types.go new file mode 100644 index 000000000..d67bfd6b9 --- /dev/null +++ b/pkg/sdk3rd/ksyun/cdn/types.go @@ -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) diff --git a/pkg/sdk3rd/ksyun/kcm/api_create_certificate.go b/pkg/sdk3rd/ksyun/kcm/api_create_certificate.go new file mode 100644 index 000000000..2b0bf83c7 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/api_create_certificate.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/api_describe_certificates.go b/pkg/sdk3rd/ksyun/kcm/api_describe_certificates.go new file mode 100644 index 000000000..78b9b3315 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/api_describe_certificates.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/api_list_user_certificates.go b/pkg/sdk3rd/ksyun/kcm/api_list_user_certificates.go new file mode 100644 index 000000000..28d8b647b --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/api_list_user_certificates.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/api_modify_certificate.go b/pkg/sdk3rd/ksyun/kcm/api_modify_certificate.go new file mode 100644 index 000000000..37b53b629 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/api_modify_certificate.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/api_upload_certificate.go b/pkg/sdk3rd/ksyun/kcm/api_upload_certificate.go new file mode 100644 index 000000000..b47132673 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/api_upload_certificate.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/client.go b/pkg/sdk3rd/ksyun/kcm/client.go new file mode 100644 index 000000000..b16628dc6 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/client.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/kcm/models.go b/pkg/sdk3rd/ksyun/kcm/models.go new file mode 100644 index 000000000..3b8010565 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/models.go @@ -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"` +} diff --git a/pkg/sdk3rd/ksyun/kcm/options.go b/pkg/sdk3rd/ksyun/kcm/options.go new file mode 100644 index 000000000..538ec5890 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/options.go @@ -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) +} diff --git a/pkg/sdk3rd/ksyun/kcm/types.go b/pkg/sdk3rd/ksyun/kcm/types.go new file mode 100644 index 000000000..0e259c1e5 --- /dev/null +++ b/pkg/sdk3rd/ksyun/kcm/types.go @@ -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) diff --git a/pkg/sdk3rd/ksyun/zz-shared-common/client.go b/pkg/sdk3rd/ksyun/zz-shared-common/client.go new file mode 100644 index 000000000..d28bd8775 --- /dev/null +++ b/pkg/sdk3rd/ksyun/zz-shared-common/client.go @@ -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 +} diff --git a/pkg/sdk3rd/ksyun/zz-shared-common/options.go b/pkg/sdk3rd/ksyun/zz-shared-common/options.go new file mode 100644 index 000000000..33b0e984f --- /dev/null +++ b/pkg/sdk3rd/ksyun/zz-shared-common/options.go @@ -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 + } +} diff --git a/pkg/sdk3rd/ksyun/zz-shared-common/signer.go b/pkg/sdk3rd/ksyun/zz-shared-common/signer.go new file mode 100644 index 000000000..cee625d82 --- /dev/null +++ b/pkg/sdk3rd/ksyun/zz-shared-common/signer.go @@ -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 +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunSLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunSLB.tsx index 1a744b83a..1d6528613 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunSLB.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKsyunSLB.tsx @@ -24,6 +24,16 @@ const BizDeployNodeConfigFieldsProviderKsyunSLB = () => { return ( <> +