chore: move '/internal/pkg' to '/pkg'

This commit is contained in:
Fu Diwei
2025-06-17 15:54:21 +08:00
parent 30840bbba5
commit 205275b52d
611 changed files with 693 additions and 693 deletions

View File

@@ -0,0 +1,232 @@
package onepanelssl
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
onepanelsdk "github.com/usual2970/certimate/pkg/sdk3rd/1panel"
onepanelsdkv2 "github.com/usual2970/certimate/pkg/sdk3rd/1panel/v2"
)
type SSLManagerProviderConfig struct {
// 1Panel 服务地址。
ServerUrl string `json:"serverUrl"`
// 1Panel 版本。
ApiVersion string `json:"apiVersion"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient any
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.ServerUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 遍历证书列表,避免重复上传
if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
} else if res != nil {
m.logger.Info("ssl certificate already exists")
return res, nil
}
// 生成新证书名(需符合 1Panel 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传证书
switch sdkClient := m.sdkClient.(type) {
case *onepanelsdk.Client:
{
uploadWebsiteSSLReq := &onepanelsdk.UploadWebsiteSSLRequest{
Type: "paste",
Description: certName,
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
uploadWebsiteSSLResp, err := sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
m.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request '1panel.UploadWebsiteSSL': %w", err)
}
}
case *onepanelsdkv2.Client:
{
uploadWebsiteSSLReq := &onepanelsdkv2.UploadWebsiteSSLRequest{
Type: "paste",
Description: certName,
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
uploadWebsiteSSLResp, err := sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
m.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request '1panel.UploadWebsiteSSL': %w", err)
}
}
default:
panic("sdk client is not implemented")
}
// 遍历证书列表,获取刚刚上传证书 ID
if res, err := m.findCertIfExists(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
} else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed")
} else {
return res, nil
}
}
func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
searchWebsiteSSLPageNumber := int32(1)
searchWebsiteSSLPageSize := int32(100)
searchWebsiteSSLItemsCount := int32(0)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
switch sdkClient := m.sdkClient.(type) {
case *onepanelsdk.Client:
{
searchWebsiteSSLReq := &onepanelsdk.SearchWebsiteSSLRequest{
Page: searchWebsiteSSLPageNumber,
PageSize: searchWebsiteSSLPageSize,
}
searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq)
m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err)
}
if searchWebsiteSSLResp.Data != nil {
for _, sslItem := range searchWebsiteSSLResp.Data.Items {
if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) &&
strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) {
// 如果已存在相同证书,直接返回
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", sslItem.ID),
CertName: sslItem.Description,
}, nil
}
}
}
searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total
}
case *onepanelsdkv2.Client:
{
searchWebsiteSSLReq := &onepanelsdkv2.SearchWebsiteSSLRequest{
Page: searchWebsiteSSLPageNumber,
PageSize: searchWebsiteSSLPageSize,
}
searchWebsiteSSLResp, err := sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq)
m.logger.Debug("sdk request '1panel.SearchWebsiteSSL'", slog.Any("request", searchWebsiteSSLReq), slog.Any("response", searchWebsiteSSLResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request '1panel.SearchWebsiteSSL': %w", err)
}
if searchWebsiteSSLResp.Data != nil {
for _, sslItem := range searchWebsiteSSLResp.Data.Items {
if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPEM) &&
strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPEM) {
// 如果已存在相同证书,直接返回
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", sslItem.ID),
CertName: sslItem.Description,
}, nil
}
}
}
searchWebsiteSSLItemsCount = searchWebsiteSSLResp.Data.Total
}
default:
panic("sdk client is not implemented")
}
if searchWebsiteSSLItemsCount < searchWebsiteSSLPageSize {
break
} else {
searchWebsiteSSLPageNumber++
}
}
return nil, nil
}
const (
sdkVersionV1 = "v1"
sdkVersionV2 = "v2"
)
func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) (any, error) {
if apiVersion == sdkVersionV1 {
client, err := onepanelsdk.NewClient(serverUrl, apiKey)
if err != nil {
return nil, err
}
if skipTlsVerify {
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
} else if apiVersion == sdkVersionV2 {
client, err := onepanelsdkv2.NewClient(serverUrl, apiKey)
if err != nil {
return nil, err
}
if skipTlsVerify {
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}
return nil, fmt.Errorf("invalid 1panel api version")
}

View File

@@ -0,0 +1,77 @@
package onepanelssl_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/1panel-ssl"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fApiVersion string
fApiKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_1PANELSSL_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiVersion, argsPrefix+"APIVERSION", "v1", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./1panel_ssl_test.go -args \
--CERTIMATE_SSLMANAGER_1PANELSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_1PANELSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_1PANELSSL_SERVERURL="http://127.0.0.1:20410" \
--CERTIMATE_SSLMANAGER_1PANELSSL_APIVERSION="v1" \
--CERTIMATE_SSLMANAGER_1PANELSSL_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("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIVERSION: %v", fApiVersion),
fmt.Sprintf("APIKEY: %v", fApiKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
ServerUrl: fServerUrl,
ApiVersion: fApiVersion,
ApiKey: fApiKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,205 @@
package aliyuncas
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
alicas "github.com/alibabacloud-go/cas-20200407/v3/client"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *alicas.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listusercertificateorder
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail
listUserCertificateOrderPage := int64(1)
listUserCertificateOrderLimit := int64(50)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listUserCertificateOrderReq := &alicas.ListUserCertificateOrderRequest{
ResourceGroupId: xtypes.ToPtrOrZeroNil(m.config.ResourceGroupId),
CurrentPage: tea.Int64(listUserCertificateOrderPage),
ShowSize: tea.Int64(listUserCertificateOrderLimit),
OrderType: tea.String("CERT"),
}
listUserCertificateOrderResp, err := m.sdkClient.ListUserCertificateOrder(listUserCertificateOrderReq)
m.logger.Debug("sdk request 'cas.ListUserCertificateOrder'", slog.Any("request", listUserCertificateOrderReq), slog.Any("response", listUserCertificateOrderResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err)
}
if listUserCertificateOrderResp.Body.CertificateOrderList != nil {
for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList {
if !strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) {
continue
}
getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{
CertId: certDetail.CertificateId,
}
getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetail(getUserCertificateDetailReq)
m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err)
}
var isSameCert bool
if *getUserCertificateDetailResp.Body.Cert == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(*getUserCertificateDetailResp.Body.Cert)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)),
CertName: *certDetail.Name,
ExtendedData: map[string]any{
"instanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId),
"certIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier),
},
}, nil
}
}
}
if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) {
break
} else {
listUserCertificateOrderPage++
}
}
// 生成新证书名(需符合阿里云命名规则)
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-uploadusercertificate
uploadUserCertificateReq := &alicas.UploadUserCertificateRequest{
ResourceGroupId: xtypes.ToPtrOrZeroNil(m.config.ResourceGroupId),
Name: tea.String(certName),
Cert: tea.String(certPEM),
Key: tea.String(privkeyPEM),
}
uploadUserCertificateResp, err := m.sdkClient.UploadUserCertificate(uploadUserCertificateReq)
m.logger.Debug("sdk request 'cas.UploadUserCertificate'", slog.Any("request", uploadUserCertificateReq), slog.Any("response", uploadUserCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.UploadUserCertificate': %w", err)
}
// 获取证书详情
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail
getUserCertificateDetailReq := &alicas.GetUserCertificateDetailRequest{
CertId: uploadUserCertificateResp.Body.CertId,
CertFilter: tea.Bool(true),
}
getUserCertificateDetailResp, err := m.sdkClient.GetUserCertificateDetail(getUserCertificateDetailReq)
m.logger.Debug("sdk request 'cas.GetUserCertificateDetail'", slog.Any("request", getUserCertificateDetailReq), slog.Any("response", getUserCertificateDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", tea.Int64Value(getUserCertificateDetailResp.Body.Id)),
CertName: certName,
ExtendedData: map[string]any{
"instanceId": tea.StringValue(getUserCertificateDetailResp.Body.InstanceId),
"certIdentifier": tea.StringValue(getUserCertificateDetailResp.Body.CertIdentifier),
},
}, nil
}
func createSDKClient(accessKeyId, accessKeySecret, region string) (*alicas.Client, error) {
// 接入点一览 https://api.aliyun.com/product/cas
var endpoint string
switch region {
case "", "cn-hangzhou":
endpoint = "cas.aliyuncs.com"
default:
endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region)
}
config := &aliopen.Config{
Endpoint: tea.String(endpoint),
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
client, err := alicas.NewClient(config)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,160 @@
package aliyunslb
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"regexp"
"strings"
"time"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
alislb "github.com/alibabacloud-go/slb-20140515/v4/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *alislb.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeservercertificates
describeServerCertificatesReq := &alislb.DescribeServerCertificatesRequest{
ResourceGroupId: xtypes.ToPtrOrZeroNil(m.config.ResourceGroupId),
RegionId: tea.String(m.config.Region),
}
describeServerCertificatesResp, err := m.sdkClient.DescribeServerCertificates(describeServerCertificatesReq)
m.logger.Debug("sdk request 'slb.DescribeServerCertificates'", slog.Any("request", describeServerCertificatesReq), slog.Any("response", describeServerCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'slb.DescribeServerCertificates': %w", err)
}
if describeServerCertificatesResp.Body.ServerCertificates != nil && describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate != nil {
fingerprint := sha256.Sum256(certX509.Raw)
fingerprintHex := hex.EncodeToString(fingerprint[:])
for _, certDetail := range describeServerCertificatesResp.Body.ServerCertificates.ServerCertificate {
isSameCert := *certDetail.IsAliCloudCertificate == 0 &&
strings.EqualFold(fingerprintHex, strings.ReplaceAll(*certDetail.Fingerprint, ":", "")) &&
strings.EqualFold(certX509.Subject.CommonName, *certDetail.CommonName)
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: *certDetail.ServerCertificateId,
CertName: *certDetail.ServerCertificateName,
}, nil
}
}
}
// 生成新证书名(需符合阿里云命名规则)
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 去除证书和私钥内容中的空白行,以符合阿里云 API 要求
// REF: https://github.com/usual2970/certimate/issues/326
re := regexp.MustCompile(`(?m)^\s*$\n?`)
certPEM = strings.TrimSpace(re.ReplaceAllString(certPEM, ""))
privkeyPEM = strings.TrimSpace(re.ReplaceAllString(privkeyPEM, ""))
// 上传新证书
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-uploadservercertificate
uploadServerCertificateReq := &alislb.UploadServerCertificateRequest{
ResourceGroupId: xtypes.ToPtrOrZeroNil(m.config.ResourceGroupId),
RegionId: tea.String(m.config.Region),
ServerCertificateName: tea.String(certName),
ServerCertificate: tea.String(certPEM),
PrivateKey: tea.String(privkeyPEM),
}
uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(uploadServerCertificateReq)
m.logger.Debug("sdk request 'slb.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'slb.UploadServerCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: *uploadServerCertificateResp.Body.ServerCertificateId,
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, accessKeySecret, region string) (*alislb.Client, error) {
// 接入点一览 https://api.aliyun.com/product/Slb
var endpoint string
switch region {
case "",
"cn-hangzhou",
"cn-hangzhou-finance",
"cn-shanghai-finance-1",
"cn-shenzhen-finance-1":
endpoint = "slb.aliyuncs.com"
default:
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
}
config := &aliopen.Config{
Endpoint: tea.String(endpoint),
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
client, err := alislb.NewClient(config)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,172 @@
package awsacm
import (
"context"
"errors"
"fmt"
"log/slog"
aws "github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
awscred "github.com/aws/aws-sdk-go-v2/credentials"
awsacm "github.com/aws/aws-sdk-go-v2/service/acm"
"golang.org/x/exp/slices"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// AWS AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// AWS SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *awsacm.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 提取服务器证书
serverCertPEM, intermediaCertPEM, err := xcert.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 获取证书列表,避免重复上传
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
var listCertificatesNextToken *string = nil
var listCertificatesMaxItems int32 = 1000
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &awsacm.ListCertificatesInput{
NextToken: listCertificatesNextToken,
MaxItems: aws.Int32(listCertificatesMaxItems),
}
listCertificatesResp, err := m.sdkClient.ListCertificates(context.TODO(), listCertificatesReq)
m.logger.Debug("sdk request 'acm.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'acm.ListCertificates': %w", err)
}
for _, certSummary := range listCertificatesResp.CertificateSummaryList {
// 先对比证书有效期
if certSummary.NotBefore == nil || !certSummary.NotBefore.Equal(certX509.NotBefore) {
continue
}
if certSummary.NotAfter == nil || !certSummary.NotAfter.Equal(certX509.NotAfter) {
continue
}
// 再对比证书多域名
if !slices.Equal(certX509.DNSNames, certSummary.SubjectAlternativeNameSummaries) {
continue
}
// 最后对比证书内容
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html
getCertificateReq := &awsacm.GetCertificateInput{
CertificateArn: certSummary.CertificateArn,
}
getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), getCertificateReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'acm.GetCertificate': %w", err)
} else {
oldCertPEM := aws.ToString(getCertificateResp.Certificate)
oldCertX509, err := xcert.ParseCertificateFromPEM(oldCertPEM)
if err != nil {
continue
}
if !xcert.EqualCertificate(certX509, oldCertX509) {
continue
}
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: *certSummary.CertificateArn,
}, nil
}
if listCertificatesResp.NextToken == nil || len(listCertificatesResp.CertificateSummaryList) < int(listCertificatesMaxItems) {
break
} else {
listCertificatesNextToken = listCertificatesResp.NextToken
}
}
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
importCertificateReq := &awsacm.ImportCertificateInput{
Certificate: ([]byte)(serverCertPEM),
CertificateChain: ([]byte)(intermediaCertPEM),
PrivateKey: ([]byte)(privkeyPEM),
}
importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
m.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: aws.ToString(importCertificateResp.CertificateArn),
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*awsacm.Client, error) {
cfg, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := awsacm.NewFromConfig(cfg, func(o *awsacm.Options) {
o.Region = region
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
})
return client, nil
}

View File

@@ -0,0 +1,185 @@
package awsiam
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
aws "github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
awscred "github.com/aws/aws-sdk-go-v2/credentials"
awsiam "github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// AWS AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// AWS SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
// IAM 证书路径。
// 选填。
CertificatePath string `json:"certificatePath,omitempty"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *awsiam.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 提取服务器证书
serverCertPEM, intermediaCertPEM, err := xcert.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 获取证书列表,避免重复上传
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_ListServerCertificates.html
var listServerCertificatesMarker *string = nil
var listServerCertificatesMaxItems int32 = 1000
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listServerCertificatesReq := &awsiam.ListServerCertificatesInput{
Marker: listServerCertificatesMarker,
MaxItems: aws.Int32(listServerCertificatesMaxItems),
}
if m.config.CertificatePath != "" {
listServerCertificatesReq.PathPrefix = aws.String(m.config.CertificatePath)
}
listServerCertificatesResp, err := m.sdkClient.ListServerCertificates(context.TODO(), listServerCertificatesReq)
m.logger.Debug("sdk request 'iam.ListServerCertificates'", slog.Any("request", listServerCertificatesReq), slog.Any("response", listServerCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'iam.ListServerCertificates': %w", err)
}
for _, certMeta := range listServerCertificatesResp.ServerCertificateMetadataList {
// 先对比证书路径
if m.config.CertificatePath != "" && aws.ToString(certMeta.Path) != m.config.CertificatePath {
continue
}
// 先对比证书有效期
if certMeta.Expiration == nil || !certMeta.Expiration.Equal(certX509.NotAfter) {
continue
}
// 最后对比证书内容
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html
getServerCertificateReq := &awsiam.GetServerCertificateInput{
ServerCertificateName: certMeta.ServerCertificateName,
}
getServerCertificateResp, err := m.sdkClient.GetServerCertificate(context.TODO(), getServerCertificateReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'iam.GetServerCertificate': %w", err)
} else {
oldCertPEM := aws.ToString(getServerCertificateResp.ServerCertificate.CertificateBody)
oldCertX509, err := xcert.ParseCertificateFromPEM(oldCertPEM)
if err != nil {
continue
}
if !xcert.EqualCertificate(certX509, oldCertX509) {
continue
}
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: aws.ToString(certMeta.ServerCertificateId),
CertName: aws.ToString(certMeta.ServerCertificateName),
}, nil
}
if listServerCertificatesResp.Marker == nil || len(listServerCertificatesResp.ServerCertificateMetadataList) < int(listServerCertificatesMaxItems) {
break
} else {
listServerCertificatesMarker = listServerCertificatesResp.Marker
}
}
// 生成新证书名(需符合 AWS IAM 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_UploadServerCertificate.html
uploadServerCertificateReq := &awsiam.UploadServerCertificateInput{
ServerCertificateName: aws.String(certName),
Path: aws.String(m.config.CertificatePath),
CertificateBody: aws.String(serverCertPEM),
CertificateChain: aws.String(intermediaCertPEM),
PrivateKey: aws.String(privkeyPEM),
}
if m.config.CertificatePath == "" {
uploadServerCertificateReq.Path = aws.String("/")
}
uploadServerCertificateResp, err := m.sdkClient.UploadServerCertificate(context.TODO(), uploadServerCertificateReq)
m.logger.Debug("sdk request 'iam.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'iam.UploadServerCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.ServerCertificateId),
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*awsiam.Client, error) {
cfg, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := awsiam.NewFromConfig(cfg, func(o *awsiam.Options) {
o.Region = region
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
})
return client, nil
}

View File

@@ -0,0 +1,204 @@
package azurekeyvault
import (
"context"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"log/slog"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates"
"github.com/usual2970/certimate/pkg/core"
azenv "github.com/usual2970/certimate/pkg/sdk3rd/azure/env"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// Azure TenantId。
TenantId string `json:"tenantId"`
// Azure ClientId。
ClientId string `json:"clientId"`
// Azure ClientSecret。
ClientSecret string `json:"clientSecret"`
// Azure 主权云环境。
CloudName string `json:"cloudName,omitempty"`
// Key Vault 名称。
KeyVaultName string `json:"keyvaultName"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *azcertificates.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 生成 Azure 业务参数
const TAG_CERTCN = "certimate/cert-cn"
const TAG_CERTSN = "certimate/cert-sn"
certCN := certX509.Subject.CommonName
certSN := certX509.SerialNumber.Text(16)
// 获取证书列表,避免重复上传
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificates/get-certificates
listCertificatesPager := m.sdkClient.NewListCertificatePropertiesPager(nil)
for listCertificatesPager.More() {
page, err := listCertificatesPager.NextPage(context.TODO())
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificates': %w", err)
}
for _, certProp := range page.Value {
// 先对比证书有效期
if certProp.Attributes == nil {
continue
}
if certProp.Attributes.NotBefore == nil || !certProp.Attributes.NotBefore.Equal(certX509.NotBefore) {
continue
}
if certProp.Attributes.Expires == nil || !certProp.Attributes.Expires.Equal(certX509.NotAfter) {
continue
}
// 再对比 Tag 中的通用名称
if v, ok := certProp.Tags[TAG_CERTCN]; !ok || v == nil {
continue
} else if *v != certCN {
continue
}
// 再对比 Tag 中的序列号
if v, ok := certProp.Tags[TAG_CERTSN]; !ok || v == nil {
continue
} else if *v != certSN {
continue
}
// 最后对比证书内容
getCertificateResp, err := m.sdkClient.GetCertificate(context.TODO(), certProp.ID.Name(), certProp.ID.Version(), nil)
m.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", certProp.ID.Name()), slog.String("request.certificateVersion", certProp.ID.Version()), slog.Any("response", getCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'keyvault.GetCertificate': %w", err)
} else {
oldCertX509, err := x509.ParseCertificate(getCertificateResp.CER)
if err != nil {
continue
}
if !xcert.EqualCertificate(certX509, oldCertX509) {
continue
}
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: string(*certProp.ID),
CertName: certProp.ID.Name(),
}, nil
}
}
// 生成新证书名(需符合 Azure 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// Azure Key Vault 不支持导入带有 Certificiate Chain 的 PEM 证书。
// Issue Link: https://github.com/Azure/azure-cli/issues/19017
// 暂时的解决方法是,将 PEM 证书转换成 PFX 格式,然后再导入。
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, "")
if err != nil {
return nil, fmt.Errorf("failed to transform certificate from PEM to PFX: %w", err)
}
// 导入证书
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
importCertificateParams := azcertificates.ImportCertificateParameters{
Base64EncodedCertificate: to.Ptr(base64.StdEncoding.EncodeToString(certPFX)),
CertificatePolicy: &azcertificates.CertificatePolicy{
SecretProperties: &azcertificates.SecretProperties{
ContentType: to.Ptr("application/x-pkcs12"),
},
},
Tags: map[string]*string{
TAG_CERTCN: to.Ptr(certCN),
TAG_CERTSN: to.Ptr(certSN),
},
}
importCertificateResp, err := m.sdkClient.ImportCertificate(context.TODO(), certName, importCertificateParams, nil)
m.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", certName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'keyvault.ImportCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: string(*importCertificateResp.ID),
CertName: certName,
}, nil
}
func createSDKClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) {
env, err := azenv.GetCloudEnvConfiguration(cloudName)
if err != nil {
return nil, err
}
clientOptions := azcore.ClientOptions{Cloud: env}
credential, err := azidentity.NewClientSecretCredential(tenantId, clientId, clientSecret,
&azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
if err != nil {
return nil, err
}
endpoint := fmt.Sprintf("https://%s.vault.azure.net", keyvaultName)
if azenv.IsUSGovernmentEnv(cloudName) {
endpoint = fmt.Sprintf("https://%s.vault.usgovcloudapi.net", keyvaultName)
} else if azenv.IsChinaEnv(cloudName) {
endpoint = fmt.Sprintf("https://%s.vault.azure.cn", keyvaultName)
}
client, err := azcertificates.NewClient(endpoint, credential, nil)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,87 @@
package azurekeyvault_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/azure-keyvault"
)
var (
fInputCertPath string
fInputKeyPath string
fTenantId string
fClientId string
fClientSecret string
fCloudName string
fKeyVaultName string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_AZUREKEYVAULT_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fTenantId, argsPrefix+"TENANTID", "", "")
flag.StringVar(&fClientId, argsPrefix+"CLIENTID", "", "")
flag.StringVar(&fClientSecret, argsPrefix+"CLIENTSECRET", "", "")
flag.StringVar(&fCloudName, argsPrefix+"CLOUDNAME", "", "")
flag.StringVar(&fKeyVaultName, argsPrefix+"KEYVAULTNAME", "", "")
}
/*
Shell command to run this test:
go test -v ./azure_keyvault_test.go -args \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_TENANTID="your-tenant-id" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_CLIENTID="your-app-registration-client-id" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_CLIENTSECRET="your-app-registration-client-secret" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_CLOUDNAME="china" \
--CERTIMATE_SSLMANAGER_AZUREKEYVAULT_KEYVAULTNAME="your-keyvault-name"
*/
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("TENANTID: %v", fTenantId),
fmt.Sprintf("CLIENTID: %v", fClientId),
fmt.Sprintf("CLIENTSECRET: %v", fClientSecret),
fmt.Sprintf("CLOUDNAME: %v", fCloudName),
fmt.Sprintf("KEYVAULTNAME: %v", fKeyVaultName),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
TenantId: fTenantId,
ClientId: fClientId,
ClientSecret: fClientSecret,
CloudName: fCloudName,
KeyVaultName: fKeyVaultName,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,137 @@
package baiducloudcert
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
bdsdk "github.com/usual2970/certimate/pkg/sdk3rd/baiducloud/cert"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 百度智能云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 百度智能云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *bdsdk.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.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 := m.sdkClient.ListCertDetail()
m.logger.Debug("sdk request 'cert.ListCertDetail'", slog.Any("response", listCertDetail))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cert.ListCertDetail': %w", err)
} 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 := m.sdkClient.GetCertRawData(certDetail.CertId)
m.logger.Debug("sdk request 'cert.GetCertRawData'", slog.Any("certId", certDetail.CertId), slog.Any("response", getCertDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cert.GetCertRawData': %w", err)
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(getCertDetailResp.CertServerData)
if err != nil {
continue
}
if !xcert.EqualCertificate(certX509, oldCertX509) {
continue
}
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
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 := m.sdkClient.CreateCert(createCertReq)
m.logger.Debug("sdk request 'cert.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cert.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
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
}

View File

@@ -0,0 +1,72 @@
package baiducloudcert_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/baiducloud-cert"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_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_SSLMANAGER_BAIDUCLOUDCAS_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_BAIDUCLOUDCAS_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_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"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,136 @@
package bytepluscdn
import (
"context"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"strings"
"time"
bytepluscdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// BytePlus AccessKey。
AccessKey string `json:"accessKey"`
// BytePlus SecretKey。
SecretKey string `json:"secretKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *bytepluscdn.CDN
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client := bytepluscdn.NewInstance()
client.Client.SetAccessKey(config.AccessKey)
client.Client.SetSecretKey(config.SecretKey)
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo
listCertInfoPageNum := int64(1)
listCertInfoPageSize := int64(100)
listCertInfoTotal := 0
listCertInfoReq := &bytepluscdn.ListCertInfoRequest{
PageNum: bytepluscdn.GetInt64Ptr(listCertInfoPageNum),
PageSize: bytepluscdn.GetInt64Ptr(listCertInfoPageSize),
Source: bytepluscdn.GetStrPtr("cert_center"),
}
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq)
m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err)
}
if listCertInfoResp.Result.CertInfo != nil {
for _, certDetail := range listCertInfoResp.Result.CertInfo {
fingerprintSha1 := sha1.Sum(certX509.Raw)
fingerprintSha256 := sha256.Sum256(certX509.Raw)
isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) &&
strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256)
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.CertId,
CertName: certDetail.Desc,
}, nil
}
}
}
listCertInfoLen := len(listCertInfoResp.Result.CertInfo)
if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen {
break
} else {
listCertInfoPageNum++
listCertInfoTotal += listCertInfoLen
}
}
// 生成新证书名(需符合 BytePlus 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-addcertificate
addCertificateReq := &bytepluscdn.AddCertificateRequest{
Certificate: certPEM,
PrivateKey: privkeyPEM,
Source: bytepluscdn.GetStrPtr("cert_center"),
Desc: bytepluscdn.GetStrPtr(certName),
}
addCertificateResp, err := m.sdkClient.AddCertificate(addCertificateReq)
m.logger.Debug("sdk request 'cdn.AddCertificate'", slog.Any("request", addCertificateReq), slog.Any("response", addCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.AddCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: addCertificateResp.Result.CertId,
CertName: certName,
}, nil
}

