chore: move '/internal/pkg' to '/pkg'
This commit is contained in:
232
pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go
Normal file
232
pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl.go
Normal 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")
|
||||
}
|
||||
77
pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl_test.go
Normal file
77
pkg/core/ssl-manager/providers/1panel-ssl/1panel_ssl_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
205
pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go
Normal file
205
pkg/core/ssl-manager/providers/aliyun-cas/aliyun_cas.go
Normal 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
|
||||
}
|
||||
160
pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go
Normal file
160
pkg/core/ssl-manager/providers/aliyun-slb/aliyun_slb.go
Normal 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
|
||||
}
|
||||
172
pkg/core/ssl-manager/providers/aws-acm/aws_acm.go
Normal file
172
pkg/core/ssl-manager/providers/aws-acm/aws_acm.go
Normal 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
|
||||
}
|
||||
185
pkg/core/ssl-manager/providers/aws-iam/aws_iam.go
Normal file
185
pkg/core/ssl-manager/providers/aws-iam/aws_iam.go
Normal 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
|
||||
}
|
||||
204
pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go
Normal file
204
pkg/core/ssl-manager/providers/azure-keyvault/azure_keyvault.go
Normal 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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
136
pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go
Normal file
136
pkg/core/ssl-manager/providers/byteplus-cdn/byteplus_cdn.go
Normal 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
|
||||
}
|
||||
171
pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go
Normal file
171
pkg/core/ssl-manager/providers/ctcccloud-ao/ctcccloud_ao.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
171
pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go
Normal file
171
pkg/core/ssl-manager/providers/ctcccloud-cdn/ctcccloud_cdn.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
185
pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go
Normal file
185
pkg/core/ssl-manager/providers/ctcccloud-cms/ctcccloud_cms.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
133
pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go
Normal file
133
pkg/core/ssl-manager/providers/ctcccloud-elb/ctcccloud_elb.go
Normal 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()
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
171
pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go
Normal file
171
pkg/core/ssl-manager/providers/ctcccloud-icdn/ctcccloud_icdn.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
171
pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go
Normal file
171
pkg/core/ssl-manager/providers/ctcccloud-lvdn/ctcccloud_lvdn.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
79
pkg/core/ssl-manager/providers/dogecloud/dogecloud.go
Normal file
79
pkg/core/ssl-manager/providers/dogecloud/dogecloud.go
Normal 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)
|
||||
}
|
||||
88
pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go
Normal file
88
pkg/core/ssl-manager/providers/gcore-cdn/gcore_cdn.go
Normal 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
|
||||
}
|
||||
@@ -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: ®ion,
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: ®ion,
|
||||
}
|
||||
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
|
||||
}
|
||||
159
pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go
Normal file
159
pkg/core/ssl-manager/providers/jdcloud-ssl/jdcloud_ssl.go
Normal 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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
243
pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go
Normal file
243
pkg/core/ssl-manager/providers/ucloud-ussl/ucloud_ussl.go
Normal 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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
72
pkg/core/ssl-manager/providers/upyun-ssl/upyun_ssl.go
Normal file
72
pkg/core/ssl-manager/providers/upyun-ssl/upyun_ssl.go
Normal 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)
|
||||
}
|
||||
72
pkg/core/ssl-manager/providers/upyun-ssl/upyun_ssl_test.go
Normal file
72
pkg/core/ssl-manager/providers/upyun-ssl/upyun_ssl_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
137
pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go
Normal file
137
pkg/core/ssl-manager/providers/volcengine-cdn/volcengine_cdn.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user