From e579cf6ceb3393da4bc134d61094f45a87fbc0d6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 25 Mar 2025 09:56:48 +0800 Subject: [PATCH] feat: add baiducloud cert uploader --- .../baiducloud-cas/baiducloud_cas.go | 139 ++++++++++++++++++ .../baiducloud-cas/baiducloud_cas_test.go | 72 +++++++++ .../pkg/vendors/baiducloud-sdk/cas/api.go | 93 ++++++++++++ .../pkg/vendors/baiducloud-sdk/cas/client.go | 17 +++ .../pkg/vendors/baiducloud-sdk/cas/models.go | 48 ++++++ 5 files changed, 369 insertions(+) create mode 100644 internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas.go create mode 100644 internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas_test.go create mode 100644 internal/pkg/vendors/baiducloud-sdk/cas/api.go create mode 100644 internal/pkg/vendors/baiducloud-sdk/cas/client.go create mode 100644 internal/pkg/vendors/baiducloud-sdk/cas/models.go diff --git a/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas.go b/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas.go new file mode 100644 index 00000000..9c0167a8 --- /dev/null +++ b/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas.go @@ -0,0 +1,139 @@ +package baiducloudcas + +import ( + "context" + "fmt" + "log/slog" + "strings" + "time" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + "github.com/usual2970/certimate/internal/pkg/utils/certutil" + bdsdk "github.com/usual2970/certimate/internal/pkg/vendors/baiducloud-sdk/cas" +) + +type UploaderConfig struct { + // 百度智能云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 百度智能云 SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *bdsdk.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &UploaderProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader { + if logger == nil { + u.logger = slog.Default() + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPem) + if err != nil { + return nil, err + } + + // 遍历证书列表,避免重复上传 + // REF: https://cloud.baidu.com/doc/Reference/s/Gjwvz27xu#35-%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8%E8%AF%A6%E6%83%85 + listCertDetail, err := u.sdkClient.ListCertDetail() + u.logger.Debug("sdk request 'cert.ListCertDetail'", slog.Any("response", listCertDetail)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cert.ListCertDetail'") + } else { + for _, certDetail := range listCertDetail.Certs { + // 先对比证书通用名称 + if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CertCommonName) { + continue + } + + // 再对比证书有效期 + oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStartTime) + oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certDetail.CertStopTime) + if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) { + continue + } + + // 再对比证书多域名 + if certDetail.CertDNSNames != strings.Join(certX509.DNSNames, ",") { + continue + } + + // 最后对比证书内容 + getCertDetailResp, err := u.sdkClient.GetCertRawData(certDetail.CertId) + u.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certDetail.CertId), slog.Any("response", getCertDetailResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cert.GetCertRawData'") + } else { + oldCertX509, err := certutil.ParseCertificateFromPEM(getCertDetailResp.CertServerData) + if err != nil { + continue + } + if !certutil.EqualCertificate(certX509, oldCertX509) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + u.logger.Info("ssl certificate already exists") + return &uploader.UploadResult{ + CertId: certDetail.CertId, + CertName: certDetail.CertName, + }, nil + } + } + + // 创建证书 + // REF: https://cloud.baidu.com/doc/Reference/s/Gjwvz27xu#31-%E5%88%9B%E5%BB%BA%E8%AF%81%E4%B9%A6 + createCertReq := &bdsdk.CreateCertArgs{} + createCertReq.CertName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + createCertReq.CertServerData = certPem + createCertReq.CertPrivateData = privkeyPem + createCertResp, err := u.sdkClient.CreateCert(createCertReq) + u.logger.Debug("sdk request 'cert.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cert.CreateCert'") + } + + return &uploader.UploadResult{ + CertId: createCertResp.CertId, + CertName: createCertResp.CertName, + }, nil +} + +func createSdkClient(accessKeyId, secretAccessKey string) (*bdsdk.Client, error) { + client, err := bdsdk.NewClient(accessKeyId, secretAccessKey, "") + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas_test.go b/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas_test.go new file mode 100644 index 00000000..d727f71f --- /dev/null +++ b/internal/pkg/core/uploader/providers/baiducloud-cas/baiducloud_cas_test.go @@ -0,0 +1,72 @@ +package baiducloudcas_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/baiducloud-cas" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fSecretAccessKey string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_BAIDUCLOUDCAS_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./baiducloud_cas_test.go -args \ + --CERTIMATE_UPLOADER_BAIDUCLOUDCAS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_BAIDUCLOUDCAS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_UPLOADER_BAIDUCLOUDCAS_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_UPLOADER_BAIDUCLOUDCAS_SECRETACCESSKEY="your-access-key-secret" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + sres, _ := json.Marshal(res) + t.Logf("ok: %s", string(sres)) + }) +} diff --git a/internal/pkg/vendors/baiducloud-sdk/cas/api.go b/internal/pkg/vendors/baiducloud-sdk/cas/api.go new file mode 100644 index 00000000..78c6c26a --- /dev/null +++ b/internal/pkg/vendors/baiducloud-sdk/cas/api.go @@ -0,0 +1,93 @@ +package cert + +import ( + "errors" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/cert" +) + +func (c *Client) CreateCert(args *CreateCertArgs) (*CreateCertResult, error) { + if args == nil { + return nil, errors.New("unset args") + } + + result, err := c.Client.CreateCert(&args.CreateCertArgs) + if err != nil { + return nil, err + } + + return &CreateCertResult{CreateCertResult: *result}, nil +} + +func (c *Client) ListCerts() (*ListCertResult, error) { + result, err := c.Client.ListCerts() + if err != nil { + return nil, err + } + + return &ListCertResult{ListCertResult: *result}, nil +} + +func (c *Client) ListCertDetail() (*ListCertDetailResult, error) { + result, err := c.Client.ListCertDetail() + if err != nil { + return nil, err + } + + return &ListCertDetailResult{ListCertDetailResult: *result}, nil +} + +func (c *Client) GetCertMeta(id string) (*CertificateMeta, error) { + result, err := c.Client.GetCertMeta(id) + if err != nil { + return nil, err + } + + return &CertificateMeta{CertificateMeta: *result}, nil +} + +func (c *Client) GetCertDetail(id string) (*CertificateDetailMeta, error) { + result, err := c.Client.GetCertDetail(id) + if err != nil { + return nil, err + } + + return &CertificateDetailMeta{CertificateDetailMeta: *result}, nil +} + +func (c *Client) GetCertRawData(id string) (*CertificateRawData, error) { + result := &CertificateRawData{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(cert.URI_PREFIX + cert.REQUEST_CERT_URL + "/" + id + "/rawData"). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) UpdateCertName(id string, args *UpdateCertNameArgs) error { + if args == nil { + return errors.New("unset args") + } + + err := c.Client.UpdateCertName(id, &args.UpdateCertNameArgs) + return err +} + +func (c *Client) UpdateCertData(id string, args *UpdateCertDataArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := c.Client.UpdateCertData(id, &args.UpdateCertDataArgs) + return err +} + +func (c *Client) DeleteCert(id string) error { + err := c.Client.DeleteCert(id) + return err +} diff --git a/internal/pkg/vendors/baiducloud-sdk/cas/client.go b/internal/pkg/vendors/baiducloud-sdk/cas/client.go new file mode 100644 index 00000000..02c4feff --- /dev/null +++ b/internal/pkg/vendors/baiducloud-sdk/cas/client.go @@ -0,0 +1,17 @@ +package cert + +import ( + "github.com/baidubce/bce-sdk-go/services/cert" +) + +type Client struct { + *cert.Client +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + client, err := cert.NewClient(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} diff --git a/internal/pkg/vendors/baiducloud-sdk/cas/models.go b/internal/pkg/vendors/baiducloud-sdk/cas/models.go new file mode 100644 index 00000000..d3fe0449 --- /dev/null +++ b/internal/pkg/vendors/baiducloud-sdk/cas/models.go @@ -0,0 +1,48 @@ +package cert + +import "github.com/baidubce/bce-sdk-go/services/cert" + +type CreateCertArgs struct { + cert.CreateCertArgs +} + +type CreateCertResult struct { + cert.CreateCertResult +} + +type UpdateCertNameArgs struct { + cert.UpdateCertNameArgs +} + +type CertificateMeta struct { + cert.CertificateMeta +} + +type CertificateDetailMeta struct { + cert.CertificateDetailMeta +} + +type CertificateRawData struct { + CertId string `json:"certId"` + CertName string `json:"certName"` + CertServerData string `json:"certServerData"` + CertPrivateData string `json:"certPrivateKey"` + CertLinkData string `json:"certLinkData,omitempty"` + CertType int `json:"certType,omitempty"` +} + +type ListCertResult struct { + cert.ListCertResult +} + +type ListCertDetailResult struct { + cert.ListCertDetailResult +} + +type UpdateCertDataArgs struct { + cert.UpdateCertDataArgs +} + +type CertInServiceMeta struct { + cert.CertInServiceMeta +}