View File

@@ -0,0 +1,171 @@
package ctcccloudao
import (
"context"
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
ctyunao "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/ao"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyunao.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询用户名下证书列表,避免重复上传
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13175&data=174&isNormal=1&vid=167
listCertPage := int32(1)
listCertPerPage := int32(1000)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertsReq := &ctyunao.ListCertsRequest{
Page: xtypes.ToPtr(listCertPage),
PerPage: xtypes.ToPtr(listCertPerPage),
UsageMode: xtypes.ToPtr(int32(0)),
}
listCertsResp, err := m.sdkClient.ListCerts(listCertsReq)
m.logger.Debug("sdk request 'ao.ListCerts'", slog.Any("request", listCertsReq), slog.Any("response", listCertsResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ao.ListCerts': %w", err)
}
if listCertsResp.ReturnObj != nil {
for _, certRecord := range listCertsResp.ReturnObj.Results {
// 对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) {
continue
}
// 对比证书扩展名称
if !slices.Equal(certX509.DNSNames, certRecord.SANs) {
continue
}
// 对比证书有效期
if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) {
continue
} else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) {
continue
}
// 查询证书详情
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13015&data=174&isNormal=1&vid=167
queryCertReq := &ctyunao.QueryCertRequest{
Id: xtypes.ToPtr(certRecord.Id),
}
queryCertResp, err := m.sdkClient.QueryCert(queryCertReq)
m.logger.Debug("sdk request 'ao.QueryCert'", slog.Any("request", queryCertReq), slog.Any("response", queryCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ao.QueryCert': %w", err)
} else if queryCertResp.ReturnObj != nil && queryCertResp.ReturnObj.Result != nil {
var isSameCert bool
if queryCertResp.ReturnObj.Result.Certs == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(queryCertResp.ReturnObj.Result.Certs)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", queryCertResp.ReturnObj.Result.Id),
CertName: queryCertResp.ReturnObj.Result.Name,
}, nil
}
}
}
}
if listCertsResp.ReturnObj == nil || len(listCertsResp.ReturnObj.Results) < int(listCertPerPage) {
break
} else {
listCertPage++
}
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=113&api=13014&data=174&isNormal=1&vid=167
createCertReq := &ctyunao.CreateCertRequest{
Name: xtypes.ToPtr(certName),
Certs: xtypes.ToPtr(certPEM),
Key: xtypes.ToPtr(privkeyPEM),
}
createCertResp, err := m.sdkClient.CreateCert(createCertReq)
m.logger.Debug("sdk request 'ao.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ao.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", createCertResp.ReturnObj.Id),
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunao.Client, error) {
return ctyunao.NewClient(accessKeyId, secretAccessKey)
}

