From 3c3d4e91094bace5b93c71ed66aa8f1251d83bfa Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 31 Oct 2024 11:37:03 +0800 Subject: [PATCH] refactor: extend qiniu sdk --- internal/deployer/qiniu_cdn.go | 222 +++++------------- internal/deployer/qiniu_cdn_test.go | 85 ------- .../core/uploader/uploader_qiniu_sslcert.go | 69 ++++++ internal/pkg/vendors/qiniu-sdk/client.go | 160 +++++++++++++ internal/pkg/vendors/qiniu-sdk/models.go | 53 +++++ 5 files changed, 338 insertions(+), 251 deletions(-) delete mode 100644 internal/deployer/qiniu_cdn_test.go create mode 100644 internal/pkg/core/uploader/uploader_qiniu_sslcert.go create mode 100644 internal/pkg/vendors/qiniu-sdk/client.go create mode 100644 internal/pkg/vendors/qiniu-sdk/models.go diff --git a/internal/deployer/qiniu_cdn.go b/internal/deployer/qiniu_cdn.go index 797e3427..73e82ae0 100644 --- a/internal/deployer/qiniu_cdn.go +++ b/internal/deployer/qiniu_cdn.go @@ -1,38 +1,53 @@ package deployer import ( - "bytes" "context" "encoding/json" "fmt" - "io" - "net/http" + xerrors "github.com/pkg/errors" "github.com/qiniu/go-sdk/v7/auth" "github.com/usual2970/certimate/internal/domain" - xhttp "github.com/usual2970/certimate/internal/utils/http" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk" ) -const qiniuGateway = "http://api.qiniu.com" - type QiniuCDNDeployer struct { - option *DeployerOption - info []string - credentials *auth.Credentials + option *DeployerOption + infos []string + + sdkClient *qiniuEx.Client + sslUploader uploader.Uploader } -func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) { +func NewQiniuCDNDeployer(option *DeployerOption) (Deployer, error) { access := &domain.QiniuAccess{} if err := json.Unmarshal([]byte(option.Access), access); err != nil { - return nil, fmt.Errorf("failed to get access: %w", err) + return nil, xerrors.Wrap(err, "failed to get access") + } + + client, err := (&QiniuCDNDeployer{}).createSdkClient( + access.AccessKey, + access.SecretKey, + ) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploader.NewQiniuSSLCertUploader(&uploader.QiniuSSLCertUploaderConfig{ + AccessKey: access.AccessKey, + SecretKey: access.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") } return &QiniuCDNDeployer{ - option: option, - info: make([]string, 0), - - credentials: auth.New(access.AccessKey, access.SecretKey), + option: option, + infos: make([]string, 0), + sdkClient: client, + sslUploader: uploader, }, nil } @@ -41,176 +56,51 @@ func (d *QiniuCDNDeployer) GetID() string { } func (d *QiniuCDNDeployer) GetInfo() []string { - return d.info + return d.infos } func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error { // 上传证书 - certId, err := d.uploadCert() + upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey) if err != nil { - return fmt.Errorf("uploadCert failed: %w", err) + return err } + d.infos = append(d.infos, toStr("已上传证书", upres)) + // 获取域名信息 - domainInfo, err := d.getDomainInfo() + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + domain := d.option.DeployConfig.GetConfigAsString("domain") + getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain) if err != nil { - return fmt.Errorf("getDomainInfo failed: %w", err) + return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'") } - // 判断域名是否启用 https - if domainInfo.Https != nil && domainInfo.Https.CertID != "" { - // 启用了 https - // 修改域名证书 - err = d.modifyDomainCert(certId, domainInfo.Https.ForceHttps, domainInfo.Https.Http2Enable) + d.infos = append(d.infos, toStr("已获取域名信息", getDomainInfoResp)) + + // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS + // REF: https://developer.qiniu.com/fusion/4246/the-domain-name + if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) if err != nil { - return fmt.Errorf("modifyDomainCert failed: %w", err) + return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") } + + d.infos = append(d.infos, toStr("已修改域名证书", modifyDomainHttpsConfResp)) } else { - // 没启用 https - // 启用 https - err = d.enableHttps(certId) + enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true) if err != nil { - return fmt.Errorf("enableHttps failed: %w", err) + return xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'") } + + d.infos = append(d.infos, toStr("已将域名升级为 HTTPS", enableDomainHttpsResp)) } return nil } -func (d *QiniuCDNDeployer) enableHttps(certId string) error { - domain := d.option.DeployConfig.GetDomain() - path := fmt.Sprintf("/domain/%s/sslize", domain) - - body := &qiniuModifyDomainCertReq{ - CertID: certId, - ForceHttps: true, - Http2Enable: true, - } - - bodyBytes, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("enable https failed: %w", err) - } - - _, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes)) - if err != nil { - return fmt.Errorf("enable https failed: %w", err) - } - - return nil -} - -type qiniuDomainInfo struct { - Https *qiniuModifyDomainCertReq `json:"https"` -} - -func (d *QiniuCDNDeployer) getDomainInfo() (*qiniuDomainInfo, error) { - domain := d.option.DeployConfig.GetDomain() - - path := fmt.Sprintf("/domain/%s", domain) - - res, err := d.req(qiniuGateway+path, http.MethodGet, nil) - if err != nil { - return nil, fmt.Errorf("req failed: %w", err) - } - - resp := &qiniuDomainInfo{} - err = json.Unmarshal(res, resp) - if err != nil { - return nil, fmt.Errorf("json.Unmarshal failed: %w", err) - } - - return resp, nil -} - -type qiniuUploadCertReq struct { - Name string `json:"name"` - CommonName string `json:"common_name"` - Pri string `json:"pri"` - Ca string `json:"ca"` -} - -type qiniuUploadCertResp struct { - CertID string `json:"certID"` -} - -func (d *QiniuCDNDeployer) uploadCert() (string, error) { - path := "/sslcert" - - body := &qiniuUploadCertReq{ - Name: getDeployString(d.option.DeployConfig, "domain"), - CommonName: getDeployString(d.option.DeployConfig, "domain"), - Pri: d.option.Certificate.PrivateKey, - Ca: d.option.Certificate.Certificate, - } - - bodyBytes, err := json.Marshal(body) - if err != nil { - return "", fmt.Errorf("json.Marshal failed: %w", err) - } - - res, err := d.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes)) - if err != nil { - return "", fmt.Errorf("req failed: %w", err) - } - resp := &qiniuUploadCertResp{} - err = json.Unmarshal(res, resp) - if err != nil { - return "", fmt.Errorf("json.Unmarshal failed: %w", err) - } - - return resp.CertID, nil -} - -type qiniuModifyDomainCertReq struct { - CertID string `json:"certId"` - ForceHttps bool `json:"forceHttps"` - Http2Enable bool `json:"http2Enable"` -} - -func (d *QiniuCDNDeployer) modifyDomainCert(certId string, forceHttps, http2Enable bool) error { - domain := d.option.DeployConfig.GetDomain() - path := fmt.Sprintf("/domain/%s/httpsconf", domain) - - body := &qiniuModifyDomainCertReq{ - CertID: certId, - ForceHttps: forceHttps, - Http2Enable: http2Enable, - } - - bodyBytes, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("json.Marshal failed: %w", err) - } - - _, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes)) - if err != nil { - return fmt.Errorf("req failed: %w", err) - } - - return nil -} - -func (d *QiniuCDNDeployer) req(url, method string, body io.Reader) ([]byte, error) { - req := xhttp.BuildReq(url, method, body, map[string]string{ - "Content-Type": "application/json", - }) - - if err := d.credentials.AddToken(auth.TokenQBox, req); err != nil { - return nil, fmt.Errorf("credentials.AddToken failed: %w", err) - } - - respBody, err := xhttp.ToRequest(req) - if err != nil { - return nil, fmt.Errorf("ToRequest failed: %w", err) - } - - defer respBody.Close() - - res, err := io.ReadAll(respBody) - if err != nil { - return nil, fmt.Errorf("io.ReadAll failed: %w", err) - } - - return res, nil +func (u *QiniuCDNDeployer) createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) { + credential := auth.New(accessKey, secretKey) + client := qiniuEx.NewClient(credential) + return client, nil } diff --git a/internal/deployer/qiniu_cdn_test.go b/internal/deployer/qiniu_cdn_test.go deleted file mode 100644 index 67012bfc..00000000 --- a/internal/deployer/qiniu_cdn_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package deployer - -import ( - "testing" - - "github.com/qiniu/go-sdk/v7/auth" - - "github.com/usual2970/certimate/internal/applicant" -) - -func Test_qiuniu_uploadCert(t *testing.T) { - type fields struct { - option *DeployerOption - } - tests := []struct { - name string - fields fields - want string - wantErr bool - }{ - { - name: "test", - fields: fields{ - option: &DeployerOption{ - DomainId: "1", - Domain: "example.com", - Access: `{"bucket":"test","accessKey":"","secretKey":""}`, - Certificate: applicant.Certificate{ - Certificate: "", - PrivateKey: "", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - q, _ := NewQiniuCDNDeployer(tt.fields.option) - got, err := q.uploadCert() - if (err != nil) != tt.wantErr { - t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("qiuniu.uploadCert() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_qiuniu_modifyDomainCert(t *testing.T) { - type fields struct { - option *DeployerOption - info []string - credentials *auth.Credentials - } - type args struct { - certId string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "test", - fields: fields{ - option: &DeployerOption{ - DomainId: "1", - Domain: "jt1.ikit.fun", - Access: `{"bucket":"test","accessKey":"","secretKey":""}`, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - q, _ := NewQiniuCDNDeployer(tt.fields.option) - if err := q.modifyDomainCert(tt.args.certId, true, true); (err != nil) != tt.wantErr { - t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/pkg/core/uploader/uploader_qiniu_sslcert.go b/internal/pkg/core/uploader/uploader_qiniu_sslcert.go new file mode 100644 index 00000000..edf72ac2 --- /dev/null +++ b/internal/pkg/core/uploader/uploader_qiniu_sslcert.go @@ -0,0 +1,69 @@ +package uploader + +import ( + "context" + "fmt" + "time" + + xerrors "github.com/pkg/errors" + "github.com/qiniu/go-sdk/v7/auth" + + "github.com/usual2970/certimate/internal/pkg/utils/x509" + qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk" +) + +type QiniuSSLCertUploaderConfig struct { + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` +} + +type QiniuSSLCertUploader struct { + config *QiniuSSLCertUploaderConfig + sdkClient *qiniuEx.Client +} + +func NewQiniuSSLCertUploader(config *QiniuSSLCertUploaderConfig) (Uploader, error) { + client, err := (&QiniuSSLCertUploader{}).createSdkClient( + config.AccessKey, + config.SecretKey, + ) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &QiniuSSLCertUploader{ + config: config, + sdkClient: client, + }, nil +} + +func (u *QiniuSSLCertUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) { + // 解析证书内容 + certX509, err := x509.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + // 生成新证书名(需符合七牛云命名规则) + var certId, certName string + certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + + // 上传新证书 + // REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate + uploadSslCertResp, err := u.sdkClient.UploadSslCert(certName, certX509.Subject.CommonName, privkeyPem, certPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadSslCert'") + } + + certId = uploadSslCertResp.CertID + return &UploadResult{ + CertId: certId, + CertName: certName, + }, nil +} + +func (u *QiniuSSLCertUploader) createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) { + credential := auth.New(accessKey, secretKey) + client := qiniuEx.NewClient(credential) + return client, nil +} diff --git a/internal/pkg/vendors/qiniu-sdk/client.go b/internal/pkg/vendors/qiniu-sdk/client.go new file mode 100644 index 00000000..eceff741 --- /dev/null +++ b/internal/pkg/vendors/qiniu-sdk/client.go @@ -0,0 +1,160 @@ +package qiniusdk + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/qiniu/go-sdk/v7/auth" + + xhttp "github.com/usual2970/certimate/internal/utils/http" +) + +const qiniuHost = "http://api.qiniu.com" + +type Client struct { + mac *auth.Credentials +} + +func NewClient(mac *auth.Credentials) *Client { + if mac == nil { + mac = auth.Default() + } + return &Client{mac: mac} +} + +func (c *Client) GetDomainInfo(domain string) (*GetDomainInfoResponse, error) { + respBytes, err := c.sendReq(http.MethodGet, fmt.Sprintf("domain/%s", domain), nil) + if err != nil { + return nil, err + } + + resp := &GetDomainInfoResponse{} + err = json.Unmarshal(respBytes, resp) + if err != nil { + return nil, err + } + if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 { + return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error) + } + + return resp, nil +} + +func (c *Client) ModifyDomainHttpsConf(domain, certId string, forceHttps, http2Enable bool) (*ModifyDomainHttpsConfResponse, error) { + req := &ModifyDomainHttpsConfRequest{ + DomainInfoHttpsData: DomainInfoHttpsData{ + CertID: certId, + ForceHttps: forceHttps, + Http2Enable: http2Enable, + }, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/httpsconf", domain), bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resp := &ModifyDomainHttpsConfResponse{} + err = json.Unmarshal(respBytes, resp) + if err != nil { + return nil, err + } + if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 { + return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error) + } + + return resp, nil +} + +func (c *Client) EnableDomainHttps(domain, certId string, forceHttps, http2Enable bool) (*EnableDomainHttpsResponse, error) { + req := &EnableDomainHttpsRequest{ + DomainInfoHttpsData: DomainInfoHttpsData{ + CertID: certId, + ForceHttps: forceHttps, + Http2Enable: http2Enable, + }, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.sendReq(http.MethodPut, fmt.Sprintf("domain/%s/sslize", domain), bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resp := &EnableDomainHttpsResponse{} + err = json.Unmarshal(respBytes, resp) + if err != nil { + return nil, err + } + if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 { + return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error) + } + + return resp, nil +} + +func (c *Client) UploadSslCert(name, commonName, pri, ca string) (*UploadSslCertResponse, error) { + req := &UploadSslCertRequest{ + Name: name, + CommonName: commonName, + Pri: pri, + Ca: ca, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.sendReq(http.MethodPost, "sslcert", bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resp := &UploadSslCertResponse{} + err = json.Unmarshal(respBytes, resp) + if err != nil { + return nil, err + } + if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 { + return nil, fmt.Errorf("code: %d, error: %s", *resp.Code, *resp.Error) + } + + return resp, nil +} + +func (c *Client) sendReq(method string, path string, body io.Reader) ([]byte, error) { + req := xhttp.BuildReq(fmt.Sprintf("%s/%s", qiniuHost, path), method, body, map[string]string{ + "Content-Type": "application/json", + }) + + if err := c.mac.AddToken(auth.TokenQBox, req); err != nil { + return nil, err + } + + respBody, err := xhttp.ToRequest(req) + if err != nil { + return nil, err + } + + defer respBody.Close() + + res, err := io.ReadAll(respBody) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/internal/pkg/vendors/qiniu-sdk/models.go b/internal/pkg/vendors/qiniu-sdk/models.go new file mode 100644 index 00000000..433909e1 --- /dev/null +++ b/internal/pkg/vendors/qiniu-sdk/models.go @@ -0,0 +1,53 @@ +package qiniusdk + +type UploadSslCertRequest struct { + Name string `json:"name"` + CommonName string `json:"common_name"` + Pri string `json:"pri"` + Ca string `json:"ca"` +} + +type UploadSslCertResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + CertID string `json:"certID"` +} + +type DomainInfoHttpsData struct { + CertID string `json:"certId"` + ForceHttps bool `json:"forceHttps"` + Http2Enable bool `json:"http2Enable"` +} + +type GetDomainInfoResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + CName string `json:"cname"` + Https *DomainInfoHttpsData `json:"https"` + PareDomain string `json:"pareDomain"` + OperationType string `json:"operationType"` + OperatingState string `json:"operatingState"` + OperatingStateDesc string `json:"operatingStateDesc"` + CreateAt string `json:"createAt"` + ModifyAt string `json:"modifyAt"` +} + +type ModifyDomainHttpsConfRequest struct { + DomainInfoHttpsData +} + +type ModifyDomainHttpsConfResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` +} + +type EnableDomainHttpsRequest struct { + DomainInfoHttpsData +} + +type EnableDomainHttpsResponse struct { + Code *int `json:"code,omitempty"` + Error *string `json:"error,omitempty"` +}