From ce553652925f5a126f85a1d5c0e61cbcc11b8f83 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 31 Oct 2024 10:14:27 +0800 Subject: [PATCH] refactor: extend huaweicloud cdn sdk --- internal/deployer/huaweicloud_cdn.go | 82 ++----------------- internal/pkg/utils/x509/common.go | 22 +++++ internal/pkg/utils/x509/converter.go | 31 +++++++ .../pkg/utils/x509/{x509.go => parser.go} | 39 --------- .../pkg/vendors/huaweicloud-cdn-sdk/client.go | 26 ++++++ .../pkg/vendors/huaweicloud-cdn-sdk/models.go | 62 ++++++++++++++ 6 files changed, 150 insertions(+), 112 deletions(-) create mode 100644 internal/pkg/utils/x509/common.go create mode 100644 internal/pkg/utils/x509/converter.go rename internal/pkg/utils/x509/{x509.go => parser.go} (60%) create mode 100644 internal/pkg/vendors/huaweicloud-cdn-sdk/client.go create mode 100644 internal/pkg/vendors/huaweicloud-cdn-sdk/models.go diff --git a/internal/deployer/huaweicloud_cdn.go b/internal/deployer/huaweicloud_cdn.go index e8f35171..dfbe131e 100644 --- a/internal/deployer/huaweicloud_cdn.go +++ b/internal/deployer/huaweicloud_cdn.go @@ -15,13 +15,14 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/uploader" "github.com/usual2970/certimate/internal/pkg/utils/cast" + hcCdnEx "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk" ) type HuaweiCloudCDNDeployer struct { option *DeployerOption infos []string - sdkClient *hcCdn.CdnClient + sdkClient *hcCdnEx.Client sslUploader uploader.Uploader } @@ -40,7 +41,6 @@ func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) { return nil, xerrors.Wrap(err, "failed to create sdk client") } - // TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版 uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, @@ -82,10 +82,9 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error { // 更新加速域名配置 // REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html // REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html - updateDomainMultiCertificatesReqBodyContent := &huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent{} + updateDomainMultiCertificatesReqBodyContent := &hcCdnEx.UpdateDomainMultiCertificatesExRequestBodyContent{} updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain") updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1 - var updateDomainMultiCertificatesResp *hcCdnModel.UpdateDomainMultiCertificatesResponse if d.option.DeployConfig.GetConfigAsBool("useSCM") { // 上传证书到 SCM upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey) @@ -104,13 +103,13 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error { updateDomainMultiCertificatesReqBodyContent.Certificate = cast.StringPtr(d.option.Certificate.Certificate) updateDomainMultiCertificatesReqBodyContent.PrivateKey = cast.StringPtr(d.option.Certificate.PrivateKey) } - updateDomainMultiCertificatesReqBodyContent = mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, updateDomainMultiCertificatesReqBodyContent) - updateDomainMultiCertificatesReq := &huaweicloudCDNUpdateDomainMultiCertificatesRequest{ - Body: &huaweicloudCDNUpdateDomainMultiCertificatesRequestBody{ + updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs) + updateDomainMultiCertificatesReq := &hcCdnEx.UpdateDomainMultiCertificatesExRequest{ + Body: &hcCdnEx.UpdateDomainMultiCertificatesExRequestBody{ Https: updateDomainMultiCertificatesReqBodyContent, }, } - updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(d.sdkClient, updateDomainMultiCertificatesReq) + updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq) if err != nil { return err } @@ -120,7 +119,7 @@ func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error { return nil } -func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdn.CdnClient, error) { +func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdnEx.Client, error) { if region == "" { region = "cn-north-1" // CDN 服务默认区域:华北一北京 } @@ -146,69 +145,6 @@ func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, r return nil, err } - client := hcCdn.NewCdnClient(hcClient) + client := hcCdnEx.NewClient(hcClient) return client, nil } - -type huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent struct { - hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"` - - SCMCertificateId *string `json:"scm_certificate_id,omitempty"` -} - -type huaweicloudCDNUpdateDomainMultiCertificatesRequestBody struct { - Https *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent `json:"https,omitempty"` -} - -type huaweicloudCDNUpdateDomainMultiCertificatesRequest struct { - Body *huaweicloudCDNUpdateDomainMultiCertificatesRequestBody `json:"body,omitempty"` -} - -func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *hcCdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*hcCdnModel.UpdateDomainMultiCertificatesResponse, error) { - // 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求 - // 可能需要等之后 SDK 更新 - - requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates() - - if resp, err := client.HcClient.Sync(request, requestDef); err != nil { - return nil, err - } else { - return resp.(*hcCdnModel.UpdateDomainMultiCertificatesResponse), nil - } -} - -func mergeHuaweiCloudCDNConfig(src *hcCdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent { - if src == nil { - return dest - } - - // 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去 - // 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化 - - if *src.OriginProtocol == "follow" { - dest.AccessOriginWay = cast.Int32Ptr(1) - } else if *src.OriginProtocol == "http" { - dest.AccessOriginWay = cast.Int32Ptr(2) - } else if *src.OriginProtocol == "https" { - dest.AccessOriginWay = cast.Int32Ptr(3) - } - - if src.ForceRedirect != nil { - dest.ForceRedirectConfig = &hcCdnModel.ForceRedirect{} - - if src.ForceRedirect.Status == "on" { - dest.ForceRedirectConfig.Switch = 1 - dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type - } else { - dest.ForceRedirectConfig.Switch = 0 - } - } - - if src.Https != nil { - if *src.Https.Http2Status == "on" { - dest.Http2 = cast.Int32Ptr(1) - } - } - - return dest -} diff --git a/internal/pkg/utils/x509/common.go b/internal/pkg/utils/x509/common.go new file mode 100644 index 00000000..f5557962 --- /dev/null +++ b/internal/pkg/utils/x509/common.go @@ -0,0 +1,22 @@ +package x509 + +import ( + "crypto/x509" +) + +// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。 +// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。 +// +// 入参: +// - a: 待比较的第一个 x509.Certificate 对象。 +// - b: 待比较的第二个 x509.Certificate 对象。 +// +// 出参: +// - 是否相同。 +func EqualCertificate(a, b *x509.Certificate) bool { + return string(a.Signature) == string(b.Signature) && + a.SignatureAlgorithm == b.SignatureAlgorithm && + a.SerialNumber.String() == b.SerialNumber.String() && + a.Issuer.SerialNumber == b.Issuer.SerialNumber && + a.Subject.SerialNumber == b.Subject.SerialNumber +} diff --git a/internal/pkg/utils/x509/converter.go b/internal/pkg/utils/x509/converter.go new file mode 100644 index 00000000..c5522f27 --- /dev/null +++ b/internal/pkg/utils/x509/converter.go @@ -0,0 +1,31 @@ +package x509 + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + + xerrors "github.com/pkg/errors" +) + +// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 +// +// 入参: +// - privkey: ecdsa.PrivateKey 对象。 +// +// 出参: +// - privkeyPem: 私钥 PEM 内容。 +// - err: 错误。 +func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) { + data, err := x509.MarshalECPrivateKey(privkey) + if err != nil { + return "", xerrors.Wrap(err, "failed to marshal EC private key") + } + + block := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: data, + } + + return string(pem.EncodeToMemory(block)), nil +} diff --git a/internal/pkg/utils/x509/x509.go b/internal/pkg/utils/x509/parser.go similarity index 60% rename from internal/pkg/utils/x509/x509.go rename to internal/pkg/utils/x509/parser.go index 2a70a083..d9604526 100644 --- a/internal/pkg/utils/x509/x509.go +++ b/internal/pkg/utils/x509/parser.go @@ -10,23 +10,6 @@ import ( xerrors "github.com/pkg/errors" ) -// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。 -// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。 -// -// 入参: -// - a: 待比较的第一个 x509.Certificate 对象。 -// - b: 待比较的第二个 x509.Certificate 对象。 -// -// 出参: -// - 是否相同。 -func EqualCertificate(a, b *x509.Certificate) bool { - return string(a.Signature) == string(b.Signature) && - a.SignatureAlgorithm == b.SignatureAlgorithm && - a.SerialNumber.String() == b.SerialNumber.String() && - a.Issuer.SerialNumber == b.Issuer.SerialNumber && - a.Subject.SerialNumber == b.Subject.SerialNumber -} - // 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。 // // 入参: @@ -98,25 +81,3 @@ func ParsePKCS1PrivateKeyFromPEM(privkeyPem string) (privkey *rsa.PrivateKey, er return privkey, nil } - -// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。 -// -// 入参: -// - privkey: ecdsa.PrivateKey 对象。 -// -// 出参: -// - privkeyPem: 私钥 PEM 内容。 -// - err: 错误。 -func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (privkeyPem string, err error) { - data, err := x509.MarshalECPrivateKey(privkey) - if err != nil { - return "", xerrors.Wrap(err, "failed to marshal EC private key") - } - - block := &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: data, - } - - return string(pem.EncodeToMemory(block)), nil -} diff --git a/internal/pkg/vendors/huaweicloud-cdn-sdk/client.go b/internal/pkg/vendors/huaweicloud-cdn-sdk/client.go new file mode 100644 index 00000000..8befeb5f --- /dev/null +++ b/internal/pkg/vendors/huaweicloud-cdn-sdk/client.go @@ -0,0 +1,26 @@ +package huaweicloudcdnsdk + +import ( + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core" + hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2" +) + +type Client struct { + hcCdn.CdnClient +} + +func NewClient(hcClient *core.HcHttpClient) *Client { + return &Client{ + CdnClient: *hcCdn.NewCdnClient(hcClient), + } +} + +func (c *Client) UploadDomainMultiCertificatesEx(request *UpdateDomainMultiCertificatesExRequest) (*UpdateDomainMultiCertificatesExResponse, error) { + requestDef := hcCdn.GenReqDefForUpdateDomainMultiCertificates() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*UpdateDomainMultiCertificatesExResponse), nil + } +} diff --git a/internal/pkg/vendors/huaweicloud-cdn-sdk/models.go b/internal/pkg/vendors/huaweicloud-cdn-sdk/models.go new file mode 100644 index 00000000..cca42058 --- /dev/null +++ b/internal/pkg/vendors/huaweicloud-cdn-sdk/models.go @@ -0,0 +1,62 @@ +package huaweicloudcdnsdk + +import ( + hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model" + + "github.com/usual2970/certimate/internal/pkg/utils/cast" +) + +type UpdateDomainMultiCertificatesExRequestBodyContent struct { + hcCdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"` + + // 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求,可能需要等之后 SDK 更新。 + SCMCertificateId *string `json:"scm_certificate_id,omitempty"` +} + +type UpdateDomainMultiCertificatesExRequestBody struct { + Https *UpdateDomainMultiCertificatesExRequestBodyContent `json:"https,omitempty"` +} + +type UpdateDomainMultiCertificatesExRequest struct { + Body *UpdateDomainMultiCertificatesExRequestBody `json:"body,omitempty"` +} + +type UpdateDomainMultiCertificatesExResponse struct { + hcCdnModel.UpdateDomainMultiCertificatesResponse +} + +func (m *UpdateDomainMultiCertificatesExRequestBodyContent) MergeConfig(src *hcCdnModel.ConfigsGetBody) *UpdateDomainMultiCertificatesExRequestBodyContent { + if src == nil { + return m + } + + // 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去。 + // 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化。 + + if *src.OriginProtocol == "follow" { + m.AccessOriginWay = cast.Int32Ptr(1) + } else if *src.OriginProtocol == "http" { + m.AccessOriginWay = cast.Int32Ptr(2) + } else if *src.OriginProtocol == "https" { + m.AccessOriginWay = cast.Int32Ptr(3) + } + + if src.ForceRedirect != nil { + m.ForceRedirectConfig = &hcCdnModel.ForceRedirect{} + + if src.ForceRedirect.Status == "on" { + m.ForceRedirectConfig.Switch = 1 + m.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type + } else { + m.ForceRedirectConfig.Switch = 0 + } + } + + if src.Https != nil { + if *src.Https.Http2Status == "on" { + m.Http2 = cast.Int32Ptr(1) + } + } + + return m +}