View File

@@ -0,0 +1,72 @@
package ctcccloudao_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-ao"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDAO_"
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 ./ctcccloud_ao_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDAO_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDAO_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDAO_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDAO_SECRETACCESSKEY="your-secret-access-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("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,171 @@
package ctcccloudcdn
import (
"context"
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
ctyuncdn "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/cdn"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyuncdn.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10901&data=161&isNormal=1&vid=154
queryCertListPage := int32(1)
queryCertListPerPage := int32(1000)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
queryCertListReq := &ctyuncdn.QueryCertListRequest{
Page: xtypes.ToPtr(queryCertListPage),
PerPage: xtypes.ToPtr(queryCertListPerPage),
UsageMode: xtypes.ToPtr(int32(0)),
}
queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq)
m.logger.Debug("sdk request 'cdn.QueryCertList'", slog.Any("request", queryCertListReq), slog.Any("response", queryCertListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertList': %w", err)
}
if queryCertListResp.ReturnObj != nil {
for _, certRecord := range queryCertListResp.ReturnObj.Results {
// 对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) {
continue
}
// 对比证书扩展名称
if !slices.Equal(certX509.DNSNames, certRecord.SANs) {
continue
}
// 对比证书有效期
if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) {
continue
} else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) {
continue
}
// 查询证书详情
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10899&data=161&isNormal=1&vid=154
queryCertDetailReq := &ctyuncdn.QueryCertDetailRequest{
Id: xtypes.ToPtr(certRecord.Id),
}
queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq)
m.logger.Debug("sdk request 'cdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.QueryCertDetail': %w", err)
} else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil {
var isSameCert bool
if queryCertDetailResp.ReturnObj.Result.Certs == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(queryCertDetailResp.ReturnObj.Result.Certs)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id),
CertName: queryCertDetailResp.ReturnObj.Result.Name,
}, nil
}
}
}
}
if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) {
break
} else {
queryCertListPage++
}
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=108&api=10893&data=161&isNormal=1&vid=154
createCertReq := &ctyuncdn.CreateCertRequest{
Name: xtypes.ToPtr(certName),
Certs: xtypes.ToPtr(certPEM),
Key: xtypes.ToPtr(privkeyPEM),
}
createCertResp, err := m.sdkClient.CreateCert(createCertReq)
m.logger.Debug("sdk request 'cdn.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", createCertResp.ReturnObj.Id),
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyuncdn.Client, error) {
return ctyuncdn.NewClient(accessKeyId, secretAccessKey)
}

