Merge branch 'usual2970:main' into main
This commit is contained in:
@@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
edgio "github.com/Edgio/edgio-api/applications/v7"
|
||||
edgiodtos "github.com/Edgio/edgio-api/applications/v7/dtos"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
edgsdk "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7"
|
||||
edgsdkdtos "github.com/usual2970/certimate/internal/pkg/vendors/edgio-sdk/applications/v7/dtos"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
@@ -24,7 +24,7 @@ type DeployerConfig struct {
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *edgsdk.EdgioClient
|
||||
sdkClient *edgio.EdgioClient
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
@@ -64,7 +64,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
|
||||
// 上传 TLS 证书
|
||||
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||
uploadTlsCertReq := edgsdkdtos.UploadTlsCertRequest{
|
||||
uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
|
||||
EnvironmentID: d.config.EnvironmentId,
|
||||
PrimaryCert: privateCertPem,
|
||||
IntermediateCert: intermediateCertPem,
|
||||
@@ -79,7 +79,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(clientId, clientSecret string) (*edgsdk.EdgioClient, error) {
|
||||
client := edgsdk.NewEdgioClient(clientId, clientSecret, "", "")
|
||||
func createSdkClient(clientId, clientSecret string) (*edgio.EdgioClient, error) {
|
||||
client := edgio.NewEdgioClient(clientId, clientSecret, "", "")
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -100,9 +100,15 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
SSlEnabled: true,
|
||||
SSLData: int(updateResourceCertId),
|
||||
ProxySSLEnabled: getResourceResp.ProxySSLEnabled,
|
||||
ProxySSLCA: &getResourceResp.ProxySSLCA,
|
||||
ProxySSLData: &getResourceResp.ProxySSLData,
|
||||
Options: getResourceResp.Options,
|
||||
}
|
||||
if getResourceResp.ProxySSLCA != 0 {
|
||||
updateResourceReq.ProxySSLCA = &getResourceResp.ProxySSLCA
|
||||
}
|
||||
if getResourceResp.ProxySSLData != 0 {
|
||||
updateResourceReq.ProxySSLData = &getResourceResp.ProxySSLData
|
||||
}
|
||||
if getResourceResp.Options != nil {
|
||||
updateResourceReq.Options = getResourceResp.Options
|
||||
}
|
||||
updateResourceResp, err := d.sdkClient.Update(context.TODO(), d.config.ResourceId, updateResourceReq)
|
||||
d.logger.Debug("sdk request 'resources.Update'", slog.Int64("resourceId", d.config.ResourceId), slog.Any("request", updateResourceReq), slog.Any("response", updateResourceResp))
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package rainyunrcdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/rainyun-sslcenter"
|
||||
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 雨云 API 密钥。
|
||||
ApiKey string `json:"apiKey"`
|
||||
// RCDN 实例 ID。
|
||||
InstanceId int32 `json:"instanceId"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *rainyunsdk.Client
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.ApiKey)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||
ApiKey: config.ApiKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
d.sslUploader.WithLogger(logger)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
// 上传证书到 SSL 证书
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
|
||||
// RCDN SSL 绑定域名
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
|
||||
certId, _ := strconv.Atoi(upres.CertId)
|
||||
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
|
||||
CertId: int32(certId),
|
||||
Domains: []string{d.config.Domain},
|
||||
}
|
||||
rcdnInstanceSslBindResp, err := d.sdkClient.RcdnInstanceSslBind(d.config.InstanceId, rcdnInstanceSslBindReq)
|
||||
d.logger.Debug("sdk request 'rcdn.InstanceSslBind'", slog.Any("instanceId", d.config.InstanceId), slog.Any("request", rcdnInstanceSslBindReq), slog.Any("response", rcdnInstanceSslBindResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'rcdn.InstanceSslBind'")
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("invalid rainyun api key")
|
||||
}
|
||||
|
||||
client := rainyunsdk.NewClient(apiKey)
|
||||
return client, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package rainyunrcdn_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fApiKey string
|
||||
fInstanceId int64
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_RAINYUNRCDN_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
flag.Int64Var(&fInstanceId, argsPrefix+"INSTANCEID", 0, "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./ucloud_ucdn_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_APIKEY="your-api-key" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_INSTANCEID="your-rcdn-instance-id" \
|
||||
--CERTIMATE_DEPLOYER_RAINYUNRCDN_DOMAIN="example.com"
|
||||
*/
|
||||
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("APIKEY: %v", fApiKey),
|
||||
fmt.Sprintf("INSTANCEID: %v", fInstanceId),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
PrivateKey: fApiKey,
|
||||
InstanceId: fInstanceId,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||
deployCertificateInstanceReq := tcssl.NewDeployCertificateInstanceRequest()
|
||||
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn")
|
||||
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
|
||||
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
|
||||
deployCertificateInstanceResp, err := d.sdkClients.SSL.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||
|
||||
@@ -182,7 +182,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
|
||||
return errors.New("config `listenerId` is required")
|
||||
}
|
||||
|
||||
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil {
|
||||
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ func (d *DeployerProvider) deployToListener(ctx context.Context, cloudCertId str
|
||||
return errors.New("config `listenerId` is required")
|
||||
}
|
||||
|
||||
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, cloudCertId); err != nil {
|
||||
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
package wangsucdnpro
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
wangsucdn "github.com/usual2970/certimate/internal/pkg/vendors/wangsu-sdk/cdn"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 网宿云 AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 网宿云 AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
// 网宿云环境。
|
||||
Environment string `json:"environment"`
|
||||
// 加速域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
// 证书 ID。
|
||||
// 选填。
|
||||
CertificateId string `json:"certificateId,omitempty"`
|
||||
// Webhook ID。
|
||||
// 选填。
|
||||
WebhookId string `json:"webhookId,omitempty"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *wangsucdn.Client
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
if d.config.Domain == "" {
|
||||
return nil, errors.New("config `domain` is required")
|
||||
}
|
||||
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询已部署加速域名的详情
|
||||
getHostnameDetailResp, err := d.sdkClient.GetHostnameDetail(d.config.Domain)
|
||||
d.logger.Debug("sdk request 'cdn.GetHostnameDetail'", slog.String("hostname", d.config.Domain), slog.Any("response", getHostnameDetailResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetHostnameDetail'")
|
||||
}
|
||||
|
||||
// 生成网宿云证书参数
|
||||
encryptedPrivateKey, err := encryptPrivateKey(privkeyPem, d.config.AccessKeySecret, time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to encrypt private key")
|
||||
}
|
||||
certificateNewVersionInfo := &wangsucdn.CertificateVersion{
|
||||
PrivateKey: tea.String(encryptedPrivateKey),
|
||||
Certificate: tea.String(certPem),
|
||||
IdentificationInfo: &wangsucdn.CertificateVersionIdentificationInfo{
|
||||
CommonName: tea.String(certX509.Subject.CommonName),
|
||||
SubjectAlternativeNames: &certX509.DNSNames,
|
||||
},
|
||||
}
|
||||
|
||||
// 网宿云证书 URL 中包含证书 ID 及版本号
|
||||
// 格式:
|
||||
// http://open.chinanetcenter.com/cdn/certificates/5dca2205f9e9cc0001df7b33
|
||||
// http://open.chinanetcenter.com/cdn/certificates/329f12c1fe6708c23c31e91f/versions/5
|
||||
var wangsuCertUrl string
|
||||
var wangsuCertId, wangsuCertVer string
|
||||
|
||||
// 如果原证书 ID 为空,则创建证书;否则更新证书。
|
||||
timestamp := time.Now().Unix()
|
||||
if d.config.CertificateId == "" {
|
||||
// 创建证书
|
||||
createCertificateReq := &wangsucdn.CreateCertificateRequest{
|
||||
Timestamp: timestamp,
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
AutoRenew: tea.String("Off"),
|
||||
NewVersion: certificateNewVersionInfo,
|
||||
}
|
||||
createCertificateResp, err := d.sdkClient.CreateCertificate(createCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateCertificate'")
|
||||
}
|
||||
|
||||
wangsuCertUrl = createCertificateResp.CertificateUrl
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
|
||||
|
||||
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertIdMatches) > 1 {
|
||||
wangsuCertId = wangsuCertIdMatches[1]
|
||||
}
|
||||
|
||||
wangsuCertVer = "1"
|
||||
} else {
|
||||
// 更新证书
|
||||
updateCertificateReq := &wangsucdn.UpdateCertificateRequest{
|
||||
Timestamp: timestamp,
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
AutoRenew: tea.String("Off"),
|
||||
NewVersion: certificateNewVersionInfo,
|
||||
}
|
||||
updateCertificateResp, err := d.sdkClient.UpdateCertificate(d.config.CertificateId, updateCertificateReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("certificateId", d.config.CertificateId), slog.Any("request", updateCertificateReq), slog.Any("response", updateCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UpdateCertificate'")
|
||||
}
|
||||
|
||||
wangsuCertUrl = updateCertificateResp.CertificateUrl
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("certUrl", wangsuCertUrl))
|
||||
|
||||
wangsuCertIdMatches := regexp.MustCompile(`/certificates/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertIdMatches) > 1 {
|
||||
wangsuCertId = wangsuCertIdMatches[1]
|
||||
}
|
||||
|
||||
wangsuCertVerMatches := regexp.MustCompile(`/versions/(\d+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertVerMatches) > 1 {
|
||||
wangsuCertVer = wangsuCertVerMatches[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 创建部署任务
|
||||
// REF: https://www.wangsu.com/document/api-doc/27034
|
||||
createDeploymentTaskReq := &wangsucdn.CreateDeploymentTaskRequest{
|
||||
Name: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
Target: tea.String(d.config.Environment),
|
||||
Actions: &[]wangsucdn.DeploymentTaskAction{
|
||||
{
|
||||
Action: tea.String("deploy_cert"),
|
||||
CertificateId: tea.String(wangsuCertId),
|
||||
Version: tea.String(wangsuCertVer),
|
||||
},
|
||||
},
|
||||
}
|
||||
if d.config.WebhookId != "" {
|
||||
createDeploymentTaskReq.Webhook = tea.String(d.config.WebhookId)
|
||||
}
|
||||
createDeploymentTaskResp, err := d.sdkClient.CreateDeploymentTask(createDeploymentTaskReq)
|
||||
d.logger.Debug("sdk request 'cdn.CreateCertificate'", slog.Any("request", createDeploymentTaskReq), slog.Any("response", createDeploymentTaskResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.CreateDeploymentTask'")
|
||||
}
|
||||
|
||||
// 循环获取部署任务详细信息,等待任务状态变更
|
||||
// REF: https://www.wangsu.com/document/api-doc/27038
|
||||
var wangsuTaskId string
|
||||
wangsuTaskMatches := regexp.MustCompile(`/deploymentTasks/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuTaskMatches) > 1 {
|
||||
wangsuTaskId = wangsuTaskMatches[1]
|
||||
}
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId)
|
||||
d.logger.Debug("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDeploymentTaskDetail'")
|
||||
}
|
||||
|
||||
if getDeploymentTaskDetailResp.Status == "failed" {
|
||||
return nil, errors.New("unexpected deployment task status")
|
||||
} else if getDeploymentTaskDetailResp.Status == "succeeded" {
|
||||
break
|
||||
}
|
||||
|
||||
d.logger.Info("waiting for deployment task completion ...")
|
||||
time.Sleep(time.Second * 15)
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(accessKeyId, accessKeySecret string) (*wangsucdn.Client, error) {
|
||||
if accessKeyId == "" {
|
||||
return nil, errors.New("invalid wangsu access key id")
|
||||
}
|
||||
|
||||
if accessKeySecret == "" {
|
||||
return nil, errors.New("invalid wangsu access key secret")
|
||||
}
|
||||
|
||||
return wangsucdn.NewClient(accessKeyId, accessKeySecret), nil
|
||||
}
|
||||
|
||||
func encryptPrivateKey(privkeyPem string, secretKey string, timestamp int64) (string, error) {
|
||||
date := time.Unix(timestamp, 0).UTC()
|
||||
dateStr := date.Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secretKey))
|
||||
mac.Write([]byte(dateStr))
|
||||
aesivkey := mac.Sum(nil)
|
||||
aesivkeyHex := hex.EncodeToString(aesivkey)
|
||||
|
||||
if len(aesivkeyHex) != 64 {
|
||||
return "", fmt.Errorf("invalid hmac length: %d", len(aesivkeyHex))
|
||||
}
|
||||
ivHex := aesivkeyHex[:32]
|
||||
keyHex := aesivkeyHex[32:64]
|
||||
|
||||
iv, err := hex.DecodeString(ivHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode iv: %w", err)
|
||||
}
|
||||
|
||||
key, err := hex.DecodeString(keyHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode key: %w", err)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plainBytes := []byte(privkeyPem)
|
||||
padlen := aes.BlockSize - len(plainBytes)%aes.BlockSize
|
||||
if padlen > 0 {
|
||||
paddata := bytes.Repeat([]byte{byte(padlen)}, padlen)
|
||||
plainBytes = append(plainBytes, paddata...)
|
||||
}
|
||||
|
||||
encBytes := make([]byte, len(plainBytes))
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encBytes, plainBytes)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(encBytes), nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package wangsucdnpro_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/wangsu-cdnpro"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
fEnvironment string
|
||||
fDomain string
|
||||
fCertificateId string
|
||||
fWebhookId string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_WANGSUCDNPRO_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||
flag.StringVar(&fEnvironment, argsPrefix+"ENVIRONMENT", "production", "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
|
||||
flag.StringVar(&fWebhookId, argsPrefix+"WEBHOOKID", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./wangsu_cdnpro_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYID="your-access-key-id" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYSECRET="your-access-key-secret" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ENVIRONMENT="production" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_DOMAIN="example.com" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_CERTIFICATEID="your-certificate-id"\
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_WEBHOOKID="your-webhook-id"
|
||||
*/
|
||||
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("ACCESSKEYSECRET: %v", fAccessKeySecret),
|
||||
fmt.Sprintf("ENVIRONMENT: %v", fEnvironment),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
|
||||
fmt.Sprintf("WEBHOOKID: %v", fWebhookId),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
Environment: fEnvironment,
|
||||
Domain: fDomain,
|
||||
CertificateId: fCertificateId,
|
||||
WebhookId: fWebhookId,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package mattermost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/nikoksr/notify/service/mattermost"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type NotifierConfig struct {
|
||||
// Mattermost 服务地址。
|
||||
ServerUrl string `json:"serverUrl"`
|
||||
// 频道ID
|
||||
ChannelId string `json:"channelId"`
|
||||
// 用户名
|
||||
Username string `json:"username"`
|
||||
// 密码
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type NotifierProvider struct {
|
||||
config *NotifierConfig
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*NotifierProvider)(nil)
|
||||
|
||||
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
|
||||
if logger == nil {
|
||||
n.logger = slog.Default()
|
||||
} else {
|
||||
n.logger = logger
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
srv := mattermost.New(n.config.ServerUrl)
|
||||
|
||||
if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.AddReceivers(n.config.ChannelId)
|
||||
|
||||
// 复写消息样式
|
||||
srv.PreSend(func(req *http.Request) error {
|
||||
m := map[string]interface{}{
|
||||
"channel_id": n.config.ChannelId,
|
||||
"props": map[string]interface{}{
|
||||
"attachments": []map[string]interface{}{
|
||||
{
|
||||
"title": subject,
|
||||
"text": message,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if body, err := json.Marshal(m); err != nil {
|
||||
return err
|
||||
} else {
|
||||
req.ContentLength = int64(len(body))
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err = srv.Send(ctx, subject, message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package mattermost_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
|
||||
)
|
||||
|
||||
const (
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
)
|
||||
|
||||
var (
|
||||
fServerUrl string
|
||||
fChannelId string
|
||||
fUsername string
|
||||
fPassword string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_NOTIFIER_MATTERMOST_"
|
||||
|
||||
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
|
||||
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", "", "")
|
||||
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
|
||||
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./mattermost_test.go -args \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_SERVERURL="https://example.com/your-server-url" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_CHANNELID="your-chanel-id" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_USERNAME="your-username" \
|
||||
--CERTIMATE_NOTIFIER_MATTERMOST_PASSWORD="your-password"
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Notify", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("SERVERURL: %v", fServerUrl),
|
||||
fmt.Sprintf("CHANNELID: %v", fChannelId),
|
||||
fmt.Sprintf("USERNAME: %v", fUsername),
|
||||
fmt.Sprintf("PASSWORD: %v", fPassword),
|
||||
}, "\n"))
|
||||
|
||||
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
ServerUrl: fServerUrl,
|
||||
ChannelId: fChannelId,
|
||||
Username: fUsername,
|
||||
Password: fPassword,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
102
internal/pkg/core/notifier/providers/pushover/pushover.go
Normal file
102
internal/pkg/core/notifier/providers/pushover/pushover.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package pushover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||
)
|
||||
|
||||
type NotifierConfig struct {
|
||||
Token string `json:"token"` // 应用 API Token
|
||||
User string `json:"user"` // 用户/分组 Key
|
||||
}
|
||||
|
||||
type NotifierProvider struct {
|
||||
config *NotifierConfig
|
||||
logger *slog.Logger
|
||||
// 未来将移除
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
var _ notifier.Notifier = (*NotifierProvider)(nil)
|
||||
|
||||
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return &NotifierProvider{
|
||||
config: config,
|
||||
httpClient: http.DefaultClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
|
||||
if logger == nil {
|
||||
n.logger = slog.Default()
|
||||
} else {
|
||||
n.logger = logger
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Notify 发送通知
|
||||
// 参考文档:https://pushover.net/api
|
||||
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
|
||||
// 请求体
|
||||
reqBody := &struct {
|
||||
Token string `json:"token"`
|
||||
User string `json:"user"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Token: n.config.Token,
|
||||
User: n.config.User,
|
||||
Title: subject,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
// Make request
|
||||
body, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "encode message body")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
"https://api.pushover.net/1/messages.json",
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create new request")
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
// Send request to pushover service
|
||||
resp, err := n.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "send request to pushover server")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
result, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read response")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("pushover returned status code %d: %s", resp.StatusCode, string(result))
|
||||
}
|
||||
|
||||
return ¬ifier.NotifyResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package pushover_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
|
||||
)
|
||||
|
||||
const (
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
)
|
||||
|
||||
var (
|
||||
fToken string
|
||||
fUser string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_NOTIFIER_PUSHOVER_"
|
||||
flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "")
|
||||
flag.StringVar(&fUser, argsPrefix+"USER", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./pushover_test.go -args \
|
||||
--CERTIMATE_NOTIFIER_PUSHOVER_TOKEN="your-pushover-token" \
|
||||
--CERTIMATE_NOTIFIER_PUSHOVER_USER="your-pushover-user" \
|
||||
*/
|
||||
func TestNotify(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Notify", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("TOKEN: %v", fToken),
|
||||
}, "\n"))
|
||||
|
||||
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
Token: fToken,
|
||||
User: fUser,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader {
|
||||
|
||||
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
// 遍历证书列表,避免重复上传
|
||||
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
} else if res != nil {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
@@ -82,7 +82,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
|
||||
// 遍历证书列表,获取刚刚上传证书 ID
|
||||
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage())
|
||||
@@ -91,7 +91,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||
searchWebsiteSSLPageNumber := int32(1)
|
||||
searchWebsiteSSLPageSize := int32(100)
|
||||
for {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package rainyunsslcenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
rainyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/rainyun-sdk"
|
||||
)
|
||||
|
||||
type UploaderConfig struct {
|
||||
// 雨云 API 密钥。
|
||||
ApiKey string `json:"ApiKey"`
|
||||
}
|
||||
|
||||
type UploaderProvider struct {
|
||||
config *UploaderConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *rainyunsdk.Client
|
||||
}
|
||||
|
||||
var _ uploader.Uploader = (*UploaderProvider)(nil)
|
||||
|
||||
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.ApiKey)
|
||||
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) {
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res != nil {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SSL 证书上传
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
|
||||
sslCenterCreateReq := &rainyunsdk.SslCenterCreateRequest{
|
||||
Cert: certPem,
|
||||
Key: privkeyPem,
|
||||
}
|
||||
sslCenterCreateResp, err := u.sdkClient.SslCenterCreate(sslCenterCreateReq)
|
||||
u.logger.Debug("sdk request 'sslcenter.Create'", slog.Any("request", sslCenterCreateReq), slog.Any("response", sslCenterCreateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Create'")
|
||||
}
|
||||
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, errors.New("rainyun sslcenter: no certificate found")
|
||||
} else {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 遍历 SSL 证书列表,避免重复上传
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943046
|
||||
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-69943048
|
||||
sslCenterListPage := int32(1)
|
||||
sslCenterListPerPage := int32(100)
|
||||
for {
|
||||
sslCenterListReq := &rainyunsdk.SslCenterListRequest{
|
||||
Filters: &rainyunsdk.SslCenterListFilters{
|
||||
Domain: &certX509.Subject.CommonName,
|
||||
},
|
||||
Page: &sslCenterListPage,
|
||||
PerPage: &sslCenterListPerPage,
|
||||
}
|
||||
sslCenterListResp, err := u.sdkClient.SslCenterList(sslCenterListReq)
|
||||
u.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.List'")
|
||||
}
|
||||
|
||||
if sslCenterListResp.Data != nil && sslCenterListResp.Data.Records != nil {
|
||||
for _, sslItem := range sslCenterListResp.Data.Records {
|
||||
// 先对比证书的多域名
|
||||
if sslItem.Domain != strings.Join(certX509.DNSNames, ", ") {
|
||||
continue
|
||||
}
|
||||
|
||||
// 再对比证书的有效期
|
||||
if sslItem.StartDate != certX509.NotBefore.Unix() || sslItem.ExpireDate != certX509.NotAfter.Unix() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 最后对比证书内容
|
||||
sslCenterGetResp, err := u.sdkClient.SslCenterGet(sslItem.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'sslcenter.Get'")
|
||||
}
|
||||
|
||||
var isSameCert bool
|
||||
if sslCenterGetResp.Data != nil {
|
||||
if sslCenterGetResp.Data.Cert == certPem {
|
||||
isSameCert = true
|
||||
} else {
|
||||
oldCertX509, err := certutil.ParseCertificateFromPEM(sslCenterGetResp.Data.Cert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isSameCert = certutil.EqualCertificate(certX509, oldCertX509)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已存在相同证书,直接返回
|
||||
if isSameCert {
|
||||
return &uploader.UploadResult{
|
||||
CertId: fmt.Sprintf("%d", sslItem.ID),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sslCenterListResp.Data == nil || len(sslCenterListResp.Data.Records) < int(sslCenterListPerPage) {
|
||||
break
|
||||
} else {
|
||||
sslCenterListPage++
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func createSdkClient(apiKey string) (*rainyunsdk.Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("invalid rainyun api key")
|
||||
}
|
||||
|
||||
client := rainyunsdk.NewClient(apiKey)
|
||||
return client, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package rainyunsslcenter_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/rainyun-sslcenter"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fApiKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_UPLOADER_RAINYUNSSLCENTER_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./rainyun_sslcenter_test.go -args \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_UPLOADER_RAINYUNSSLCENTER_APIKEY="your-api-key"
|
||||
*/
|
||||
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("APIKEY: %v", fApiKey),
|
||||
}, "\n"))
|
||||
|
||||
uploader, err := provider.NewUploader(&provider.UploaderConfig{
|
||||
ApiKey: fApiKey,
|
||||
})
|
||||
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))
|
||||
})
|
||||
}
|
||||
@@ -89,10 +89,10 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
u.logger.Debug("sdk request 'ussl.UploadNormalCertificate'", slog.Any("request", uploadNormalCertificateReq), slog.Any("response", uploadNormalCertificateResp))
|
||||
if err != nil {
|
||||
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 {
|
||||
if res, err := u.getExistCert(ctx, certPem); err != nil {
|
||||
if res, err := u.getCertIfExists(ctx, certPem); err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, errors.New("no certificate found")
|
||||
return nil, errors.New("ucloud ssl: no certificate found")
|
||||
} else {
|
||||
u.logger.Info("ssl certificate already exists")
|
||||
return res, nil
|
||||
@@ -112,7 +112,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPem string) (res *uploader.UploadResult, err error) {
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user