Merge branch 'main' of https://github.com/fudiwei/certimate
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package bunny
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/providers/dns/bunny"
|
||||
)
|
||||
|
||||
type ChallengeProviderConfig struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int32 `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
providerConfig := bunny.NewDefaultConfig()
|
||||
providerConfig.APIKey = config.ApiKey
|
||||
if config.DnsPropagationTimeout != 0 {
|
||||
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||
}
|
||||
if config.DnsTTL != 0 {
|
||||
providerConfig.TTL = int(config.DnsTTL)
|
||||
}
|
||||
|
||||
provider, err := bunny.NewDNSProviderConfig(providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -23,8 +24,14 @@ type DeployerConfig struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
// 是否允许不安全的连接。
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
// 部署资源类型。
|
||||
ResourceType ResourceType `json:"resourceType"`
|
||||
// 网站 ID。
|
||||
WebsiteId int64 `json:"websiteId"`
|
||||
// 部署资源类型为 [RESOURCE_TYPE_WEBSITE] 时必填。
|
||||
WebsiteId int64 `json:"websiteId,omitempty"`
|
||||
// 证书 ID。
|
||||
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
|
||||
CertificateId int64 `json:"certificateId,omitempty"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
@@ -73,6 +80,30 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
// 根据部署资源类型决定部署方式
|
||||
switch d.config.ResourceType {
|
||||
case RESOURCE_TYPE_WEBSITE:
|
||||
if err := d.deployToWebsite(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case RESOURCE_TYPE_CERTIFICATE:
|
||||
if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) deployToWebsite(ctx context.Context, certPem string, privkeyPem string) error {
|
||||
if d.config.WebsiteId == 0 {
|
||||
return errors.New("config `websiteId` is required")
|
||||
}
|
||||
|
||||
// 获取网站 HTTPS 配置
|
||||
getHttpsConfReq := &opsdk.GetHttpsConfRequest{
|
||||
WebsiteID: d.config.WebsiteId,
|
||||
@@ -80,13 +111,13 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq)
|
||||
d.logger.Debug("sdk request '1panel.GetHttpsConf'", slog.Any("request", getHttpsConfReq), slog.Any("response", getHttpsConfResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'")
|
||||
return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'")
|
||||
}
|
||||
|
||||
// 上传证书到面板
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||
return xerrors.Wrap(err, "failed to upload certificate file")
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
@@ -106,10 +137,42 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq)
|
||||
d.logger.Debug("sdk request '1panel.UpdateHttpsConf'", slog.Any("request", updateHttpsConfReq), slog.Any("response", updateHttpsConfResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'")
|
||||
return xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'")
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error {
|
||||
if d.config.CertificateId == 0 {
|
||||
return errors.New("config `certificateId` is required")
|
||||
}
|
||||
|
||||
// 获取证书详情
|
||||
getWebsiteSSLReq := &opsdk.GetWebsiteSSLRequest{
|
||||
SSLID: d.config.CertificateId,
|
||||
}
|
||||
getWebsiteSSLResp, err := d.sdkClient.GetWebsiteSSL(getWebsiteSSLReq)
|
||||
d.logger.Debug("sdk request '1panel.GetWebsiteSSL'", slog.Any("request", getWebsiteSSLReq), slog.Any("response", getWebsiteSSLResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request '1panel.GetWebsiteSSL'")
|
||||
}
|
||||
|
||||
// 更新证书
|
||||
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
|
||||
Type: "paste",
|
||||
SSLID: d.config.CertificateId,
|
||||
Description: getWebsiteSSLResp.Data.Description,
|
||||
Certificate: certPem,
|
||||
PrivateKey: privkeyPem,
|
||||
}
|
||||
uploadWebsiteSSLResp, err := d.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
|
||||
d.logger.Debug("sdk request '1panel.UploadWebsiteSSL'", slog.Any("request", uploadWebsiteSSLReq), slog.Any("response", uploadWebsiteSSLResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) {
|
||||
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_"
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_1PANELSITE_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
@@ -32,12 +32,12 @@ func init() {
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./1panel_console_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \
|
||||
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" \
|
||||
--CERTIMATE_DEPLOYER_1PANELCONSOLE_WEBSITEID="your-website-id"
|
||||
go test -v ./1panel_site_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_1PANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_1PANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_1PANELSITE_APIURL="http://127.0.0.1:20410" \
|
||||
--CERTIMATE_DEPLOYER_1PANELSITE_APIKEY="your-api-key" \
|
||||
--CERTIMATE_DEPLOYER_1PANELSITE_WEBSITEID="your-website-id"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
@@ -55,8 +55,9 @@ func TestDeploy(t *testing.T) {
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
ApiUrl: fApiUrl,
|
||||
ApiKey: fApiKey,
|
||||
WebsiteId: fWebsiteId,
|
||||
AllowInsecureConnections: true,
|
||||
ResourceType: provider.RESOURCE_TYPE_WEBSITE,
|
||||
WebsiteId: fWebsiteId,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
|
||||
10
internal/pkg/core/deployer/providers/1panel-site/consts.go
Normal file
10
internal/pkg/core/deployer/providers/1panel-site/consts.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package onepanelsite
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
// 资源类型:替换指定网站的证书。
|
||||
RESOURCE_TYPE_WEBSITE = ResourceType("website")
|
||||
// 资源类型:替换指定证书。
|
||||
RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
|
||||
)
|
||||
@@ -0,0 +1,269 @@
|
||||
package aliyunapigw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
aliapig "github.com/alibabacloud-go/apig-20240327/v3/client"
|
||||
alicloudapi "github.com/alibabacloud-go/cloudapi-20160714/v5/client"
|
||||
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// 阿里云 AccessKeyId。
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 阿里云 AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
// 阿里云地域。
|
||||
Region string `json:"region"`
|
||||
// 服务类型。
|
||||
ServiceType ServiceType `json:"serviceType"`
|
||||
// API 网关 ID。
|
||||
// 服务类型为 [SERVICE_TYPE_CLOUDNATIVE] 时必填。
|
||||
GatewayId string `json:"gatewayId,omitempty"`
|
||||
// API 分组 ID。
|
||||
// 服务类型为 [SERVICE_TYPE_TRADITIONAL] 时必填。
|
||||
GroupId string `json:"groupId,omitempty"`
|
||||
// 自定义域名(支持泛域名)。
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClients *wSdkClients
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
type wSdkClients struct {
|
||||
CloudNativeAPIGateway *aliapig.Client
|
||||
TraditionalAPIGateway *alicloudapi.Client
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
clients, err := createSdkClients(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||
}
|
||||
|
||||
uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClients: clients,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
switch d.config.ServiceType {
|
||||
case SERVICE_TYPE_TRADITIONAL:
|
||||
if err := d.deployToTraditional(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case SERVICE_TYPE_CLOUDNATIVE:
|
||||
if err := d.deployToCloudNative(ctx, certPem, privkeyPem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, xerrors.Errorf("unsupported service type: %s", string(d.config.ServiceType))
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) deployToTraditional(ctx context.Context, certPem string, privkeyPem string) error {
|
||||
if d.config.GroupId == "" {
|
||||
return errors.New("config `groupId` is required")
|
||||
}
|
||||
if d.config.Domain == "" {
|
||||
return errors.New("config `domain` is required")
|
||||
}
|
||||
|
||||
// 为自定义域名添加 SSL 证书
|
||||
// REF: https://help.aliyun.com/zh/api-gateway/traditional-api-gateway/developer-reference/api-cloudapi-2016-07-14-setdomaincertificate
|
||||
setDomainCertificateReq := &alicloudapi.SetDomainCertificateRequest{
|
||||
GroupId: tea.String(d.config.GroupId),
|
||||
DomainName: tea.String(d.config.Domain),
|
||||
CertificateName: tea.String(fmt.Sprintf("certimate_%d", time.Now().UnixMilli())),
|
||||
CertificateBody: tea.String(certPem),
|
||||
CertificatePrivateKey: tea.String(privkeyPem),
|
||||
}
|
||||
setDomainCertificateResp, err := d.sdkClients.TraditionalAPIGateway.SetDomainCertificate(setDomainCertificateReq)
|
||||
d.logger.Debug("sdk request 'apigateway.SetDomainCertificate'", slog.Any("request", setDomainCertificateReq), slog.Any("response", setDomainCertificateResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'apigateway.SetDomainCertificate'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) deployToCloudNative(ctx context.Context, certPem string, privkeyPem string) error {
|
||||
if d.config.GatewayId == "" {
|
||||
return errors.New("config `gatewayId` is required")
|
||||
}
|
||||
if d.config.Domain == "" {
|
||||
return errors.New("config `domain` is required")
|
||||
}
|
||||
|
||||
// 遍历查询域名列表,获取域名 ID
|
||||
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-listdomains
|
||||
var domainId string
|
||||
listDomainsPageNumber := int32(1)
|
||||
listDomainsPageSize := int32(10)
|
||||
for {
|
||||
listDomainsReq := &aliapig.ListDomainsRequest{
|
||||
GatewayId: tea.String(d.config.GatewayId),
|
||||
NameLike: tea.String(d.config.Domain),
|
||||
PageNumber: tea.Int32(listDomainsPageNumber),
|
||||
PageSize: tea.Int32(listDomainsPageSize),
|
||||
}
|
||||
listDomainsResp, err := d.sdkClients.CloudNativeAPIGateway.ListDomains(listDomainsReq)
|
||||
d.logger.Debug("sdk request 'apig.ListDomains'", slog.Any("request", listDomainsReq), slog.Any("response", listDomainsResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'apig.ListDomains'")
|
||||
}
|
||||
|
||||
if listDomainsResp.Body.Data.Items != nil {
|
||||
for _, domainInfo := range listDomainsResp.Body.Data.Items {
|
||||
if strings.EqualFold(tea.StringValue(domainInfo.Name), d.config.Domain) {
|
||||
domainId = tea.StringValue(domainInfo.DomainId)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if domainId != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if listDomainsResp.Body.Data.Items == nil || len(listDomainsResp.Body.Data.Items) < int(listDomainsPageSize) {
|
||||
break
|
||||
} else {
|
||||
listDomainsPageNumber++
|
||||
}
|
||||
}
|
||||
if domainId == "" {
|
||||
return errors.New("domain not found")
|
||||
}
|
||||
|
||||
// 查询域名
|
||||
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-getdomain
|
||||
getDomainReq := &aliapig.GetDomainRequest{}
|
||||
getDomainResp, err := d.sdkClients.CloudNativeAPIGateway.GetDomain(tea.String(domainId), getDomainReq)
|
||||
d.logger.Debug("sdk request 'apig.GetDomain'", slog.Any("domainId", domainId), slog.Any("request", getDomainReq), slog.Any("response", getDomainResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'apig.GetDomain'")
|
||||
}
|
||||
|
||||
// 上传证书到 CAS
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to upload certificate file")
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
|
||||
// 更新域名
|
||||
// REF: https://help.aliyun.com/zh/api-gateway/cloud-native-api-gateway/developer-reference/api-apig-2024-03-27-updatedomain
|
||||
updateDomainReq := &aliapig.UpdateDomainRequest{
|
||||
Protocol: tea.String("HTTPS"),
|
||||
ForceHttps: getDomainResp.Body.Data.ForceHttps,
|
||||
MTLSEnabled: getDomainResp.Body.Data.MTLSEnabled,
|
||||
Http2Option: getDomainResp.Body.Data.Http2Option,
|
||||
TlsMin: getDomainResp.Body.Data.TlsMin,
|
||||
TlsMax: getDomainResp.Body.Data.TlsMax,
|
||||
TlsCipherSuitesConfig: getDomainResp.Body.Data.TlsCipherSuitesConfig,
|
||||
CertIdentifier: tea.String(upres.ExtendedData["certIdentifier"].(string)),
|
||||
}
|
||||
updateDomainResp, err := d.sdkClients.CloudNativeAPIGateway.UpdateDomain(tea.String(domainId), updateDomainReq)
|
||||
d.logger.Debug("sdk request 'apig.UpdateDomain'", slog.Any("domainId", domainId), slog.Any("request", updateDomainReq), slog.Any("response", updateDomainResp))
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to execute sdk request 'apig.UpdateDomain'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) {
|
||||
// 接入点一览 https://api.aliyun.com/product/APIG
|
||||
cloudNativeAPIGEndpoint := fmt.Sprintf("apig.%s.aliyuncs.com", region)
|
||||
cloudNativeAPIGConfig := &aliopen.Config{
|
||||
AccessKeyId: tea.String(accessKeyId),
|
||||
AccessKeySecret: tea.String(accessKeySecret),
|
||||
Endpoint: tea.String(cloudNativeAPIGEndpoint),
|
||||
}
|
||||
cloudNativeAPIGClient, err := aliapig.NewClient(cloudNativeAPIGConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 接入点一览 https://api.aliyun.com/product/CloudAPI
|
||||
traditionalAPIGEndpoint := fmt.Sprintf("apigateway.%s.aliyuncs.com", region)
|
||||
traditionalAPIGConfig := &aliopen.Config{
|
||||
AccessKeyId: tea.String(accessKeyId),
|
||||
AccessKeySecret: tea.String(accessKeySecret),
|
||||
Endpoint: tea.String(traditionalAPIGEndpoint),
|
||||
}
|
||||
traditionalAPIGClient, err := alicloudapi.NewClient(traditionalAPIGConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wSdkClients{
|
||||
CloudNativeAPIGateway: cloudNativeAPIGClient,
|
||||
TraditionalAPIGateway: traditionalAPIGClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
|
||||
casRegion := region
|
||||
if casRegion != "" {
|
||||
// 阿里云 CAS 服务接入点是独立于 APIGateway 服务的
|
||||
// 国内版固定接入点:华东一杭州
|
||||
// 国际版固定接入点:亚太东南一新加坡
|
||||
if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") {
|
||||
casRegion = "ap-southeast-1"
|
||||
} else {
|
||||
casRegion = "cn-hangzhou"
|
||||
}
|
||||
}
|
||||
|
||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
Region: casRegion,
|
||||
})
|
||||
return uploader, err
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package aliyunapigw_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-apigw"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
fRegion string
|
||||
fServiceType string
|
||||
fGatewayId string
|
||||
fGroupId string
|
||||
fDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNAPIGW_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
|
||||
flag.StringVar(&fGatewayId, argsPrefix+"GATEWARYID", "", "")
|
||||
flag.StringVar(&fGroupId, argsPrefix+"GROUPID", "", "")
|
||||
flag.StringVar(&fServiceType, argsPrefix+"SERVICETYPE", "", "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./aliyun_apigw_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYID="your-access-key-id" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_ACCESSKEYSECRET="your-access-key-secret" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_REGION="cn-hangzhou" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_GATEWAYID="your-api-gateway-id" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_GROUPID="your-api-group-id" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_SERVICETYPE="cloudnative" \
|
||||
--CERTIMATE_DEPLOYER_ALIYUNAPIGW_DOMAIN="example.com"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
|
||||
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
|
||||
fmt.Sprintf("REGION: %v", fRegion),
|
||||
fmt.Sprintf("GATEWAYID: %v", fGatewayId),
|
||||
fmt.Sprintf("GROUPID: %v", fGroupId),
|
||||
fmt.Sprintf("SERVICETYPE: %v", fServiceType),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
Region: fRegion,
|
||||
ServiceType: provider.ServiceType(fServiceType),
|
||||
GatewayId: fGatewayId,
|
||||
GroupId: fGroupId,
|
||||
Domain: fDomain,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
10
internal/pkg/core/deployer/providers/aliyun-apigw/consts.go
Normal file
10
internal/pkg/core/deployer/providers/aliyun-apigw/consts.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package aliyunapigw
|
||||
|
||||
type ServiceType string
|
||||
|
||||
const (
|
||||
// 服务类型:原 API 网关。
|
||||
SERVICE_TYPE_TRADITIONAL = ServiceType("traditional")
|
||||
// 服务类型:云原生 API 网关。
|
||||
SERVICE_TYPE_CLOUDNATIVE = ServiceType("cloudnative")
|
||||
)
|
||||
@@ -2,13 +2,23 @@
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"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/keyvault/azcertificates"
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/certutil"
|
||||
azcommon "github.com/usual2970/certimate/internal/pkg/vendors/azure-sdk/common"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
@@ -22,11 +32,15 @@ type DeployerConfig struct {
|
||||
CloudName string `json:"cloudName,omitempty"`
|
||||
// Key Vault 名称。
|
||||
KeyVaultName string `json:"keyvaultName"`
|
||||
// Key Vault 证书名称。
|
||||
// 选填。
|
||||
CertificateName string `json:"certificateName,omitempty"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *azcertificates.Client
|
||||
sslUploader uploader.Uploader
|
||||
}
|
||||
|
||||
@@ -37,6 +51,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
client, err := createSdkClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||
}
|
||||
|
||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||
TenantId: config.TenantId,
|
||||
ClientId: config.ClientId,
|
||||
@@ -51,6 +70,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: client,
|
||||
sslUploader: uploader,
|
||||
}, nil
|
||||
}
|
||||
@@ -66,13 +86,93 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
// 上传证书到 KeyVault
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
// 解析证书内容
|
||||
certX509, err := certutil.ParseCertificateFromPEM(certPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换证书格式
|
||||
certPfx, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, "")
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to transform certificate from PEM to PFX")
|
||||
}
|
||||
|
||||
if d.config.CertificateName == "" {
|
||||
// 上传证书到 KeyVault
|
||||
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
} else {
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
// 获取证书
|
||||
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate
|
||||
getCertificateResp, err := d.sdkClient.GetCertificate(context.TODO(), d.config.CertificateName, "", nil)
|
||||
d.logger.Debug("sdk request 'keyvault.GetCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("response", getCertificateResp))
|
||||
if err != nil {
|
||||
var respErr *azcore.ResponseError
|
||||
if !errors.As(err, &respErr) || (respErr.ErrorCode != "ResourceNotFound" && respErr.ErrorCode != "CertificateNotFound") {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.GetCertificate'")
|
||||
}
|
||||
} else {
|
||||
oldCertX509, err := x509.ParseCertificate(getCertificateResp.CER)
|
||||
if err == nil {
|
||||
if certutil.EqualCertificate(certX509, oldCertX509) {
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导入证书
|
||||
// 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{
|
||||
"certimate/cert-cn": to.Ptr(certX509.Subject.CommonName),
|
||||
"certimate/cert-sn": to.Ptr(certX509.SerialNumber.Text(16)),
|
||||
},
|
||||
}
|
||||
importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), d.config.CertificateName, importCertificateParams, nil)
|
||||
d.logger.Debug("sdk request 'keyvault.ImportCertificate'", slog.String("request.certificateName", d.config.CertificateName), slog.Any("request.parameters", importCertificateParams), slog.Any("response", importCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.ImportCertificate'")
|
||||
}
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSdkClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) {
|
||||
env, err := azcommon.GetCloudEnvironmentConfiguration(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 azcommon.IsEnvironmentGovernment(cloudName) {
|
||||
endpoint = fmt.Sprintf("https://%s.vault.usgovcloudapi.net", keyvaultName)
|
||||
} else if azcommon.IsEnvironmentChina(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
|
||||
}
|
||||
|
||||
70
internal/pkg/core/deployer/providers/bunny-cdn/bunny_cdn.go
Normal file
70
internal/pkg/core/deployer/providers/bunny-cdn/bunny_cdn.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package bunnycdn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
|
||||
xerrors "github.com/pkg/errors"
|
||||
|
||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||
bunnysdk "github.com/usual2970/certimate/internal/pkg/vendors/bunny-sdk"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
// Bunny API Key
|
||||
ApiKey string `json:"apiKey"`
|
||||
// Bunny Pull Zone ID
|
||||
PullZoneId string `json:"pullZoneId"`
|
||||
// Bunny CDN Hostname(支持泛域名)
|
||||
HostName string `json:"hostName"`
|
||||
}
|
||||
|
||||
type DeployerProvider struct {
|
||||
config *DeployerConfig
|
||||
logger *slog.Logger
|
||||
sdkClient *bunnysdk.Client
|
||||
}
|
||||
|
||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||
|
||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||
if config == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return &DeployerProvider{
|
||||
config: config,
|
||||
logger: slog.Default(),
|
||||
sdkClient: bunnysdk.NewClient(config.ApiKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||
if logger == nil {
|
||||
d.logger = slog.Default()
|
||||
} else {
|
||||
d.logger = logger
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||
// Prepare
|
||||
certPemBase64 := base64.StdEncoding.EncodeToString([]byte(certPem))
|
||||
privkeyPemBase64 := base64.StdEncoding.EncodeToString([]byte(privkeyPem))
|
||||
// 上传证书
|
||||
createCertificateReq := &bunnysdk.AddCustomCertificateRequest{
|
||||
Hostname: d.config.HostName,
|
||||
PullZoneId: d.config.PullZoneId,
|
||||
Certificate: certPemBase64,
|
||||
CertificateKey: privkeyPemBase64,
|
||||
}
|
||||
createCertificateResp, err := d.sdkClient.AddCustomCertificate(createCertificateReq)
|
||||
d.logger.Debug("sdk request 'bunny-cdn.AddCustomCertificate'", slog.Any("request", createCertificateReq), slog.Any("response", createCertificateResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'bunny-cdn.AddCustomCertificate'")
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package bunnycdn_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fApiKey string
|
||||
fPullZoneId string
|
||||
fHostName string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_DEPLOYER_BUNNYCDN_"
|
||||
|
||||
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
flag.StringVar(&fPullZoneId, argsPrefix+"PULLZONEID", "", "")
|
||||
flag.StringVar(&fHostName, argsPrefix+"HOSTNAME", "", "")
|
||||
}
|
||||
|
||||
/*
|
||||
Shell command to run this test:
|
||||
|
||||
go test -v ./bunny_cdn_test.go -args \
|
||||
--CERTIMATE_DEPLOYER_BUNNYCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_DEPLOYER_BUNNYCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_BUNNYCDN_APITOKEN="your-api-token" \
|
||||
--CERTIMATE_DEPLOYER_BUNNYCDN_PULLZONEID="your-pull-zone-id" \
|
||||
--CERTIMATE_DEPLOYER_BUNNYCDN_HOSTNAME="example.com"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
flag.Parse()
|
||||
|
||||
t.Run("Deploy", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("APIKEY: %v", fApiKey),
|
||||
fmt.Sprintf("PULLZONEID: %v", fPullZoneId),
|
||||
fmt.Sprintf("HOSTNAME: %v", fHostName),
|
||||
}, "\n"))
|
||||
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
ApiKey: fApiKey,
|
||||
PullZoneId: fPullZoneId,
|
||||
HostName: fHostName,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fInputCertData, _ := os.ReadFile(fInputCertPath)
|
||||
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
|
||||
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
@@ -243,7 +243,6 @@ func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error {
|
||||
if err != nil {
|
||||
return xerrors.Wrap(err, "failed to create scp client")
|
||||
}
|
||||
defer scpCli.Close()
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{})
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
@@ -28,6 +29,8 @@ type DeployerConfig struct {
|
||||
AccessKeyId string `json:"accessKeyId"`
|
||||
// 网宿云 AccessKeySecret。
|
||||
AccessKeySecret string `json:"accessKeySecret"`
|
||||
// 网宿云 API Key。
|
||||
ApiKey string `json:"apiKey"`
|
||||
// 网宿云环境。
|
||||
Environment string `json:"environment"`
|
||||
// 加速域名(支持泛域名)。
|
||||
@@ -93,7 +96,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
|
||||
// 生成网宿云证书参数
|
||||
encryptedPrivateKey, err := encryptPrivateKey(privkeyPem, d.config.AccessKeySecret, time.Now().Unix())
|
||||
encryptedPrivateKey, err := encryptPrivateKey(privkeyPem, d.config.ApiKey, time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to encrypt private key")
|
||||
}
|
||||
@@ -111,7 +114,8 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
// http://open.chinanetcenter.com/cdn/certificates/5dca2205f9e9cc0001df7b33
|
||||
// http://open.chinanetcenter.com/cdn/certificates/329f12c1fe6708c23c31e91f/versions/5
|
||||
var wangsuCertUrl string
|
||||
var wangsuCertId, wangsuCertVer string
|
||||
var wangsuCertId string
|
||||
var wangsuCertVer int32
|
||||
|
||||
// 如果原证书 ID 为空,则创建证书;否则更新证书。
|
||||
timestamp := time.Now().Unix()
|
||||
@@ -137,7 +141,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
wangsuCertId = wangsuCertIdMatches[1]
|
||||
}
|
||||
|
||||
wangsuCertVer = "1"
|
||||
wangsuCertVer = 1
|
||||
} else {
|
||||
// 更新证书
|
||||
updateCertificateReq := &wangsucdn.UpdateCertificateRequest{
|
||||
@@ -162,7 +166,8 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
|
||||
wangsuCertVerMatches := regexp.MustCompile(`/versions/(\d+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
if len(wangsuCertVerMatches) > 1 {
|
||||
wangsuCertVer = wangsuCertVerMatches[1]
|
||||
n, _ := strconv.ParseInt(wangsuCertVerMatches[1], 10, 32)
|
||||
wangsuCertVer = int32(n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +180,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
{
|
||||
Action: tea.String("deploy_cert"),
|
||||
CertificateId: tea.String(wangsuCertId),
|
||||
Version: tea.String(wangsuCertVer),
|
||||
Version: tea.Int32(wangsuCertVer),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -191,7 +196,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
// 循环获取部署任务详细信息,等待任务状态变更
|
||||
// REF: https://www.wangsu.com/document/api-doc/27038
|
||||
var wangsuTaskId string
|
||||
wangsuTaskMatches := regexp.MustCompile(`/deploymentTasks/([a-zA-Z0-9-]+)`).FindStringSubmatch(wangsuCertUrl)
|
||||
wangsuTaskMatches := regexp.MustCompile(`/deploymentTasks/([a-zA-Z0-9-]+)`).FindStringSubmatch(createDeploymentTaskResp.DeploymentTaskUrl)
|
||||
if len(wangsuTaskMatches) > 1 {
|
||||
wangsuTaskId = wangsuTaskMatches[1]
|
||||
}
|
||||
@@ -201,19 +206,19 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
|
||||
getDeploymentTaskDetailResp, err := d.sdkClient.GetDeploymentTaskDetail(wangsuTaskId)
|
||||
d.logger.Debug("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
|
||||
d.logger.Info("sdk request 'cdn.GetDeploymentTaskDetail'", slog.Any("taskId", wangsuTaskId), slog.Any("response", getDeploymentTaskDetailResp))
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDeploymentTaskDetail'")
|
||||
}
|
||||
|
||||
if getDeploymentTaskDetailResp.Status == "failed" {
|
||||
return nil, errors.New("unexpected deployment task status")
|
||||
} else if getDeploymentTaskDetailResp.Status == "succeeded" {
|
||||
} else if getDeploymentTaskDetailResp.Status == "succeeded" || getDeploymentTaskDetailResp.FinishTime != "" {
|
||||
break
|
||||
}
|
||||
|
||||
d.logger.Info("waiting for deployment task completion ...")
|
||||
time.Sleep(time.Second * 15)
|
||||
d.logger.Info(fmt.Sprintf("waiting for deployment task completion (current status: %s) ...", getDeploymentTaskDetailResp.Status))
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
@@ -231,11 +236,11 @@ func createSdkClient(accessKeyId, accessKeySecret string) (*wangsucdn.Client, er
|
||||
return wangsucdn.NewClient(accessKeyId, accessKeySecret), nil
|
||||
}
|
||||
|
||||
func encryptPrivateKey(privkeyPem string, secretKey string, timestamp int64) (string, error) {
|
||||
func encryptPrivateKey(privkeyPem string, apiKey string, timestamp int64) (string, error) {
|
||||
date := time.Unix(timestamp, 0).UTC()
|
||||
dateStr := date.Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secretKey))
|
||||
mac := hmac.New(sha256.New, []byte(apiKey))
|
||||
mac.Write([]byte(dateStr))
|
||||
aesivkey := mac.Sum(nil)
|
||||
aesivkeyHex := hex.EncodeToString(aesivkey)
|
||||
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
fInputKeyPath string
|
||||
fAccessKeyId string
|
||||
fAccessKeySecret string
|
||||
fApiKey string
|
||||
fEnvironment string
|
||||
fDomain string
|
||||
fCertificateId string
|
||||
@@ -29,6 +30,7 @@ func init() {
|
||||
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
|
||||
flag.StringVar(&fEnvironment, argsPrefix+"ENVIRONMENT", "production", "")
|
||||
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
|
||||
@@ -43,9 +45,10 @@ Shell command to run this test:
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYID="your-access-key-id" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ACCESSKEYSECRET="your-access-key-secret" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_APIKEY="your-api-key" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_ENVIRONMENT="production" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_DOMAIN="example.com" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_CERTIFICATEID="your-certificate-id"\
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_CERTIFICATEID="your-certificate-id" \
|
||||
--CERTIMATE_DEPLOYER_WANGSUCDNPRO_WEBHOOKID="your-webhook-id"
|
||||
*/
|
||||
func TestDeploy(t *testing.T) {
|
||||
@@ -58,6 +61,7 @@ func TestDeploy(t *testing.T) {
|
||||
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
|
||||
fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret),
|
||||
fmt.Sprintf("APIKEY: %v", fApiKey),
|
||||
fmt.Sprintf("ENVIRONMENT: %v", fEnvironment),
|
||||
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
|
||||
@@ -67,6 +71,7 @@ func TestDeploy(t *testing.T) {
|
||||
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||
AccessKeyId: fAccessKeyId,
|
||||
AccessKeySecret: fAccessKeySecret,
|
||||
ApiKey: fApiKey,
|
||||
Environment: fEnvironment,
|
||||
Domain: fDomain,
|
||||
CertificateId: fCertificateId,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
@@ -141,13 +142,21 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
// 生成新证书名(需符合 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 := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, "")
|
||||
if err != nil {
|
||||
return nil, xerrors.Wrap(err, "failed to transform certificate from PEM to PFX")
|
||||
}
|
||||
|
||||
// 导入证书
|
||||
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
|
||||
importCertificateParams := azcertificates.ImportCertificateParameters{
|
||||
Base64EncodedCertificate: to.Ptr(certPem),
|
||||
Base64EncodedCertificate: to.Ptr(base64.StdEncoding.EncodeToString(certPfx)),
|
||||
CertificatePolicy: &azcertificates.CertificatePolicy{
|
||||
SecretProperties: &azcertificates.SecretProperties{
|
||||
ContentType: to.Ptr("application/x-pem-file"),
|
||||
ContentType: to.Ptr("application/x-pkcs12"),
|
||||
},
|
||||
},
|
||||
Tags: map[string]*string{
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package azurekeyvault_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault"
|
||||
)
|
||||
|
||||
var (
|
||||
fInputCertPath string
|
||||
fInputKeyPath string
|
||||
fTenantId string
|
||||
fClientId string
|
||||
fClientSecret string
|
||||
fCloudName string
|
||||
fKeyVaultName string
|
||||
)
|
||||
|
||||
func init() {
|
||||
argsPrefix := "CERTIMATE_UPLOADER_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_UPLOADER_AZUREKEYVAULT_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||
--CERTIMATE_UPLOADER_AZUREKEYVAULT_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||
--CERTIMATE_UPLOADER_AZUREKEYVAULT_TENANTID="your-tenant-id" \
|
||||
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLIENTID="your-app-registration-client-id" \
|
||||
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLIENTSECRET="your-app-registration-client-secret" \
|
||||
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CLOUDNAME="china" \
|
||||
--CERTIMATE_UPLOADER_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"))
|
||||
|
||||
uploader, err := provider.NewUploader(&provider.UploaderConfig{
|
||||
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 := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
sres, _ := json.Marshal(res)
|
||||
t.Logf("ok: %s", string(sres))
|
||||
})
|
||||
}
|
||||
@@ -72,12 +72,17 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
|
||||
}
|
||||
|
||||
var certId string
|
||||
if importCertificateResp.InstanceId != nil {
|
||||
if importCertificateResp.InstanceId != nil && *importCertificateResp.InstanceId != "" {
|
||||
certId = *importCertificateResp.InstanceId
|
||||
}
|
||||
if importCertificateResp.RepeatId != nil {
|
||||
if importCertificateResp.RepeatId != nil && *importCertificateResp.RepeatId != "" {
|
||||
certId = *importCertificateResp.RepeatId
|
||||
}
|
||||
|
||||
if certId == "" {
|
||||
return nil, xerrors.New("failed to get certificate id, both `InstanceId` and `RepeatId` are empty")
|
||||
}
|
||||
|
||||
return &uploader.UploadResult{
|
||||
CertId: certId,
|
||||
}, nil
|
||||
|
||||
Reference in New Issue
Block a user