View File

@@ -0,0 +1,72 @@
package ctcccloudcdn_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-cdn"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDCDN_"
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 ./ctcccloud_cdn_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCDN_SECRETACCESSKEY="your-secret-access-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("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,185 @@
package ctcccloudcms
import (
"context"
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
ctyuncms "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/cms"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyuncms.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 遍历证书列表,避免重复上传
if res, _ := m.findCertIfExists(ctx, certPEM); res != nil {
return res, nil
}
// 提取服务器证书
serverCertPEM, intermediaCertPEM, err := xcert.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("cm%d", time.Now().Unix())
// 上传证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=152&api=17243&data=204&isNormal=1&vid=283
uploadCertificateReq := &ctyuncms.UploadCertificateRequest{
Name: xtypes.ToPtr(certName),
Certificate: xtypes.ToPtr(serverCertPEM),
CertificateChain: xtypes.ToPtr(intermediaCertPEM),
PrivateKey: xtypes.ToPtr(privkeyPEM),
EncryptionStandard: xtypes.ToPtr("INTERNATIONAL"),
}
uploadCertificateResp, err := m.sdkClient.UploadCertificate(uploadCertificateReq)
m.logger.Debug("sdk request 'cms.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp))
if err != nil {
if uploadCertificateResp != nil && uploadCertificateResp.GetError() == "CCMS_100000067" {
if res, err := m.findCertIfExists(ctx, certPEM); err != nil {
return nil, err
} else if res == nil {
return nil, errors.New("ctyun cms: no certificate found")
} else {
m.logger.Info("ssl certificate already exists")
return res, nil
}
}
return nil, fmt.Errorf("failed to execute sdk request 'cms.UploadCertificate': %w", err)
}
// 遍历证书列表,获取刚刚上传证书 ID
if res, err := m.findCertIfExists(ctx, certPEM); err != nil {
return nil, err
} else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed")
} else {
return res, nil
}
}
func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询用户证书列表
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=152&api=17233&data=204&isNormal=1&vid=283
getCertificateListPageNum := int32(1)
getCertificateListPageSize := int32(10)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
getCertificateListReq := &ctyuncms.GetCertificateListRequest{
PageNum: xtypes.ToPtr(getCertificateListPageNum),
PageSize: xtypes.ToPtr(getCertificateListPageSize),
Keyword: xtypes.ToPtr(certX509.Subject.CommonName),
Origin: xtypes.ToPtr("UPLOAD"),
}
getCertificateListResp, err := m.sdkClient.GetCertificateList(getCertificateListReq)
m.logger.Debug("sdk request 'cms.GetCertificateList'", slog.Any("request", getCertificateListReq), slog.Any("response", getCertificateListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cms.GetCertificateList': %w", err)
}
if getCertificateListResp.ReturnObj != nil {
fingerprint := sha1.Sum(certX509.Raw)
fingerprintHex := hex.EncodeToString(fingerprint[:])
for _, certRecord := range getCertificateListResp.ReturnObj.List {
// 对比证书名称
if !strings.EqualFold(strings.Join(certX509.DNSNames, ","), certRecord.DomainName) {
continue
}
// 对比证书有效期
oldCertNotBefore, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.IssueTime)
oldCertNotAfter, _ := time.Parse("2006-01-02T15:04:05Z", certRecord.ExpireTime)
if !certX509.NotBefore.Equal(oldCertNotBefore) {
continue
} else if !certX509.NotAfter.Equal(oldCertNotAfter) {
continue
}
// 对比证书指纹
if !strings.EqualFold(fingerprintHex, certRecord.Fingerprint) {
continue
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: string(*&certRecord.Id),
CertName: certRecord.Name,
}, nil
}
}
if getCertificateListResp.ReturnObj == nil || len(getCertificateListResp.ReturnObj.List) < int(getCertificateListPageSize) {
break
} else {
getCertificateListPageNum++
}
}
return nil, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyuncms.Client, error) {
return ctyuncms.NewClient(accessKeyId, secretAccessKey)
}

View File

@@ -0,0 +1,72 @@
package ctcccloudcms_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-cms"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDCMS_"
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 ./ctcccloud_cms_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCMS_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCMS_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCMS_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDCMS_SECRETACCESSKEY="your-secret-access-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("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,133 @@
package ctcccloudelb
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/google/uuid"
"github.com/usual2970/certimate/pkg/core"
ctyunelb "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/elb"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 天翼云资源池 ID。
RegionId string `json:"regionId"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyunelb.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5692&data=88&isNormal=1&vid=82
listCertificatesReq := &ctyunelb.ListCertificatesRequest{
RegionID: xtypes.ToPtr(m.config.RegionId),
}
listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq)
m.logger.Debug("sdk request 'elb.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
} else {
for _, certRecord := range listCertificatesResp.ReturnObj {
var isSameCert bool
if certRecord.Certificate == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(certRecord.Certificate)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certRecord.ID,
CertName: certRecord.Name,
}, nil
}
}
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=24&api=5685&data=88&isNormal=1&vid=82
createCertificateReq := &ctyunelb.CreateCertificateRequest{
ClientToken: xtypes.ToPtr(generateClientToken()),
RegionID: xtypes.ToPtr(m.config.RegionId),
Name: xtypes.ToPtr(certName),
Description: xtypes.ToPtr("upload from certimate"),
Type: xtypes.ToPtr("Server"),
Certificate: xtypes.ToPtr(certPEM),
PrivateKey: xtypes.ToPtr(privkeyPEM),
}
createCertificateResp, err := m.sdkClient.CreateCertificate(createCertificateReq)
m.logger.Debug("sdk request 'elb.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.CreateCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: createCertificateResp.ReturnObj.ID,
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunelb.Client, error) {
return ctyunelb.NewClient(accessKeyId, secretAccessKey)
}
func generateClientToken() string {
return uuid.New().String()
}

View File

@@ -0,0 +1,77 @@
package ctcccloudelb_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-elb"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
fRegionId string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDELB_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
flag.StringVar(&fRegionId, argsPrefix+"REGIONID", "", "")
}
/*
Shell command to run this test:
go test -v ./ctcccloud_elb_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDELB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDELB_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDELB_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDELB_SECRETACCESSKEY="your-secret-access-key" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDELB_REGIONID="your-region-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("SECRETACCESSKEY: %v", fSecretAccessKey),
fmt.Sprintf("REGIONID: %v", fRegionId),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
RegionId: fRegionId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,171 @@
package ctcccloudicdn
import (
"context"
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
ctyunicdn "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/icdn"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyunicdn.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10838&data=173&isNormal=1&vid=166
queryCertListPage := int32(1)
queryCertListPerPage := int32(1000)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
queryCertListReq := &ctyunicdn.QueryCertListRequest{
Page: xtypes.ToPtr(queryCertListPage),
PerPage: xtypes.ToPtr(queryCertListPerPage),
UsageMode: xtypes.ToPtr(int32(0)),
}
queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq)
m.logger.Debug("sdk request 'icdn.QueryCertList'", slog.Any("request", queryCertListReq), slog.Any("response", queryCertListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertList': %w", err)
}
if queryCertListResp.ReturnObj != nil {
for _, certRecord := range queryCertListResp.ReturnObj.Results {
// 对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) {
continue
}
// 对比证书扩展名称
if !slices.Equal(certX509.DNSNames, certRecord.SANs) {
continue
}
// 对比证书有效期
if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) {
continue
} else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) {
continue
}
// 查询证书详情
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10837&data=173&isNormal=1&vid=166
queryCertDetailReq := &ctyunicdn.QueryCertDetailRequest{
Id: xtypes.ToPtr(certRecord.Id),
}
queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq)
m.logger.Debug("sdk request 'icdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'icdn.QueryCertDetail': %w", err)
} else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil {
var isSameCert bool
if queryCertDetailResp.ReturnObj.Result.Certs == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(queryCertDetailResp.ReturnObj.Result.Certs)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id),
CertName: queryCertDetailResp.ReturnObj.Result.Name,
}, nil
}
}
}
}
if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) {
break
} else {
queryCertListPage++
}
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=112&api=10835&data=173&isNormal=1&vid=166
createCertReq := &ctyunicdn.CreateCertRequest{
Name: xtypes.ToPtr(certName),
Certs: xtypes.ToPtr(certPEM),
Key: xtypes.ToPtr(privkeyPEM),
}
createCertResp, err := m.sdkClient.CreateCert(createCertReq)
m.logger.Debug("sdk request 'icdn.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'icdn.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", createCertResp.ReturnObj.Id),
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunicdn.Client, error) {
return ctyunicdn.NewClient(accessKeyId, secretAccessKey)
}

View File

@@ -0,0 +1,72 @@
package ctcccloudicdn_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-icdn"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDICDN_"
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 ./ctcccloud_icdn_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDICDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDICDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDICDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDICDN_SECRETACCESSKEY="your-secret-access-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("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,171 @@
package ctcccloudlvdn
import (
"context"
"errors"
"fmt"
"log/slog"
"slices"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
ctyunlvdn "github.com/usual2970/certimate/pkg/sdk3rd/ctyun/lvdn"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 天翼云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 天翼云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *ctyunlvdn.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11452&data=183&isNormal=1&vid=261
queryCertListPage := int32(1)
queryCertListPerPage := int32(1000)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
queryCertListReq := &ctyunlvdn.QueryCertListRequest{
Page: xtypes.ToPtr(queryCertListPage),
PerPage: xtypes.ToPtr(queryCertListPerPage),
UsageMode: xtypes.ToPtr(int32(0)),
}
queryCertListResp, err := m.sdkClient.QueryCertList(queryCertListReq)
m.logger.Debug("sdk request 'lvdn.QueryCertList'", slog.Any("request", queryCertListReq), slog.Any("response", queryCertListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertList': %w", err)
}
if queryCertListResp.ReturnObj != nil {
for _, certRecord := range queryCertListResp.ReturnObj.Results {
// 对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certRecord.CN) {
continue
}
// 对比证书扩展名称
if !slices.Equal(certX509.DNSNames, certRecord.SANs) {
continue
}
// 对比证书有效期
if !certX509.NotBefore.Equal(time.Unix(certRecord.IssueTime, 0).UTC()) {
continue
} else if !certX509.NotAfter.Equal(time.Unix(certRecord.ExpiresTime, 0).UTC()) {
continue
}
// 查询证书详情
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11449&data=183&isNormal=1&vid=261
queryCertDetailReq := &ctyunlvdn.QueryCertDetailRequest{
Id: xtypes.ToPtr(certRecord.Id),
}
queryCertDetailResp, err := m.sdkClient.QueryCertDetail(queryCertDetailReq)
m.logger.Debug("sdk request 'lvdn.QueryCertDetail'", slog.Any("request", queryCertDetailReq), slog.Any("response", queryCertDetailResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'lvdn.QueryCertDetail': %w", err)
} else if queryCertDetailResp.ReturnObj != nil && queryCertDetailResp.ReturnObj.Result != nil {
var isSameCert bool
if queryCertDetailResp.ReturnObj.Result.Certs == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(queryCertDetailResp.ReturnObj.Result.Certs)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", queryCertDetailResp.ReturnObj.Result.Id),
CertName: queryCertDetailResp.ReturnObj.Result.Name,
}, nil
}
}
}
}
if queryCertListResp.ReturnObj == nil || len(queryCertListResp.ReturnObj.Results) < int(queryCertListPerPage) {
break
} else {
queryCertListPage++
}
}
// 生成新证书名(需符合天翼云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://eop.ctyun.cn/ebp/ctapiDocument/search?sid=125&api=11436&data=183&isNormal=1&vid=261
createCertReq := &ctyunlvdn.CreateCertRequest{
Name: xtypes.ToPtr(certName),
Certs: xtypes.ToPtr(certPEM),
Key: xtypes.ToPtr(privkeyPEM),
}
createCertResp, err := m.sdkClient.CreateCert(createCertReq)
m.logger.Debug("sdk request 'lvdn.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'lvdn.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", createCertResp.ReturnObj.Id),
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey string) (*ctyunlvdn.Client, error) {
return ctyunlvdn.NewClient(accessKeyId, secretAccessKey)
}

View File

@@ -0,0 +1,72 @@
package ctcccloudlvdn_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ctcccloud-lvdn"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_CTCCCLOUDLVDN_"
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 ./ctcccloud_lvdn_test.go -args \
--CERTIMATE_SSLMANAGER_CTCCCLOUDLVDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDLVDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDLVDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_CTCCCLOUDLVDN_SECRETACCESSKEY="your-secret-access-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("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,79 @@
package dogecloud
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/usual2970/certimate/pkg/core"
dogesdk "github.com/usual2970/certimate/pkg/sdk3rd/dogecloud"
)
type SSLManagerProviderConfig struct {
// 多吉云 AccessKey。
AccessKey string `json:"accessKey"`
// 多吉云 SecretKey。
SecretKey string `json:"secretKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *dogesdk.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKey, config.SecretKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 生成新证书名(需符合多吉云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://docs.dogecloud.com/cdn/api-cert-upload
uploadSslCertReq := &dogesdk.UploadCdnCertRequest{
Note: certName,
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
uploadSslCertResp, err := m.sdkClient.UploadCdnCert(uploadSslCertReq)
m.logger.Debug("sdk request 'cdn.UploadCdnCert'", slog.Any("request", uploadSslCertReq), slog.Any("response", uploadSslCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.UploadCdnCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", uploadSslCertResp.Data.Id),
CertName: certName,
}, nil
}
func createSDKClient(accessKey, secretKey string) (*dogesdk.Client, error) {
return dogesdk.NewClient(accessKey, secretKey)
}

View File

@@ -0,0 +1,88 @@
package gcorecdn
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/G-Core/gcorelabscdn-go/gcore/provider"
"github.com/G-Core/gcorelabscdn-go/sslcerts"
"github.com/usual2970/certimate/pkg/core"
gcoresdk "github.com/usual2970/certimate/pkg/sdk3rd/gcore"
)
type SSLManagerProviderConfig struct {
// Gcore API Token。
ApiToken string `json:"apiToken"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *sslcerts.Service
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.ApiToken)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 新增证书
// REF: https://api.gcore.com/docs/cdn#tag/SSL-certificates/operation/add_ssl_certificates
createCertificateReq := &sslcerts.CreateRequest{
Name: fmt.Sprintf("certimate_%d", time.Now().UnixMilli()),
Cert: certPEM,
PrivateKey: privkeyPEM,
Automated: false,
ValidateRootCA: false,
}
createCertificateResp, err := m.sdkClient.Create(context.TODO(), createCertificateReq)
m.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Create': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", createCertificateResp.ID),
CertName: createCertificateResp.Name,
}, nil
}
func createSDKClient(apiToken string) (*sslcerts.Service, error) {
if apiToken == "" {
return nil, errors.New("invalid gcore api token")
}
requester := provider.NewClient(
gcoresdk.BASE_URL,
provider.WithSigner(gcoresdk.NewAuthRequestSigner(apiToken)),
)
service := sslcerts.NewService(requester)
return service, nil
}

View File

@@ -0,0 +1,233 @@
package huaweicloudelb
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
hcelb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
hcelbmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
hcelbregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
hciam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
hciammodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
hciamregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云企业项目 ID。
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
// 华为云区域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *hcelb.ElbClient
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 遍历查询已有证书,避免重复上传
// REF: https://support.huaweicloud.com/api-elb/ListCertificates.html
listCertificatesLimit := int32(2000)
var listCertificatesMarker *string = nil
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &hcelbmodel.ListCertificatesRequest{
Limit: xtypes.ToPtr(listCertificatesLimit),
Marker: listCertificatesMarker,
Type: &[]string{"server"},
}
listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq)
m.logger.Debug("sdk request 'elb.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
}
if listCertificatesResp.Certificates != nil {
for _, certDetail := range *listCertificatesResp.Certificates {
var isSameCert bool
if certDetail.Certificate == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(certDetail.Certificate)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.Id,
CertName: certDetail.Name,
}, nil
}
}
}
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
break
} else {
listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker
}
}
// 获取项目 ID
// REF: https://support.huaweicloud.com/api-iam/iam_06_0001.html
projectId, err := getSdkProjectId(m.config.AccessKeyId, m.config.SecretAccessKey, m.config.Region)
if err != nil {
return nil, fmt.Errorf("failed to get SDK project id: %w", err)
}
// 生成新证书名(需符合华为云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建新证书
// REF: https://support.huaweicloud.com/api-elb/CreateCertificate.html
createCertificateReq := &hcelbmodel.CreateCertificateRequest{
Body: &hcelbmodel.CreateCertificateRequestBody{
Certificate: &hcelbmodel.CreateCertificateOption{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
ProjectId: xtypes.ToPtr(projectId),
Name: xtypes.ToPtr(certName),
Certificate: xtypes.ToPtr(certPEM),
PrivateKey: xtypes.ToPtr(privkeyPEM),
},
},
}
createCertificateResp, err := m.sdkClient.CreateCertificate(createCertificateReq)
m.logger.Debug("sdk request 'elb.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.CreateCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: createCertificateResp.Certificate.Id,
CertName: createCertificateResp.Certificate.Name,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*hcelb.ElbClient, error) {
if region == "" {
region = "cn-north-4" // ELB 服务默认区域:华北四北京
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hcelbregion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hcelb.ElbClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hcelb.NewElbClient(hcClient)
return client, nil
}
func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
if region == "" {
region = "cn-north-4" // IAM 服务默认区域:华北四北京
}
auth, err := global.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return "", err
}
hcRegion, err := hciamregion.SafeValueOf(region)
if err != nil {
return "", err
}
hcClient, err := hciam.IamClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return "", err
}
client := hciam.NewIamClient(hcClient)
request := &hciammodel.KeystoneListProjectsRequest{
Name: &region,
}
response, err := client.KeystoneListProjects(request)
if err != nil {
return "", err
} else if response.Projects == nil || len(*response.Projects) == 0 {
return "", errors.New("no project found")
}
return (*response.Projects)[0].Id, nil
}

View File

@@ -0,0 +1,193 @@
package huaweicloudscm
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
hcscm "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3"
hcscmmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/model"
hcscmregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/region"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云企业项目 ID。
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
// 华为云区域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *hcscm.ScmClient
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 遍历查询已有证书,避免重复上传
// REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html
// REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html
listCertificatesLimit := int32(50)
listCertificatesOffset := int32(0)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &hcscmmodel.ListCertificatesRequest{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
Limit: xtypes.ToPtr(listCertificatesLimit),
Offset: xtypes.ToPtr(listCertificatesOffset),
SortDir: xtypes.ToPtr("DESC"),
SortKey: xtypes.ToPtr("certExpiredTime"),
}
listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq)
m.logger.Debug("sdk request 'scm.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'scm.ListCertificates': %w", err)
}
if listCertificatesResp.Certificates != nil {
for _, certDetail := range *listCertificatesResp.Certificates {
exportCertificateReq := &hcscmmodel.ExportCertificateRequest{
CertificateId: certDetail.Id,
}
exportCertificateResp, err := m.sdkClient.ExportCertificate(exportCertificateReq)
m.logger.Debug("sdk request 'scm.ExportCertificate'", slog.Any("request", exportCertificateReq), slog.Any("response", exportCertificateResp))
if err != nil {
if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 {
continue
}
return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err)
}
var isSameCert bool
if *exportCertificateResp.Certificate == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.Id,
CertName: certDetail.Name,
}, nil
}
}
}
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
break
} else {
listCertificatesOffset += listCertificatesLimit
}
}
// 生成新证书名(需符合华为云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://support.huaweicloud.com/api-ccm/ImportCertificate.html
importCertificateReq := &hcscmmodel.ImportCertificateRequest{
Body: &hcscmmodel.ImportCertificateRequestBody{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
Name: certName,
Certificate: certPEM,
PrivateKey: privkeyPEM,
},
}
importCertificateResp, err := m.sdkClient.ImportCertificate(importCertificateReq)
m.logger.Debug("sdk request 'scm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'scm.ImportCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: *importCertificateResp.CertificateId,
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*hcscm.ScmClient, error) {
if region == "" {
region = "cn-north-4" // SCM 服务默认区域:华北四北京
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hcscmregion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hcscm.ScmClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hcscm.NewScmClient(hcClient)
return client, nil
}

View File

@@ -0,0 +1,232 @@
package huaweicloudwaf
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
hciam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3"
hciammodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
hciamregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
hcwaf "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1"
hcwafmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/model"
hcwafregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/region"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云企业项目 ID。
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
// 华为云区域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *hcwaf.WafClient
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 遍历查询已有证书,避免重复上传
// REF: https://support.huaweicloud.com/api-waf/ListCertificates.html
// REF: https://support.huaweicloud.com/api-waf/ShowCertificate.html
listCertificatesPage := int32(1)
listCertificatesPageSize := int32(100)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &hcwafmodel.ListCertificatesRequest{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
Page: xtypes.ToPtr(listCertificatesPage),
Pagesize: xtypes.ToPtr(listCertificatesPageSize),
}
listCertificatesResp, err := m.sdkClient.ListCertificates(listCertificatesReq)
m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'waf.ListCertificates': %w", err)
}
if listCertificatesResp.Items != nil {
for _, certItem := range *listCertificatesResp.Items {
showCertificateReq := &hcwafmodel.ShowCertificateRequest{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
CertificateId: certItem.Id,
}
showCertificateResp, err := m.sdkClient.ShowCertificate(showCertificateReq)
m.logger.Debug("sdk request 'waf.ShowCertificate'", slog.Any("request", showCertificateReq), slog.Any("response", showCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'waf.ShowCertificate': %w", err)
}
var isSameCert bool
if *showCertificateResp.Content == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(*showCertificateResp.Content)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certItem.Id,
CertName: certItem.Name,
}, nil
}
}
}
if listCertificatesResp.Items == nil || len(*listCertificatesResp.Items) < int(listCertificatesPageSize) {
break
} else {
listCertificatesPage++
}
}
// 生成新证书名(需符合华为云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建证书
// REF: https://support.huaweicloud.com/api-waf/CreateCertificate.html
createCertificateReq := &hcwafmodel.CreateCertificateRequest{
EnterpriseProjectId: xtypes.ToPtrOrZeroNil(m.config.EnterpriseProjectId),
Body: &hcwafmodel.CreateCertificateRequestBody{
Name: certName,
Content: certPEM,
Key: privkeyPEM,
},
}
createCertificateResp, err := m.sdkClient.CreateCertificate(createCertificateReq)
m.logger.Debug("sdk request 'waf.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'waf.CreateCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: *createCertificateResp.Id,
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*hcwaf.WafClient, error) {
projectId, err := getSdkProjectId(accessKeyId, secretAccessKey, region)
if err != nil {
return nil, err
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
WithProjectId(projectId).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hcwafregion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hcwaf.WafClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hcwaf.NewWafClient(hcClient)
return client, nil
}
func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
auth, err := global.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return "", err
}
hcRegion, err := hciamregion.SafeValueOf(region)
if err != nil {
return "", err
}
hcClient, err := hciam.IamClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return "", err
}
client := hciam.NewIamClient(hcClient)
request := &hciammodel.KeystoneListProjectsRequest{
Name: &region,
}
response, err := client.KeystoneListProjects(request)
if err != nil {
return "", err
} else if response.Projects == nil || len(*response.Projects) == 0 {
return "", errors.New("no project found")
}
return (*response.Projects)[0].Id, nil
}

View File

@@ -0,0 +1,159 @@
package jdcloudssl
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"strings"
"time"
jdcore "github.com/jdcloud-api/jdcloud-sdk-go/core"
jdsslapi "github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/apis"
jdsslclient "github.com/jdcloud-api/jdcloud-sdk-go/services/ssl/client"
"golang.org/x/exp/slices"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 京东云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 京东云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *jdsslclient.SslClient
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 格式化私钥内容,以便后续计算私钥摘要
privkeyPEM = strings.TrimSpace(privkeyPEM)
privkeyPEM = strings.ReplaceAll(privkeyPEM, "\r", "")
privkeyPEM = strings.ReplaceAll(privkeyPEM, "\n", "\r\n")
privkeyPEM = privkeyPEM + "\r\n"
// 遍历查看证书列表,避免重复上传
// REF: https://docs.jdcloud.com/cn/ssl-certificate/api/describecerts
describeCertsPageNumber := 1
describeCertsPageSize := 10
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
describeCertsReq := jdsslapi.NewDescribeCertsRequest()
describeCertsReq.SetDomainName(certX509.Subject.CommonName)
describeCertsReq.SetPageNumber(describeCertsPageNumber)
describeCertsReq.SetPageSize(describeCertsPageSize)
describeCertsResp, err := m.sdkClient.DescribeCerts(describeCertsReq)
m.logger.Debug("sdk request 'ssl.DescribeCerts'", slog.Any("request", describeCertsReq), slog.Any("response", describeCertsResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCerts': %w", err)
}
for _, certDetail := range describeCertsResp.Result.CertListDetails {
// 先对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CommonName) {
continue
}
// 再对比证书多域名
if !slices.Equal(certX509.DNSNames, certDetail.DnsNames) {
continue
}
// 再对比证书有效期
oldCertNotBefore, _ := time.Parse(time.RFC3339, certDetail.StartTime)
oldCertNotAfter, _ := time.Parse(time.RFC3339, certDetail.EndTime)
if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) {
continue
}
// 最后对比私钥摘要
newKeyDigest := sha256.Sum256([]byte(privkeyPEM))
newKeyDigestHex := hex.EncodeToString(newKeyDigest[:])
if !strings.EqualFold(newKeyDigestHex, certDetail.Digest) {
continue
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.CertId,
CertName: certDetail.CertName,
}, nil
}
if len(describeCertsResp.Result.CertListDetails) < int(describeCertsPageSize) {
break
} else {
describeCertsPageNumber++
}
}
// 生成新证书名(需符合京东云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传证书
// REF: https://docs.jdcloud.com/cn/ssl-certificate/api/uploadcert
uploadCertReq := jdsslapi.NewUploadCertRequest(certName, privkeyPEM, certPEM)
uploadCertResp, err := m.sdkClient.UploadCert(uploadCertReq)
m.logger.Debug("sdk request 'ssl.UploadCertificate'", slog.Any("request", uploadCertReq), slog.Any("response", uploadCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: uploadCertResp.Result.CertId,
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, accessKeySecret string) (*jdsslclient.SslClient, error) {
clientCredentials := jdcore.NewCredentials(accessKeyId, accessKeySecret)
client := jdsslclient.NewSslClient(clientCredentials)
client.SetLogger(jdcore.NewDefaultLogger(jdcore.LogWarn))
return client, nil
}

View File

@@ -0,0 +1,72 @@
package jdcloudssl_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/jdcloud-ssl"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_JDCLOUDSSL_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
}
/*
Shell command to run this test:
go test -v ./jdcloud_ssl_test.go -args \
--CERTIMATE_SSLMANAGER_JDCLOUDSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_JDCLOUDSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_JDCLOUDSSL_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_JDCLOUDSSL_ACCESSKEYSECRET="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("ACCESSKEYSECRET: %v", fAccessKeySecret),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,93 @@
package qiniusslcert
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/usual2970/certimate/pkg/core"
qiniusdk "github.com/usual2970/certimate/pkg/sdk3rd/qiniu"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 七牛云 AccessKey。
AccessKey string `json:"accessKey"`
// 七牛云 SecretKey。
SecretKey string `json:"secretKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *qiniusdk.CdnManager
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKey, config.SecretKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 生成新证书名(需符合七牛云命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://developer.qiniu.com/fusion/8593/interface-related-certificate
uploadSslCertResp, err := m.sdkClient.UploadSslCert(context.TODO(), certName, certX509.Subject.CommonName, certPEM, privkeyPEM)
m.logger.Debug("sdk request 'cdn.UploadSslCert'", slog.Any("response", uploadSslCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.UploadSslCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: uploadSslCertResp.CertID,
CertName: certName,
}, nil
}
func createSDKClient(accessKey, secretKey string) (*qiniusdk.CdnManager, error) {
if secretKey == "" {
return nil, errors.New("invalid qiniu access key")
}
if secretKey == "" {
return nil, errors.New("invalid qiniu secret key")
}
credential := auth.New(accessKey, secretKey)
client := qiniusdk.NewCdnManager(credential)
return client, nil
}

View File

@@ -0,0 +1,169 @@
package rainyunsslcenter
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"github.com/usual2970/certimate/pkg/core"
rainyunsdk "github.com/usual2970/certimate/pkg/sdk3rd/rainyun"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 雨云 API 密钥。
ApiKey string `json:"ApiKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *rainyunsdk.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.ApiKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 遍历证书列表,避免重复上传
if res, err := m.findCertIfExists(ctx, certPEM); err != nil {
return nil, err
} else if res != nil {
m.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 := m.sdkClient.SslCenterCreate(sslCenterCreateReq)
m.logger.Debug("sdk request 'sslcenter.Create'", slog.Any("request", sslCenterCreateReq), slog.Any("response", sslCenterCreateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Create': %w", err)
}
// 遍历证书列表,获取刚刚上传证书 ID
if res, err := m.findCertIfExists(ctx, certPEM); err != nil {
return nil, err
} else if res == nil {
return nil, errors.New("no ssl certificate found, may be upload failed")
} else {
return res, nil
}
}
func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.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 {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
sslCenterListReq := &rainyunsdk.SslCenterListRequest{
Filters: &rainyunsdk.SslCenterListFilters{
Domain: &certX509.Subject.CommonName,
},
Page: &sslCenterListPage,
PerPage: &sslCenterListPerPage,
}
sslCenterListResp, err := m.sdkClient.SslCenterList(sslCenterListReq)
m.logger.Debug("sdk request 'sslcenter.List'", slog.Any("request", sslCenterListReq), slog.Any("response", sslCenterListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.List': %w", err)
}
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 := m.sdkClient.SslCenterGet(sslItem.ID)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'sslcenter.Get': %w", err)
}
var isSameCert bool
if sslCenterGetResp.Data != nil {
if sslCenterGetResp.Data.Cert == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(sslCenterGetResp.Data.Cert)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
}
// 如果已存在相同证书,直接返回
if isSameCert {
return &core.SSLManageUploadResult{
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) {
return rainyunsdk.NewClient(apiKey)
}

View File

@@ -0,0 +1,67 @@
package rainyunsslcenter_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/rainyun-sslcenter"
)
var (
fInputCertPath string
fInputKeyPath string
fApiKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_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_SSLMANAGER_RAINYUNSSLCENTER_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_RAINYUNSSLCENTER_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_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"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
ApiKey: fApiKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,83 @@
package tencentcloudssl
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/pkg/core"
)
type SSLManagerProviderConfig struct {
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *tcssl.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.SecretId, config.SecretKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 上传新证书
// REF: https://cloud.tencent.com/document/product/400/41665
uploadCertificateReq := tcssl.NewUploadCertificateRequest()
uploadCertificateReq.CertificatePublicKey = common.StringPtr(certPEM)
uploadCertificateReq.CertificatePrivateKey = common.StringPtr(privkeyPEM)
uploadCertificateReq.Repeatable = common.BoolPtr(false)
uploadCertificateResp, err := m.sdkClient.UploadCertificate(uploadCertificateReq)
m.logger.Debug("sdk request 'ssl.UploadCertificate'", slog.Any("request", uploadCertificateReq), slog.Any("response", uploadCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err)
}
certId := *uploadCertificateResp.Response.CertificateId
return &core.SSLManageUploadResult{
CertId: certId,
CertName: "",
}, nil
}
func createSDKClient(secretId, secretKey string) (*tcssl.Client, error) {
credential := common.NewCredential(secretId, secretKey)
client, err := tcssl.NewClient(credential, "", profile.NewClientProfile())
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,243 @@
package ucloudussl
import (
"context"
"crypto/md5"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/ucloud/ucloud-sdk-go/ucloud"
ucloudauth "github.com/ucloud/ucloud-sdk-go/ucloud/auth"
"github.com/usual2970/certimate/pkg/core"
usslsdk "github.com/usual2970/certimate/pkg/sdk3rd/ucloud/ussl"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 优刻得 API 私钥。
PrivateKey string `json:"privateKey"`
// 优刻得 API 公钥。
PublicKey string `json:"publicKey"`
// 优刻得项目 ID。
ProjectId string `json:"projectId,omitempty"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *usslsdk.USSLClient
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.PrivateKey, config.PublicKey)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 生成新证书名(需符合优刻得命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 生成优刻得所需的证书参数
certPEMBase64 := base64.StdEncoding.EncodeToString([]byte(certPEM))
privkeyPEMBase64 := base64.StdEncoding.EncodeToString([]byte(privkeyPEM))
certMd5 := md5.Sum([]byte(certPEMBase64 + privkeyPEMBase64))
certMd5Hex := hex.EncodeToString(certMd5[:])
// 上传托管证书
// REF: https://docs.ucloud.cn/api/usslcertificate-api/upload_normal_certificate
uploadNormalCertificateReq := m.sdkClient.NewUploadNormalCertificateRequest()
uploadNormalCertificateReq.CertificateName = ucloud.String(certName)
uploadNormalCertificateReq.SslPublicKey = ucloud.String(certPEMBase64)
uploadNormalCertificateReq.SslPrivateKey = ucloud.String(privkeyPEMBase64)
uploadNormalCertificateReq.SslMD5 = ucloud.String(certMd5Hex)
if m.config.ProjectId != "" {
uploadNormalCertificateReq.ProjectId = ucloud.String(m.config.ProjectId)
}
uploadNormalCertificateResp, err := m.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq)
m.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 := m.findCertIfExists(ctx, certPEM); err != nil {
return nil, err
} else if res == nil {
return nil, errors.New("ucloud ssl: no certificate found")
} else {
m.logger.Info("ssl certificate already exists")
return res, nil
}
}
return nil, fmt.Errorf("failed to execute sdk request 'ussl.UploadNormalCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", uploadNormalCertificateResp.CertificateID),
CertName: certName,
ExtendedData: map[string]any{
"resourceId": uploadNormalCertificateResp.LongResourceID,
},
}, nil
}
func (m *SSLManagerProvider) findCertIfExists(ctx context.Context, certPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 遍历获取用户证书列表
// REF: https://docs.ucloud.cn/api/usslcertificate-api/get_certificate_list
// REF: https://docs.ucloud.cn/api/usslcertificate-api/download_certificate
getCertificateListPage := int(1)
getCertificateListLimit := int(1000)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
getCertificateListReq := m.sdkClient.NewGetCertificateListRequest()
getCertificateListReq.Mode = ucloud.String("trust")
getCertificateListReq.Domain = ucloud.String(certX509.Subject.CommonName)
getCertificateListReq.Sort = ucloud.String("2")
getCertificateListReq.Page = ucloud.Int(getCertificateListPage)
getCertificateListReq.PageSize = ucloud.Int(getCertificateListLimit)
if m.config.ProjectId != "" {
getCertificateListReq.ProjectId = ucloud.String(m.config.ProjectId)
}
getCertificateListResp, err := m.sdkClient.GetCertificateList(getCertificateListReq)
m.logger.Debug("sdk request 'ussl.GetCertificateList'", slog.Any("request", getCertificateListReq), slog.Any("response", getCertificateListResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateList': %w", err)
}
if getCertificateListResp.CertificateList != nil {
for _, certInfo := range getCertificateListResp.CertificateList {
// 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书
// 先分别对比证书的多域名、品牌、有效期,再对比签名算法
if len(certX509.DNSNames) == 0 || certInfo.Domains != strings.Join(certX509.DNSNames, ",") {
continue
}
if len(certX509.Issuer.Organization) == 0 || certInfo.Brand != certX509.Issuer.Organization[0] {
continue
}
if int64(certInfo.NotBefore) != certX509.NotBefore.UnixMilli() || int64(certInfo.NotAfter) != certX509.NotAfter.UnixMilli() {
continue
}
getCertificateDetailInfoReq := m.sdkClient.NewGetCertificateDetailInfoRequest()
getCertificateDetailInfoReq.CertificateID = ucloud.Int(certInfo.CertificateID)
if m.config.ProjectId != "" {
getCertificateDetailInfoReq.ProjectId = ucloud.String(m.config.ProjectId)
}
getCertificateDetailInfoResp, err := m.sdkClient.GetCertificateDetailInfo(getCertificateDetailInfoReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ussl.GetCertificateDetailInfo': %w", err)
}
switch certX509.SignatureAlgorithm {
case x509.SHA256WithRSA:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSA") {
continue
}
case x509.SHA384WithRSA:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSA") {
continue
}
case x509.SHA512WithRSA:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSA") {
continue
}
case x509.SHA256WithRSAPSS:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA256-RSAPSS") {
continue
}
case x509.SHA384WithRSAPSS:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA384-RSAPSS") {
continue
}
case x509.SHA512WithRSAPSS:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "SHA512-RSAPSS") {
continue
}
case x509.ECDSAWithSHA256:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA256") {
continue
}
case x509.ECDSAWithSHA384:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA384") {
continue
}
case x509.ECDSAWithSHA512:
if !strings.EqualFold(getCertificateDetailInfoResp.CertificateInfo.Algorithm, "ECDSA-SHA512") {
continue
}
default:
// 未知签名算法,跳过
continue
}
return &core.SSLManageUploadResult{
CertId: fmt.Sprintf("%d", certInfo.CertificateID),
CertName: certInfo.Name,
ExtendedData: map[string]any{
"resourceId": certInfo.CertificateSN,
},
}, nil
}
}
if getCertificateListResp.CertificateList == nil || len(getCertificateListResp.CertificateList) < int(getCertificateListLimit) {
break
} else {
getCertificateListPage++
}
}
return nil, nil
}
func createSDKClient(privateKey, publicKey string) (*usslsdk.USSLClient, error) {
cfg := ucloud.NewConfig()
credential := ucloudauth.NewCredential()
credential.PrivateKey = privateKey
credential.PublicKey = publicKey
client := usslsdk.NewClient(&cfg, &credential)
return client, nil
}

View File

@@ -0,0 +1,72 @@
package ucloudussl_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/ucloud-ussl"
)
var (
fInputCertPath string
fInputKeyPath string
fPrivateKey string
fPublicKey string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_UCLOUDUSSL_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fPrivateKey, argsPrefix+"PRIVATEKEY", "", "")
flag.StringVar(&fPublicKey, argsPrefix+"PUBLICKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./ucloud_ussl_test.go -args \
--CERTIMATE_SSLMANAGER_UCLOUDUSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_UCLOUDUSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_UCLOUDUSSL_PRIVATEKEY="your-private-key" \
--CERTIMATE_SSLMANAGER_UCLOUDUSSL_PUBLICKEY="your-public-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("PRIVATEKEY: %v", fPrivateKey),
fmt.Sprintf("PUBLICKEY: %v", fPublicKey),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
PrivateKey: fPrivateKey,
PublicKey: fPublicKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,72 @@
package upyunssl
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/usual2970/certimate/pkg/core"
upyunsdk "github.com/usual2970/certimate/pkg/sdk3rd/upyun/console"
)
type SSLManagerProviderConfig struct {
// 又拍云账号用户名。
Username string `json:"username"`
// 又拍云账号密码。
Password string `json:"password"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *upyunsdk.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.Username, config.Password)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 上传证书
uploadHttpsCertificateReq := &upyunsdk.UploadHttpsCertificateRequest{
Certificate: certPEM,
PrivateKey: privkeyPEM,
}
uploadHttpsCertificateResp, err := m.sdkClient.UploadHttpsCertificate(uploadHttpsCertificateReq)
m.logger.Debug("sdk request 'console.UploadHttpsCertificate'", slog.Any("response", uploadHttpsCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'console.UploadHttpsCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: uploadHttpsCertificateResp.Data.Result.CertificateId,
}, nil
}
func createSDKClient(username, password string) (*upyunsdk.Client, error) {
return upyunsdk.NewClient(username, password)
}

View File

@@ -0,0 +1,72 @@
package upyunssl_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/upyun-ssl"
)
var (
fInputCertPath string
fInputKeyPath string
fUsername string
fPassword string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_UPYUNSSL_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
}
/*
Shell command to run this test:
go test -v ./upyun_ssl_test.go -args \
--CERTIMATE_SSLMANAGER_UPYUNSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_UPYUNSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_UPYUNSSL_USERNAME="your-username" \
--CERTIMATE_SSLMANAGER_UPYUNSSL_PASSWORD="your-password"
*/
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("USERNAME: %v", fUsername),
fmt.Sprintf("PASSWORD: %v", fPassword),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
Username: fUsername,
Password: fPassword,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,137 @@
package volcenginecdn
import (
"context"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"strings"
"time"
vecdn "github.com/volcengine/volc-sdk-golang/service/cdn"
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *vecdn.CDN
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client := vecdn.NewInstance()
client.Client.SetAccessKey(config.AccessKeyId)
client.Client.SetSecretKey(config.AccessKeySecret)
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://www.volcengine.com/docs/6454/125709
listCertInfoPageNum := int64(1)
listCertInfoPageSize := int64(100)
listCertInfoTotal := 0
listCertInfoReq := &vecdn.ListCertInfoRequest{
PageNum: ve.Int64(listCertInfoPageNum),
PageSize: ve.Int64(listCertInfoPageSize),
Source: "volc_cert_center",
}
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertInfoResp, err := m.sdkClient.ListCertInfo(listCertInfoReq)
m.logger.Debug("sdk request 'cdn.ListCertInfo'", slog.Any("request", listCertInfoReq), slog.Any("response", listCertInfoResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCertInfo': %w", err)
}
if listCertInfoResp.Result.CertInfo != nil {
for _, certDetail := range listCertInfoResp.Result.CertInfo {
fingerprintSha1 := sha1.Sum(certX509.Raw)
fingerprintSha256 := sha256.Sum256(certX509.Raw)
isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) &&
strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256)
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.CertId,
CertName: certDetail.Desc,
}, nil
}
}
}
listCertInfoLen := len(listCertInfoResp.Result.CertInfo)
if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen {
break
} else {
listCertInfoPageNum++
listCertInfoTotal += listCertInfoLen
}
}
// 生成新证书名(需符合火山引擎命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://www.volcengine.com/docs/6454/1245763
addCertificateReq := &vecdn.AddCertificateRequest{
Certificate: certPEM,
PrivateKey: privkeyPEM,
Source: ve.String("volc_cert_center"),
Desc: ve.String(certName),
}
addCertificateResp, err := m.sdkClient.AddCertificate(addCertificateReq)
m.logger.Debug("sdk request 'cdn.AddCertificate'", slog.Any("request", addCertificateResp), slog.Any("response", addCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cdn.AddCertificate': %w", err)
}
return &core.SSLManageUploadResult{
CertId: addCertificateResp.Result.CertId,
CertName: certName,
}, nil
}

View File

@@ -0,0 +1,105 @@
package volcenginecertcenter
import (
"context"
"errors"
"fmt"
"log/slog"
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
vesession "github.com/volcengine/volcengine-go-sdk/volcengine/session"
"github.com/usual2970/certimate/pkg/core"
veccsdk "github.com/usual2970/certimate/pkg/sdk3rd/volcengine/certcenter"
)
type SSLManagerProviderConfig struct {
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
// 火山引擎地域。
Region string `json:"region"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *veccsdk.CertCenter
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 上传证书
// REF: https://www.volcengine.com/docs/6638/1365580
importCertificateReq := &veccsdk.ImportCertificateInput{
CertificateInfo: &veccsdk.ImportCertificateInputCertificateInfo{
CertificateChain: ve.String(certPEM),
PrivateKey: ve.String(privkeyPEM),
},
Repeatable: ve.Bool(false),
}
importCertificateResp, err := m.sdkClient.ImportCertificate(importCertificateReq)
m.logger.Debug("sdk request 'certcenter.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'certcenter.ImportCertificate': %w", err)
}
var sslId string
if importCertificateResp.InstanceId != nil && *importCertificateResp.InstanceId != "" {
sslId = *importCertificateResp.InstanceId
}
if importCertificateResp.RepeatId != nil && *importCertificateResp.RepeatId != "" {
sslId = *importCertificateResp.RepeatId
}
if sslId == "" {
return nil, errors.New("received empty certificate id, both `InstanceId` and `RepeatId` are empty")
}
return &core.SSLManageUploadResult{
CertId: sslId,
}, nil
}
func createSDKClient(accessKeyId, accessKeySecret, region string) (*veccsdk.CertCenter, error) {
if region == "" {
region = "cn-beijing" // 证书中心默认区域:北京
}
config := ve.NewConfig().WithRegion(region).WithAkSk(accessKeyId, accessKeySecret)
session, err := vesession.NewSession(config)
if err != nil {
return nil, err
}
client := veccsdk.New(session)
return client, nil
}

View File

@@ -0,0 +1,72 @@
package volcenginecertcenter_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/volcengine-certcenter"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_VOLCENGINECERTCENTER_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
}
/*
Shell command to run this test:
go test -v ./volcengine_certcenter_test.go -args \
--CERTIMATE_SSLMANAGER_VOLCENGINECERTCENTER_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_VOLCENGINECERTCENTER_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_VOLCENGINECERTCENTER_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_VOLCENGINECERTCENTER_ACCESSKEYSECRET="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("ACCESSKEYSECRET: %v", fAccessKeySecret),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}

View File

@@ -0,0 +1,133 @@
package volcenginelive
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"time"
velive "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
ve "github.com/volcengine/volcengine-go-sdk/volcengine"
"github.com/usual2970/certimate/pkg/core"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
)
type SSLManagerProviderConfig struct {
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *velive.Live
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client := velive.NewInstance()
client.SetAccessKey(config.AccessKeyId)
client.SetSecretKey(config.AccessKeySecret)
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E8%AF%A2%E8%AF%81%E4%B9%A6%E5%88%97%E8%A1%A8
listCertReq := &velive.ListCertV2Body{}
listCertResp, err := m.sdkClient.ListCertV2(ctx, listCertReq)
m.logger.Debug("sdk request 'live.ListCertV2'", slog.Any("request", listCertReq), slog.Any("response", listCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'live.ListCertV2': %w", err)
}
if listCertResp.Result.CertList != nil {
for _, certDetail := range listCertResp.Result.CertList {
// 查询证书详细信息
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%9F%A5%E7%9C%8B%E8%AF%81%E4%B9%A6%E8%AF%A6%E6%83%85
describeCertDetailSecretReq := &velive.DescribeCertDetailSecretV2Body{
ChainID: ve.String(certDetail.ChainID),
}
describeCertDetailSecretResp, err := m.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq)
m.logger.Debug("sdk request 'live.DescribeCertDetailSecretV2'", slog.Any("request", describeCertDetailSecretReq), slog.Any("response", describeCertDetailSecretResp))
if err != nil {
continue
}
var isSameCert bool
certificate := strings.Join(describeCertDetailSecretResp.Result.SSL.Chain, "\n\n")
if certificate == certPEM {
isSameCert = true
} else {
oldCertX509, err := xcert.ParseCertificateFromPEM(certificate)
if err != nil {
continue
}
isSameCert = xcert.EqualCertificate(certX509, oldCertX509)
}
// 如果已存在相同证书,直接返回
if isSameCert {
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certDetail.ChainID,
CertName: certDetail.CertName,
}, nil
}
}
}
// 生成新证书名(需符合火山引擎命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6
createCertReq := &velive.CreateCertBody{
CertName: ve.String(certName),
UseWay: "https",
ProjectName: ve.String("default"),
Rsa: velive.CreateCertBodyRsa{
Prikey: privkeyPEM,
Pubkey: certPEM,
},
}
createCertResp, err := m.sdkClient.CreateCert(ctx, createCertReq)
m.logger.Debug("sdk request 'live.CreateCert'", slog.Any("request", createCertReq), slog.Any("response", createCertResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'live.CreateCert': %w", err)
}
return &core.SSLManageUploadResult{
CertId: *createCertResp.Result.ChainID,
CertName: certName,
}, nil
}

View File

@@ -0,0 +1,130 @@
package wangsucertificate
import (
"context"
"errors"
"fmt"
"log/slog"
"regexp"
"strings"
"time"
"github.com/usual2970/certimate/pkg/core"
wangsusdk "github.com/usual2970/certimate/pkg/sdk3rd/wangsu/certificate"
xcert "github.com/usual2970/certimate/pkg/utils/cert"
xtypes "github.com/usual2970/certimate/pkg/utils/types"
)
type SSLManagerProviderConfig struct {
// 网宿云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 网宿云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}
type SSLManagerProvider struct {
config *SSLManagerProviderConfig
logger *slog.Logger
sdkClient *wangsusdk.Client
}
var _ core.SSLManager = (*SSLManagerProvider)(nil)
func NewSSLManagerProvider(config *SSLManagerProviderConfig) (*SSLManagerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl manager provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
return &SSLManagerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (m *SSLManagerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
m.logger = slog.New(slog.DiscardHandler)
} else {
m.logger = logger
}
}
func (m *SSLManagerProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLManageUploadResult, error) {
// 解析证书内容
certX509, err := xcert.ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://www.wangsu.com/document/api-doc/22675?productCode=certificatemanagement
listCertificatesResp, err := m.sdkClient.ListCertificates()
m.logger.Debug("sdk request 'certificatemanagement.ListCertificates'", slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.ListCertificates': %w", err)
}
if listCertificatesResp.Certificates != nil {
for _, certificate := range listCertificatesResp.Certificates {
// 对比证书序列号
if !strings.EqualFold(certX509.SerialNumber.Text(16), certificate.Serial) {
continue
}
// 再对比证书有效期
cstzone := time.FixedZone("CST", 8*60*60)
oldCertNotBefore, _ := time.ParseInLocation(time.DateTime, certificate.ValidityFrom, cstzone)
oldCertNotAfter, _ := time.ParseInLocation(time.DateTime, certificate.ValidityTo, cstzone)
if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) {
continue
}
// 如果以上信息都一致,则视为已存在相同证书,直接返回
m.logger.Info("ssl certificate already exists")
return &core.SSLManageUploadResult{
CertId: certificate.CertificateId,
CertName: certificate.Name,
}, nil
}
}
// 生成新证书名(需符合网宿云命名规则)
certName := fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 新增证书
// REF: https://www.wangsu.com/document/api-doc/25199?productCode=certificatemanagement
createCertificateReq := &wangsusdk.CreateCertificateRequest{
Name: xtypes.ToPtr(certName),
Certificate: xtypes.ToPtr(certPEM),
PrivateKey: xtypes.ToPtr(privkeyPEM),
Comment: xtypes.ToPtr("upload from certimate"),
}
createCertificateResp, err := m.sdkClient.CreateCertificate(createCertificateReq)
m.logger.Debug("sdk request 'certificatemanagement.CreateCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'certificatemanagement.CreateCertificate': %w", err)
}
// 网宿云证书 URL 中包含证书 ID
// 格式:
// https://open.chinanetcenter.com/api/certificate/100001
wangsuCertIdMatches := regexp.MustCompile(`/certificate/([0-9]+)`).FindStringSubmatch(createCertificateResp.CertificateLocation)
if len(wangsuCertIdMatches) == 0 {
return nil, fmt.Errorf("received empty certificate id")
}
return &core.SSLManageUploadResult{
CertId: wangsuCertIdMatches[1],
CertName: certName,
}, nil
}
func createSDKClient(accessKeyId, accessKeySecret string) (*wangsusdk.Client, error) {
return wangsusdk.NewClient(accessKeyId, accessKeySecret)
}

View File

@@ -0,0 +1,72 @@
package wangsucertificate_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/pkg/core/ssl-manager/providers/wangsu-certificate"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fAccessKeySecret string
)
func init() {
argsPrefix := "CERTIMATE_SSLMANAGER_WANGSUCERTIFICATE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
}
/*
Shell command to run this test:
go test -v ./wangsu_certificate_test.go -args \
--CERTIMATE_SSLMANAGER_WANGSUCERTIFICATE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLMANAGER_WANGSUCERTIFICATE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLMANAGER_WANGSUCERTIFICATE_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLMANAGER_WANGSUCERTIFICATE_ACCESSKEYSECRET="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("ACCESSKEYSECRET: %v", fAccessKeySecret),
}, "\n"))
sslmanager, err := provider.NewSSLManagerProvider(&provider.SSLManagerProviderConfig{
AccessKeyId: fAccessKeyId,
AccessKeySecret: fAccessKeySecret,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := sslmanager.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))
})
}