mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
fix conflict
This commit is contained in:
commit
47050769fc
@ -20,10 +20,10 @@ func NewVolcengine(option *ApplyOption) Applicant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *volcengine) Apply() (*Certificate, error) {
|
func (a *volcengine) Apply() (*Certificate, error) {
|
||||||
access := &domain.VolcengineAccess{}
|
access := &domain.VolcEngineAccess{}
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("VOLC_ACCESSKEY", access.AccessKeyID)
|
os.Setenv("VOLC_ACCESSKEY", access.AccessKeyId)
|
||||||
os.Setenv("VOLC_SECRETKEY", access.SecretAccessKey)
|
os.Setenv("VOLC_SECRETKEY", access.SecretAccessKey)
|
||||||
os.Setenv("VOLC_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
os.Setenv("VOLC_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||||
dnsProvider, err := volcengineDns.NewDNSProvider()
|
dnsProvider, err := volcengineDns.NewDNSProvider()
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"software.sslmate.com/src/go-pkcs12"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/applicant"
|
"github.com/usual2970/certimate/internal/applicant"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
|
||||||
"github.com/usual2970/certimate/internal/repository"
|
"github.com/usual2970/certimate/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,15 +28,15 @@ const (
|
|||||||
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
targetHuaweiCloudCDN = "huaweicloud-cdn"
|
||||||
targetHuaweiCloudELB = "huaweicloud-elb"
|
targetHuaweiCloudELB = "huaweicloud-elb"
|
||||||
targetBaiduCloudCDN = "baiducloud-cdn"
|
targetBaiduCloudCDN = "baiducloud-cdn"
|
||||||
|
targetVolcEngineLive = "volcengine-live"
|
||||||
|
targetVolcEngineCDN = "volcengine-cdn"
|
||||||
|
targetBytePlusCDN = "byteplus-cdn"
|
||||||
targetQiniuCdn = "qiniu-cdn"
|
targetQiniuCdn = "qiniu-cdn"
|
||||||
targetDogeCloudCdn = "dogecloud-cdn"
|
targetDogeCloudCdn = "dogecloud-cdn"
|
||||||
targetLocal = "local"
|
targetLocal = "local"
|
||||||
targetSSH = "ssh"
|
targetSSH = "ssh"
|
||||||
targetWebhook = "webhook"
|
targetWebhook = "webhook"
|
||||||
targetK8sSecret = "k8s-secret"
|
targetK8sSecret = "k8s-secret"
|
||||||
targetVolcengineLive = "volcengine-live"
|
|
||||||
targetVolcengineCDN = "volcengine-cdn"
|
|
||||||
targetByteplusCDN = "byteplus-cdn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerOption struct {
|
type DeployerOption struct {
|
||||||
@ -162,11 +156,11 @@ func getWithTypeAndOption(deployType string, option *DeployerOption) (Deployer,
|
|||||||
return NewWebhookDeployer(option)
|
return NewWebhookDeployer(option)
|
||||||
case targetK8sSecret:
|
case targetK8sSecret:
|
||||||
return NewK8sSecretDeployer(option)
|
return NewK8sSecretDeployer(option)
|
||||||
case targetVolcengineLive:
|
case targetVolcEngineLive:
|
||||||
return NewVolcengineLiveDeployer(option)
|
return NewVolcengineLiveDeployer(option)
|
||||||
case targetVolcengineCDN:
|
case targetVolcEngineCDN:
|
||||||
return NewVolcengineCDNDeployer(option)
|
return NewVolcengineCDNDeployer(option)
|
||||||
case targetByteplusCDN:
|
case targetBytePlusCDN:
|
||||||
return NewByteplusCDNDeployer(option)
|
return NewByteplusCDNDeployer(option)
|
||||||
}
|
}
|
||||||
return nil, errors.New("unsupported deploy target")
|
return nil, errors.New("unsupported deploy target")
|
||||||
@ -179,57 +173,3 @@ func toStr(tag string, data any) string {
|
|||||||
byts, _ := json.Marshal(data)
|
byts, _ := json.Marshal(data)
|
||||||
return tag + ":" + string(byts)
|
return tag + ":" + string(byts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) {
|
|
||||||
cert, err := x509.ParseCertificateFromPEM(certificate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privkey, err := x509.ParsePKCS1PrivateKeyFromPEM(privateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pfxData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertPEMToJKS(certificate string, privateKey string, alias string, keypass string, storepass string) ([]byte, error) {
|
|
||||||
certBlock, _ := pem.Decode([]byte(certificate))
|
|
||||||
if certBlock == nil {
|
|
||||||
return nil, errors.New("failed to decode certificate PEM")
|
|
||||||
}
|
|
||||||
|
|
||||||
privkeyBlock, _ := pem.Decode([]byte(privateKey))
|
|
||||||
if privkeyBlock == nil {
|
|
||||||
return nil, errors.New("failed to decode private key PEM")
|
|
||||||
}
|
|
||||||
|
|
||||||
ks := keystore.New()
|
|
||||||
entry := keystore.PrivateKeyEntry{
|
|
||||||
CreationTime: time.Now(),
|
|
||||||
PrivateKey: privkeyBlock.Bytes,
|
|
||||||
CertificateChain: []keystore.Certificate{
|
|
||||||
{
|
|
||||||
Type: "X509",
|
|
||||||
Content: certBlock.Bytes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ks.SetPrivateKeyEntry(alias, entry, []byte(keypass)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := ks.Store(&buf, []byte(storepass)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
374
internal/deployer/factory.go
Normal file
374
internal/deployer/factory.go
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
providerAliyunAlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb"
|
||||||
|
providerAliyunCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
|
||||||
|
providerAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb"
|
||||||
|
providerAliyunDcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn"
|
||||||
|
providerAliyunNlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb"
|
||||||
|
providerAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss"
|
||||||
|
providerBaiduCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn"
|
||||||
|
providerBytePlusCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
|
||||||
|
providerDogeCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn"
|
||||||
|
providerHuaweiCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn"
|
||||||
|
providerHuaweiCloudElb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb"
|
||||||
|
providerK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
|
||||||
|
providerLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
|
||||||
|
providerQiniuCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
|
||||||
|
providerSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
|
||||||
|
providerTencentCloudCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
|
||||||
|
providerTencentCloudClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb"
|
||||||
|
providerTencentCloudCos "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos"
|
||||||
|
providerTencentCloudEcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn"
|
||||||
|
providerTencentCloudTeo "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-teo"
|
||||||
|
providerVolcEngineCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn"
|
||||||
|
providerVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
|
||||||
|
providerWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: 该方法目前未实际使用,将在后续迭代中替换
|
||||||
|
func createDeployer(target string, accessConfig string, deployConfig map[string]any) (deployer.Deployer, deployer.Logger, error) {
|
||||||
|
logger := deployer.NewDefaultLogger()
|
||||||
|
|
||||||
|
switch target {
|
||||||
|
case targetAliyunALB, targetAliyunCDN, targetAliyunCLB, targetAliyunDCDN, targetAliyunNLB, targetAliyunOSS:
|
||||||
|
{
|
||||||
|
access := &domain.AliyunAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch target {
|
||||||
|
case targetAliyunALB:
|
||||||
|
deployer, err := providerAliyunAlb.NewWithLogger(&providerAliyunAlb.AliyunALBDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
ResourceType: providerAliyunAlb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")),
|
||||||
|
LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"),
|
||||||
|
ListenerId: maps.GetValueAsString(deployConfig, "listenerId"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetAliyunCDN:
|
||||||
|
deployer, err := providerAliyunCdn.NewWithLogger(&providerAliyunCdn.AliyunCDNDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetAliyunCLB:
|
||||||
|
deployer, err := providerAliyunClb.NewWithLogger(&providerAliyunClb.AliyunCLBDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
ResourceType: providerAliyunClb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")),
|
||||||
|
LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"),
|
||||||
|
ListenerPort: maps.GetValueAsInt32(deployConfig, "listenerPort"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetAliyunDCDN:
|
||||||
|
deployer, err := providerAliyunDcdn.NewWithLogger(&providerAliyunDcdn.AliyunDCDNDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetAliyunNLB:
|
||||||
|
deployer, err := providerAliyunNlb.NewWithLogger(&providerAliyunNlb.AliyunNLBDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
ResourceType: providerAliyunNlb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")),
|
||||||
|
LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"),
|
||||||
|
ListenerId: maps.GetValueAsString(deployConfig, "listenerId"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetAliyunOSS:
|
||||||
|
deployer, err := providerAliyunOss.NewWithLogger(&providerAliyunOss.AliyunOSSDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
AccessKeySecret: access.AccessKeySecret,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
Bucket: maps.GetValueAsString(deployConfig, "bucket"),
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetBaiduCloudCDN:
|
||||||
|
{
|
||||||
|
access := &domain.BaiduCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerBaiduCloudCdn.NewWithLogger(&providerBaiduCloudCdn.BaiduCloudCDNDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetBytePlusCDN:
|
||||||
|
{
|
||||||
|
access := &domain.ByteplusAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerBytePlusCdn.NewWithLogger(&providerBytePlusCdn.BytePlusCDNDeployerConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetDogeCloudCdn:
|
||||||
|
{
|
||||||
|
access := &domain.DogeCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerDogeCdn.NewWithLogger(&providerDogeCdn.DogeCloudCDNDeployerConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetHuaweiCloudCDN, targetHuaweiCloudELB:
|
||||||
|
{
|
||||||
|
access := &domain.HuaweiCloudAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch target {
|
||||||
|
case targetHuaweiCloudCDN:
|
||||||
|
deployer, err := providerHuaweiCloudCdn.NewWithLogger(&providerHuaweiCloudCdn.HuaweiCloudCDNDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetHuaweiCloudELB:
|
||||||
|
deployer, err := providerHuaweiCloudElb.NewWithLogger(&providerHuaweiCloudElb.HuaweiCloudELBDeployerConfig{
|
||||||
|
AccessKeyId: access.AccessKeyId,
|
||||||
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
ResourceType: providerHuaweiCloudElb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")),
|
||||||
|
CertificateId: maps.GetValueAsString(deployConfig, "certificateId"),
|
||||||
|
LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"),
|
||||||
|
ListenerId: maps.GetValueAsString(deployConfig, "listenerId"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetLocal:
|
||||||
|
{
|
||||||
|
deployer, err := providerLocal.NewWithLogger(&providerLocal.LocalDeployerConfig{
|
||||||
|
ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(deployConfig, "shellEnv")),
|
||||||
|
PreCommand: maps.GetValueAsString(deployConfig, "preCommand"),
|
||||||
|
PostCommand: maps.GetValueAsString(deployConfig, "postCommand"),
|
||||||
|
OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")),
|
||||||
|
OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"),
|
||||||
|
OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"),
|
||||||
|
PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"),
|
||||||
|
JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"),
|
||||||
|
JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"),
|
||||||
|
JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetK8sSecret:
|
||||||
|
{
|
||||||
|
access := &domain.KubernetesAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerK8sSecret.NewWithLogger(&providerK8sSecret.K8sSecretDeployerConfig{
|
||||||
|
KubeConfig: access.KubeConfig,
|
||||||
|
Namespace: maps.GetValueOrDefaultAsString(deployConfig, "namespace", "default"),
|
||||||
|
SecretName: maps.GetValueAsString(deployConfig, "secretName"),
|
||||||
|
SecretDataKeyForCrt: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForCrt", "tls.crt"),
|
||||||
|
SecretDataKeyForKey: maps.GetValueOrDefaultAsString(deployConfig, "secretDataKeyForKey", "tls.key"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetQiniuCdn:
|
||||||
|
{
|
||||||
|
access := &domain.QiniuAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerQiniuCdn.NewWithLogger(&providerQiniuCdn.QiniuCDNDeployerConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetSSH:
|
||||||
|
{
|
||||||
|
access := &domain.SSHAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshPort, _ := strconv.ParseInt(access.Port, 10, 32)
|
||||||
|
deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{
|
||||||
|
SshHost: access.Host,
|
||||||
|
SshPort: int32(sshPort),
|
||||||
|
SshUsername: access.Username,
|
||||||
|
SshPassword: access.Password,
|
||||||
|
SshKey: access.Key,
|
||||||
|
SshKeyPassphrase: access.KeyPassphrase,
|
||||||
|
PreCommand: maps.GetValueAsString(deployConfig, "preCommand"),
|
||||||
|
PostCommand: maps.GetValueAsString(deployConfig, "postCommand"),
|
||||||
|
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")),
|
||||||
|
OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"),
|
||||||
|
OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"),
|
||||||
|
PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"),
|
||||||
|
JksAlias: maps.GetValueAsString(deployConfig, "jksAlias"),
|
||||||
|
JksKeypass: maps.GetValueAsString(deployConfig, "jksKeypass"),
|
||||||
|
JksStorepass: maps.GetValueAsString(deployConfig, "jksStorepass"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetTencentCDN, targetTencentCLB, targetTencentCOS, targetTencentECDN, targetTencentTEO:
|
||||||
|
{
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch target {
|
||||||
|
case targetTencentCDN:
|
||||||
|
deployer, err := providerTencentCloudCdn.NewWithLogger(&providerTencentCloudCdn.TencentCloudCDNDeployerConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetTencentCLB:
|
||||||
|
deployer, err := providerTencentCloudClb.NewWithLogger(&providerTencentCloudClb.TencentCloudCLBDeployerConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
ResourceType: providerTencentCloudClb.DeployResourceType(maps.GetValueAsString(deployConfig, "resourceType")),
|
||||||
|
LoadbalancerId: maps.GetValueAsString(deployConfig, "loadbalancerId"),
|
||||||
|
ListenerId: maps.GetValueAsString(deployConfig, "listenerId"),
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetTencentCOS:
|
||||||
|
deployer, err := providerTencentCloudCos.NewWithLogger(&providerTencentCloudCos.TencentCloudCOSDeployerConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Region: maps.GetValueAsString(deployConfig, "region"),
|
||||||
|
Bucket: maps.GetValueAsString(deployConfig, "bucket"),
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetTencentECDN:
|
||||||
|
deployer, err := providerTencentCloudEcdn.NewWithLogger(&providerTencentCloudEcdn.TencentCloudECDNDeployerConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetTencentTEO:
|
||||||
|
deployer, err := providerTencentCloudTeo.NewWithLogger(&providerTencentCloudTeo.TencentCloudTEODeployerConfig{
|
||||||
|
SecretId: access.SecretId,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
ZoneId: maps.GetValueAsString(deployConfig, "zoneId"),
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetVolcEngineCDN, targetVolcEngineLive:
|
||||||
|
{
|
||||||
|
access := &domain.VolcEngineAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch target {
|
||||||
|
case targetVolcEngineCDN:
|
||||||
|
deployer, err := providerVolcEngineCdn.NewWithLogger(&providerVolcEngineCdn.VolcEngineCDNDeployerConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
case targetVolcEngineLive:
|
||||||
|
deployer, err := providerVolcEngineLive.NewWithLogger(&providerVolcEngineLive.VolcEngineLiveDeployerConfig{
|
||||||
|
AccessKey: access.AccessKey,
|
||||||
|
SecretKey: access.SecretKey,
|
||||||
|
Domain: maps.GetValueAsString(deployConfig, "domain"),
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case targetWebhook:
|
||||||
|
{
|
||||||
|
access := &domain.WebhookAccess{}
|
||||||
|
if err := json.Unmarshal([]byte(accessConfig), access); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{
|
||||||
|
Url: access.Url,
|
||||||
|
Variables: nil, // TODO: 尚未实现
|
||||||
|
}, logger)
|
||||||
|
return deployer, logger, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, fmt.Errorf("unsupported deployer target: %s", target)
|
||||||
|
}
|
@ -5,8 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
|
||||||
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
@ -17,6 +15,7 @@ import (
|
|||||||
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
hcIamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model"
|
||||||
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
hcIamRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region"
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
@ -163,9 +162,6 @@ func (u *HuaweiCloudELBDeployer) getSdkProjectId(accessKeyId, secretAccessKey, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := hcIam.NewIamClient(hcClient)
|
client := hcIam.NewIamClient(hcClient)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &hcIamModel.KeystoneListProjectsRequest{
|
request := &hcIamModel.KeystoneListProjectsRequest{
|
||||||
Name: ®ion,
|
Name: ®ion,
|
||||||
@ -352,11 +348,7 @@ func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context,
|
|||||||
newCertificate := showNewCertificateResp.Certificate
|
newCertificate := showNewCertificateResp.Certificate
|
||||||
|
|
||||||
if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil {
|
if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil {
|
||||||
oldCertificateSans := oldCertificate.SubjectAlternativeNames
|
if slices.Equal(*oldCertificate.SubjectAlternativeNames, *newCertificate.SubjectAlternativeNames) {
|
||||||
newCertificateSans := newCertificate.SubjectAlternativeNames
|
|
||||||
sort.Strings(*oldCertificateSans)
|
|
||||||
sort.Strings(*newCertificateSans)
|
|
||||||
if strings.Join(*oldCertificateSans, ";") == strings.Join(*newCertificateSans, ";") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalDeployer struct {
|
type LocalDeployer struct {
|
||||||
@ -73,7 +74,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
|||||||
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
||||||
|
|
||||||
case certFormatPFX:
|
case certFormatPFX:
|
||||||
pfxData, err := convertPEMToPFX(
|
pfxData, err := x509.TransformCertificateFromPEMToPFX(
|
||||||
d.option.Certificate.Certificate,
|
d.option.Certificate.Certificate,
|
||||||
d.option.Certificate.PrivateKey,
|
d.option.Certificate.PrivateKey,
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
@ -89,7 +90,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
|||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||||
|
|
||||||
case certFormatJKS:
|
case certFormatJKS:
|
||||||
jksData, err := convertPEMToJKS(
|
jksData, err := x509.TransformCertificateFromPEMToJKS(
|
||||||
d.option.Certificate.Certificate,
|
d.option.Certificate.Certificate,
|
||||||
d.option.Certificate.PrivateKey,
|
d.option.Certificate.PrivateKey,
|
||||||
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHDeployer struct {
|
type SSHDeployer struct {
|
||||||
@ -78,7 +79,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
|||||||
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
||||||
|
|
||||||
case certFormatPFX:
|
case certFormatPFX:
|
||||||
pfxData, err := convertPEMToPFX(
|
pfxData, err := x509.TransformCertificateFromPEMToPFX(
|
||||||
d.option.Certificate.Certificate,
|
d.option.Certificate.Certificate,
|
||||||
d.option.Certificate.PrivateKey,
|
d.option.Certificate.PrivateKey,
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
||||||
@ -94,7 +95,7 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
|||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||||
|
|
||||||
case certFormatJKS:
|
case certFormatJKS:
|
||||||
jksData, err := convertPEMToJKS(
|
jksData, err := x509.TransformCertificateFromPEMToJKS(
|
||||||
d.option.Certificate.Certificate,
|
d.option.Certificate.Certificate,
|
||||||
d.option.Certificate.PrivateKey,
|
d.option.Certificate.PrivateKey,
|
||||||
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
||||||
|
@ -22,15 +22,15 @@ type VolcengineCDNDeployer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) {
|
func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.VolcengineAccess{}
|
access := &domain.VolcEngineAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
client := cdn.NewInstance()
|
client := cdn.NewInstance()
|
||||||
client.Client.SetAccessKey(access.AccessKeyID)
|
client.Client.SetAccessKey(access.AccessKeyId)
|
||||||
client.Client.SetSecretKey(access.SecretAccessKey)
|
client.Client.SetSecretKey(access.SecretAccessKey)
|
||||||
uploader, err := volcenginecdn.New(&volcenginecdn.VolcengineCDNUploaderConfig{
|
uploader, err := volcenginecdn.New(&volcenginecdn.VolcEngineCDNUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyID,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.SecretAccessKey,
|
AccessKeySecret: access.SecretAccessKey,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -24,17 +24,17 @@ type VolcengineLiveDeployer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) {
|
func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) {
|
||||||
access := &domain.VolcengineAccess{}
|
access := &domain.VolcEngineAccess{}
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
return nil, xerrors.Wrap(err, "failed to get access")
|
||||||
}
|
}
|
||||||
client := live.NewInstance()
|
client := live.NewInstance()
|
||||||
client.SetCredential(base.Credentials{
|
client.SetCredential(base.Credentials{
|
||||||
AccessKeyID: access.AccessKeyID,
|
AccessKeyID: access.AccessKeyId,
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
})
|
})
|
||||||
uploader, err := volcenginelive.New(&volcenginelive.VolcengineLiveUploaderConfig{
|
uploader, err := volcenginelive.New(&volcenginelive.VolcEngineLiveUploaderConfig{
|
||||||
AccessKeyId: access.AccessKeyID,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKeySecret: access.SecretAccessKey,
|
AccessKeySecret: access.SecretAccessKey,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -27,8 +27,8 @@ type AliyunAccess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ByteplusAccess struct {
|
type ByteplusAccess struct {
|
||||||
AccessKey string
|
AccessKey string `json:"accessKey"`
|
||||||
SecretKey string
|
SecretKey string `json:"secretKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TencentAccess struct {
|
type TencentAccess struct {
|
||||||
@ -48,9 +48,9 @@ type BaiduCloudAccess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AwsAccess struct {
|
type AwsAccess struct {
|
||||||
Region string `json:"region"`
|
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
Region string `json:"region"`
|
||||||
HostedZoneId string `json:"hostedZoneId"`
|
HostedZoneId string `json:"hostedZoneId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +82,14 @@ type PdnsAccess struct {
|
|||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolcengineAccess struct {
|
type VolcEngineAccess struct {
|
||||||
AccessKeyID string
|
AccessKey string `json:"accessKey"`
|
||||||
SecretAccessKey string
|
SecretKey string `json:"secretKey"`
|
||||||
|
|
||||||
|
// Deprecated: Use [AccessKey] and [SecretKey] instead in the future
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// Deprecated: Use [AccessKey] and [SecretKey] instead in the future
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HttpreqAccess struct {
|
type HttpreqAccess struct {
|
||||||
|
@ -41,7 +41,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string {
|
|||||||
// - defaultValue: 默认值。
|
// - defaultValue: 默认值。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。
|
// - 配置项的值。如果配置项不存在、类型不是字符串或者值为零值,则返回默认值。
|
||||||
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
|
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
|
||||||
return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue)
|
return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue)
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 {
|
|||||||
// - defaultValue: 默认值。
|
// - defaultValue: 默认值。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。
|
// - 配置项的值。如果配置项不存在、类型不是 32 位整数或者值为零值,则返回默认值。
|
||||||
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 {
|
||||||
return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue)
|
return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue)
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
package notify
|
package notify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
notifierBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
|
providerBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
|
||||||
notifierDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
|
providerDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
|
||||||
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
providerEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||||
notifierLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
providerLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
||||||
notifierServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
providerServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
||||||
notifierTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
providerTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||||
notifierWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
providerWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
"github.com/usual2970/certimate/internal/pkg/utils/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) {
|
func createNotifier(channel string, channelConfig map[string]any) (notifier.Notifier, error) {
|
||||||
switch channel {
|
switch channel {
|
||||||
case domain.NotifyChannelEmail:
|
case domain.NotifyChannelEmail:
|
||||||
return notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
return providerEmail.New(&providerEmail.EmailNotifierConfig{
|
||||||
SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"),
|
SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"),
|
||||||
SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"),
|
SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"),
|
||||||
SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true),
|
SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true),
|
||||||
@ -29,38 +29,38 @@ func createNotifier(channel string, channelConfig map[string]any) (notifier.Noti
|
|||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelWebhook:
|
case domain.NotifyChannelWebhook:
|
||||||
return notifierWebhook.New(¬ifierWebhook.WebhookNotifierConfig{
|
return providerWebhook.New(&providerWebhook.WebhookNotifierConfig{
|
||||||
Url: maps.GetValueAsString(channelConfig, "url"),
|
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelDingtalk:
|
case domain.NotifyChannelDingtalk:
|
||||||
return notifierDingTalk.New(¬ifierDingTalk.DingTalkNotifierConfig{
|
return providerDingTalk.New(&providerDingTalk.DingTalkNotifierConfig{
|
||||||
AccessToken: maps.GetValueAsString(channelConfig, "accessToken"),
|
AccessToken: maps.GetValueAsString(channelConfig, "accessToken"),
|
||||||
Secret: maps.GetValueAsString(channelConfig, "secret"),
|
Secret: maps.GetValueAsString(channelConfig, "secret"),
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelLark:
|
case domain.NotifyChannelLark:
|
||||||
return notifierLark.New(¬ifierLark.LarkNotifierConfig{
|
return providerLark.New(&providerLark.LarkNotifierConfig{
|
||||||
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
|
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelTelegram:
|
case domain.NotifyChannelTelegram:
|
||||||
return notifierTelegram.New(¬ifierTelegram.TelegramNotifierConfig{
|
return providerTelegram.New(&providerTelegram.TelegramNotifierConfig{
|
||||||
ApiToken: maps.GetValueAsString(channelConfig, "apiToken"),
|
ApiToken: maps.GetValueAsString(channelConfig, "apiToken"),
|
||||||
ChatId: maps.GetValueAsInt64(channelConfig, "chatId"),
|
ChatId: maps.GetValueAsInt64(channelConfig, "chatId"),
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelServerChan:
|
case domain.NotifyChannelServerChan:
|
||||||
return notifierServerChan.New(¬ifierServerChan.ServerChanNotifierConfig{
|
return providerServerChan.New(&providerServerChan.ServerChanNotifierConfig{
|
||||||
Url: maps.GetValueAsString(channelConfig, "url"),
|
Url: maps.GetValueAsString(channelConfig, "url"),
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.NotifyChannelBark:
|
case domain.NotifyChannelBark:
|
||||||
return notifierBark.New(¬ifierBark.BarkNotifierConfig{
|
return providerBark.New(&providerBark.BarkNotifierConfig{
|
||||||
DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"),
|
DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"),
|
||||||
ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"),
|
ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unsupported notifier channel")
|
return nil, fmt.Errorf("unsupported notifier channel: %s", channelConfig)
|
||||||
}
|
}
|
||||||
|
24
internal/pkg/core/deployer/deployer.go
Normal file
24
internal/pkg/core/deployer/deployer.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// 表示定义证书部署器的抽象类型接口。
|
||||||
|
// 注意与 `Uploader` 区分,“部署”通常为“上传”的后置操作。
|
||||||
|
type Deployer interface {
|
||||||
|
// 部署证书。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - ctx:上下文。
|
||||||
|
// - certPem:证书 PEM 内容。
|
||||||
|
// - privkeyPem:私钥 PEM 内容。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - res:部署结果。
|
||||||
|
// - err: 错误。
|
||||||
|
Deploy(ctx context.Context, certPem string, privkeyPem string) (res *DeployResult, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示证书部署结果的数据结构。
|
||||||
|
type DeployResult struct {
|
||||||
|
DeploymentData map[string]any `json:"deploymentData,omitempty"`
|
||||||
|
}
|
117
internal/pkg/core/deployer/logger.go
Normal file
117
internal/pkg/core/deployer/logger.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package deployer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 表示定义证书部署器的日志记录器的抽象类型接口。
|
||||||
|
type Logger interface {
|
||||||
|
// 追加一条日志记录。
|
||||||
|
// 该方法会将 `data` 以 JSON 序列化后拼接到 `tag` 结尾。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - tag:标签。
|
||||||
|
// - data:数据。
|
||||||
|
Logt(tag string, data ...any)
|
||||||
|
|
||||||
|
// 追加一条日志记录。
|
||||||
|
// 该方法会将 `args` 以 `format` 格式化。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - format:格式化字符串。
|
||||||
|
// - args:格式化参数。
|
||||||
|
Logf(format string, args ...any)
|
||||||
|
|
||||||
|
// 获取所有日志记录。
|
||||||
|
GetRecords() []string
|
||||||
|
|
||||||
|
// 清空所有日志记录。
|
||||||
|
FlushRecords()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示默认的日志记录器类型。
|
||||||
|
type DefaultLogger struct {
|
||||||
|
records []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Logger = (*DefaultLogger)(nil)
|
||||||
|
|
||||||
|
func (l *DefaultLogger) Logt(tag string, data ...any) {
|
||||||
|
l.ensureInitialized()
|
||||||
|
|
||||||
|
temp := make([]string, len(data)+1)
|
||||||
|
temp[0] = tag
|
||||||
|
for i, v := range data {
|
||||||
|
s := ""
|
||||||
|
if v == nil {
|
||||||
|
s = "<nil>"
|
||||||
|
} else {
|
||||||
|
switch reflect.ValueOf(v).Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
s = v.(string)
|
||||||
|
case reflect.Bool,
|
||||||
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Float32, reflect.Float64:
|
||||||
|
s = fmt.Sprintf("%v", v)
|
||||||
|
default:
|
||||||
|
jsonData, _ := json.Marshal(v)
|
||||||
|
s = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
temp[i+1] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
l.records = append(l.records, strings.Join(temp, ": "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DefaultLogger) Logf(format string, args ...any) {
|
||||||
|
l.ensureInitialized()
|
||||||
|
|
||||||
|
l.records = append(l.records, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DefaultLogger) GetRecords() []string {
|
||||||
|
l.ensureInitialized()
|
||||||
|
|
||||||
|
temp := make([]string, len(l.records))
|
||||||
|
copy(temp, l.records)
|
||||||
|
return temp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DefaultLogger) FlushRecords() {
|
||||||
|
l.records = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DefaultLogger) ensureInitialized() {
|
||||||
|
if l.records == nil {
|
||||||
|
l.records = make([]string, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultLogger() *DefaultLogger {
|
||||||
|
return &DefaultLogger{
|
||||||
|
records: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示空的日志记录器类型。
|
||||||
|
// 该日志记录器不会执行任何操作。
|
||||||
|
type NilLogger struct{}
|
||||||
|
|
||||||
|
var _ Logger = (*NilLogger)(nil)
|
||||||
|
|
||||||
|
func (l *NilLogger) Logt(string, ...any) {}
|
||||||
|
func (l *NilLogger) Logf(string, ...any) {}
|
||||||
|
func (l *NilLogger) GetRecords() []string {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
func (l *NilLogger) FlushRecords() {}
|
||||||
|
|
||||||
|
func NewNilLogger() *NilLogger {
|
||||||
|
return &NilLogger{}
|
||||||
|
}
|
56
internal/pkg/core/deployer/logger_test.go
Normal file
56
internal/pkg/core/deployer/logger_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package deployer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v logger_test.go
|
||||||
|
*/
|
||||||
|
func TestLogger(t *testing.T) {
|
||||||
|
t.Run("Logger_Appendt", func(t *testing.T) {
|
||||||
|
logger := deployer.NewDefaultLogger()
|
||||||
|
|
||||||
|
logger.Logt("test")
|
||||||
|
logger.Logt("test_nil", nil)
|
||||||
|
logger.Logt("test_int", 1024)
|
||||||
|
logger.Logt("test_string", "certimate")
|
||||||
|
logger.Logt("test_map", map[string]interface{}{"key": "value"})
|
||||||
|
logger.Logt("test_struct", struct{ Name string }{Name: "certimate"})
|
||||||
|
logger.Logt("test_slice", []string{"certimate"})
|
||||||
|
t.Log(logger.GetRecords())
|
||||||
|
if len(logger.GetRecords()) != 7 {
|
||||||
|
t.Errorf("expected 7 records, got %d", len(logger.GetRecords()))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.FlushRecords()
|
||||||
|
if len(logger.GetRecords()) != 0 {
|
||||||
|
t.Errorf("expected 0 records, got %d", len(logger.GetRecords()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Logger_Appendf", func(t *testing.T) {
|
||||||
|
logger := deployer.NewDefaultLogger()
|
||||||
|
|
||||||
|
logger.Logf("test")
|
||||||
|
logger.Logf("test_nil: %v", nil)
|
||||||
|
logger.Logf("test_int: %v", 1024)
|
||||||
|
logger.Logf("test_string: %v", "certimate")
|
||||||
|
logger.Logf("test_map: %v", map[string]interface{}{"key": "value"})
|
||||||
|
logger.Logf("test_struct: %v", struct{ Name string }{Name: "certimate"})
|
||||||
|
logger.Logf("test_slice: %v", []string{"certimate"})
|
||||||
|
t.Log(logger.GetRecords())
|
||||||
|
if len(logger.GetRecords()) != 7 {
|
||||||
|
t.Errorf("expected 7 records, got %d", len(logger.GetRecords()))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.FlushRecords()
|
||||||
|
if len(logger.GetRecords()) != 0 {
|
||||||
|
t.Errorf("expected 0 records, got %d", len(logger.GetRecords()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
289
internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go
Normal file
289
internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
package aliyunalb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client"
|
||||||
|
aliyunOpen "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"
|
||||||
|
providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunALBDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 阿里云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 部署资源类型。
|
||||||
|
ResourceType DeployResourceType `json:"resourceType"`
|
||||||
|
// 负载均衡实例 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
|
||||||
|
LoadbalancerId string `json:"loadbalancerId,omitempty"`
|
||||||
|
// 负载均衡监听 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
|
||||||
|
ListenerId string `json:"listenerId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunALBDeployer struct {
|
||||||
|
config *AliyunALBDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *aliyunAlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunALBDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunALBDeployerConfig) (*AliyunALBDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunALBDeployerConfig, logger deployer.Logger) (*AliyunALBDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliyunCasRegion := config.Region
|
||||||
|
if aliyunCasRegion != "" {
|
||||||
|
// 阿里云 CAS 服务接入点是独立于 ALB 服务的
|
||||||
|
// 国内版固定接入点:华东一杭州
|
||||||
|
// 国际版固定接入点:亚太东南一新加坡
|
||||||
|
if !strings.HasPrefix(aliyunCasRegion, "cn-") {
|
||||||
|
aliyunCasRegion = "ap-southeast-1"
|
||||||
|
} else {
|
||||||
|
aliyunCasRegion = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKeyId,
|
||||||
|
AccessKeySecret: config.AccessKeySecret,
|
||||||
|
Region: aliyunCasRegion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunALBDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CAS
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 根据部署资源类型决定部署方式
|
||||||
|
switch d.config.ResourceType {
|
||||||
|
case DEPLOY_RESOURCE_LOADBALANCER:
|
||||||
|
if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LISTENER:
|
||||||
|
if err := d.deployToListener(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute
|
||||||
|
getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(d.config.LoadbalancerId),
|
||||||
|
}
|
||||||
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp)
|
||||||
|
|
||||||
|
// 查询 HTTPS 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("HTTPS"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
listenerIds = append(listenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", listenerIds)
|
||||||
|
|
||||||
|
// 查询 QUIC 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners
|
||||||
|
listListenersPage = 1
|
||||||
|
listListenersToken = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunAlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("QUIC"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
listenerIds = append(listenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds)
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, listenerId := range listenerIds {
|
||||||
|
if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) deployToListener(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error {
|
||||||
|
// 查询监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute
|
||||||
|
getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(cloudListenerId),
|
||||||
|
}
|
||||||
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ALB 监听配置", getListenerAttributeResp)
|
||||||
|
|
||||||
|
// 修改监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute
|
||||||
|
updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(cloudListenerId),
|
||||||
|
Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{
|
||||||
|
CertificateId: tea.String(cloudCertId),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新 ALB 监听配置", updateListenerAttributeResp)
|
||||||
|
|
||||||
|
// TODO: #347
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-endpoint
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "cn-hangzhou-finance":
|
||||||
|
endpoint = "alb.cn-hangzhou.aliyuncs.com"
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String(endpoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunAlb.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package aliyunalb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fRegion string
|
||||||
|
fLoadbalancerId string
|
||||||
|
fListenerId string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNALB_"
|
||||||
|
|
||||||
|
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(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
|
||||||
|
flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_alb_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_REGION="cn-hangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_LOADBALANCERID="your-alb-instance-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id"
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_ToLoadbalancer", 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("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunALBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToListener", 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("LISTENERID: %v", fListenerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunALBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LISTENER,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
})
|
||||||
|
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-alb/defines.go
Normal file
10
internal/pkg/core/deployer/providers/aliyun-alb/defines.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aliyunalb
|
||||||
|
|
||||||
|
type DeployResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 资源类型:部署到指定负载均衡器。
|
||||||
|
DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
|
||||||
|
// 资源类型:部署到指定监听器。
|
||||||
|
DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
|
||||||
|
)
|
@ -0,0 +1,93 @@
|
|||||||
|
package aliyuncdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
|
||||||
|
aliyunOpen "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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunCDNDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 加速域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunCDNDeployer struct {
|
||||||
|
config *AliyunCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *aliyunCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunCDNDeployerConfig) (*AliyunCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunCDNDeployerConfig, logger deployer.Logger) (*AliyunCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 设置 CDN 域名域名证书
|
||||||
|
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
|
||||||
|
setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{
|
||||||
|
DomainName: tea.String(d.config.Domain),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
|
CertType: tea.String("upload"),
|
||||||
|
SSLProtocol: tea.String("on"),
|
||||||
|
SSLPub: tea.String(certPem),
|
||||||
|
SSLPri: tea.String(privkeyPem),
|
||||||
|
}
|
||||||
|
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) {
|
||||||
|
config := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("cdn.aliyuncs.com"),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunCdn.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package aliyuncdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||||
|
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCDN_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("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunCDNDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
291
internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go
Normal file
291
internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
package aliyunclb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/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"
|
||||||
|
providerSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunCLBDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 阿里云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 部署资源类型。
|
||||||
|
ResourceType DeployResourceType `json:"resourceType"`
|
||||||
|
// 负载均衡实例 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时必填。
|
||||||
|
LoadbalancerId string `json:"loadbalancerId,omitempty"`
|
||||||
|
// 负载均衡监听端口。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
|
||||||
|
ListenerPort int32 `json:"listenerPort,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunCLBDeployer struct {
|
||||||
|
config *AliyunCLBDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *aliyunSlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunCLBDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunCLBDeployerConfig) (*AliyunCLBDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunCLBDeployerConfig, logger deployer.Logger) (*AliyunCLBDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSlb.New(&providerSlb.AliyunSLBUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKeyId,
|
||||||
|
AccessKeySecret: config.AccessKeySecret,
|
||||||
|
Region: config.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunCLBDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SLB
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 根据部署资源类型决定部署方式
|
||||||
|
switch d.config.ResourceType {
|
||||||
|
case DEPLOY_RESOURCE_LOADBALANCER:
|
||||||
|
if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LISTENER:
|
||||||
|
if err := d.deployToListener(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerPorts := make([]int32, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute
|
||||||
|
describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{
|
||||||
|
RegionId: tea.String(d.config.Region),
|
||||||
|
LoadBalancerId: tea.String(d.config.LoadbalancerId),
|
||||||
|
}
|
||||||
|
describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp)
|
||||||
|
|
||||||
|
// 查询 HTTPS 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{
|
||||||
|
RegionId: tea.String(d.config.Region),
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("https"),
|
||||||
|
}
|
||||||
|
describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeLoadBalancerListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range describeLoadBalancerListenersResp.Body.Listeners {
|
||||||
|
listenerPorts = append(listenerPorts, *listener.ListenerPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(describeLoadBalancerListenersResp.Body.Listeners) == 0 || describeLoadBalancerListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = describeLoadBalancerListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts)
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, listenerPort := range listenerPorts {
|
||||||
|
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if d.config.ListenerPort == 0 {
|
||||||
|
return errors.New("config `listenerPort` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerPort, cloudCertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloudLoadbalancerId string, cloudListenerPort int32, cloudCertId string) error {
|
||||||
|
// 查询监听配置
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute
|
||||||
|
describeLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.DescribeLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(cloudLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(cloudListenerPort),
|
||||||
|
}
|
||||||
|
describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp)
|
||||||
|
|
||||||
|
// 查询扩展域名
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions
|
||||||
|
describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{
|
||||||
|
RegionId: tea.String(d.config.Region),
|
||||||
|
LoadBalancerId: tea.String(cloudLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(cloudListenerPort),
|
||||||
|
}
|
||||||
|
describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 CLB 扩展域名", describeDomainExtensionsResp)
|
||||||
|
|
||||||
|
// 遍历修改扩展域名
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute
|
||||||
|
//
|
||||||
|
// 这里仅修改跟被替换证书一致的扩展域名
|
||||||
|
if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil {
|
||||||
|
for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension {
|
||||||
|
if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{
|
||||||
|
RegionId: tea.String(d.config.Region),
|
||||||
|
DomainExtensionId: tea.String(*domainExtension.DomainExtensionId),
|
||||||
|
ServerCertificateId: tea.String(cloudCertId),
|
||||||
|
}
|
||||||
|
_, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改监听配置
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute
|
||||||
|
//
|
||||||
|
// 注意修改监听配置要放在修改扩展域名之后
|
||||||
|
setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{
|
||||||
|
RegionId: tea.String(d.config.Region),
|
||||||
|
LoadBalancerId: tea.String(cloudLoadbalancerId),
|
||||||
|
ListenerPort: tea.Int32(cloudListenerPort),
|
||||||
|
ServerCertificateId: tea.String(cloudCertId),
|
||||||
|
}
|
||||||
|
setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint
|
||||||
|
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 := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String(endpoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunSlb.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package aliyunclb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fRegion string
|
||||||
|
fLoadbalancerId string
|
||||||
|
fListenerPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCLB_"
|
||||||
|
|
||||||
|
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(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
|
||||||
|
flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_clb_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_REGION="cn-hangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_LOADBALANCERID="your-clb-instance-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_ToLoadbalancer", 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("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunCLBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToListener", 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("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
fmt.Sprintf("LISTENERPORT: %v", fListenerPort),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunCLBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LISTENER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
ListenerPort: int32(fListenerPort),
|
||||||
|
})
|
||||||
|
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-clb/defines.go
Normal file
10
internal/pkg/core/deployer/providers/aliyun-clb/defines.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aliyunclb
|
||||||
|
|
||||||
|
type DeployResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 资源类型:部署到指定负载均衡器。
|
||||||
|
DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
|
||||||
|
// 资源类型:部署到指定监听器。
|
||||||
|
DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
|
||||||
|
)
|
@ -0,0 +1,97 @@
|
|||||||
|
package aliyundcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunDcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunDCDNDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunDCDNDeployer struct {
|
||||||
|
config *AliyunDCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *aliyunDcdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunDCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunDCDNDeployerConfig) (*AliyunDCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunDCDNDeployerConfig, logger deployer.Logger) (*AliyunDCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunDCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunDCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式
|
||||||
|
domain := strings.TrimPrefix(d.config.Domain, "*")
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate
|
||||||
|
setDcdnDomainSSLCertificateReq := &aliyunDcdn.SetDcdnDomainSSLCertificateRequest{
|
||||||
|
DomainName: tea.String(domain),
|
||||||
|
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
||||||
|
CertType: tea.String("upload"),
|
||||||
|
SSLProtocol: tea.String("on"),
|
||||||
|
SSLPub: tea.String(certPem),
|
||||||
|
SSLPri: tea.String(privkeyPem),
|
||||||
|
}
|
||||||
|
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) {
|
||||||
|
config := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String("dcdn.aliyuncs.com"),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunDcdn.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package aliyundcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||||
|
flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_dcdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNDCDN_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("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunDCDNDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
251
internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go
Normal file
251
internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package aliyunnlb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
aliyunNlb "github.com/alibabacloud-go/nlb-20220430/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"
|
||||||
|
providerCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunNLBDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 阿里云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 部署资源类型。
|
||||||
|
ResourceType DeployResourceType `json:"resourceType"`
|
||||||
|
// 负载均衡实例 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
|
||||||
|
LoadbalancerId string `json:"loadbalancerId,omitempty"`
|
||||||
|
// 负载均衡监听 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
|
||||||
|
ListenerId string `json:"listenerId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunNLBDeployer struct {
|
||||||
|
config *AliyunNLBDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *aliyunNlb.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunNLBDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunNLBDeployerConfig) (*AliyunNLBDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunNLBDeployerConfig, logger deployer.Logger) (*AliyunNLBDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliyunCasRegion := config.Region
|
||||||
|
if aliyunCasRegion != "" {
|
||||||
|
// 阿里云 CAS 服务接入点是独立于 NLB 服务的
|
||||||
|
// 国内版固定接入点:华东一杭州
|
||||||
|
// 国际版固定接入点:亚太东南一新加坡
|
||||||
|
if !strings.HasPrefix(aliyunCasRegion, "cn-") {
|
||||||
|
aliyunCasRegion = "ap-southeast-1"
|
||||||
|
} else {
|
||||||
|
aliyunCasRegion = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploader, err := providerCas.New(&providerCas.AliyunCASUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKeyId,
|
||||||
|
AccessKeySecret: config.AccessKeySecret,
|
||||||
|
Region: aliyunCasRegion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunNLBDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CAS
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 根据部署资源类型决定部署方式
|
||||||
|
switch d.config.ResourceType {
|
||||||
|
case DEPLOY_RESOURCE_LOADBALANCER:
|
||||||
|
if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LISTENER:
|
||||||
|
if err := d.deployToListener(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡实例的详细信息
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute
|
||||||
|
getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{
|
||||||
|
LoadBalancerId: tea.String(d.config.LoadbalancerId),
|
||||||
|
}
|
||||||
|
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp)
|
||||||
|
|
||||||
|
// 查询 TCPSSL 监听列表
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners
|
||||||
|
listListenersPage := 1
|
||||||
|
listListenersLimit := int32(100)
|
||||||
|
var listListenersToken *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &aliyunNlb.ListListenersRequest{
|
||||||
|
MaxResults: tea.Int32(listListenersLimit),
|
||||||
|
NextToken: listListenersToken,
|
||||||
|
LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)},
|
||||||
|
ListenerProtocol: tea.String("TCPSSL"),
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Body.Listeners != nil {
|
||||||
|
for _, listener := range listListenersResp.Body.Listeners {
|
||||||
|
listenerIds = append(listenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listListenersResp.Body.Listeners) == 0 || listListenersResp.Body.NextToken == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersToken = listListenersResp.Body.NextToken
|
||||||
|
listListenersPage += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds)
|
||||||
|
|
||||||
|
// 批量更新监听证书
|
||||||
|
var errs []error
|
||||||
|
for _, listenerId := range listenerIds {
|
||||||
|
if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新监听
|
||||||
|
if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error {
|
||||||
|
// 查询监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute
|
||||||
|
getListenerAttributeReq := &aliyunNlb.GetListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(cloudListenerId),
|
||||||
|
}
|
||||||
|
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 NLB 监听配置", getListenerAttributeResp)
|
||||||
|
|
||||||
|
// 修改监听的属性
|
||||||
|
// REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute
|
||||||
|
updateListenerAttributeReq := &aliyunNlb.UpdateListenerAttributeRequest{
|
||||||
|
ListenerId: tea.String(cloudListenerId),
|
||||||
|
CertificateIds: []*string{tea.String(cloudCertId)},
|
||||||
|
}
|
||||||
|
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新 NLB 监听配置", updateListenerAttributeResp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接入点一览 https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-endpoint
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &aliyunOpen.Config{
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
Endpoint: tea.String(endpoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunNlb.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package aliyunnlb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fRegion string
|
||||||
|
fLoadbalancerId string
|
||||||
|
fListenerId string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNNLB_"
|
||||||
|
|
||||||
|
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(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
|
||||||
|
flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_nlb_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_REGION="cn-hangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_LOADBALANCERID="your-nlb-instance-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNNLB_LISTENERID="your-nlb-listener-id"
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_ToLoadbalancer", 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("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunNLBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToListener", 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("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
fmt.Sprintf("LISTENERID: %v", fListenerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunNLBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LISTENER,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
})
|
||||||
|
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-nlb/defines.go
Normal file
10
internal/pkg/core/deployer/providers/aliyun-nlb/defines.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package aliyunnlb
|
||||||
|
|
||||||
|
type DeployResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 资源类型:部署到指定负载均衡器。
|
||||||
|
DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
|
||||||
|
// 资源类型:部署到指定监听器。
|
||||||
|
DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
|
||||||
|
)
|
112
internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go
Normal file
112
internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package aliyunoss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunOSSDeployerConfig struct {
|
||||||
|
// 阿里云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 阿里云 AccessKeySecret。
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
// 阿里云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 存储桶名。
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
// 自定义域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliyunOSSDeployer struct {
|
||||||
|
config *AliyunOSSDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *oss.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*AliyunOSSDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *AliyunOSSDeployerConfig) (*AliyunOSSDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *AliyunOSSDeployerConfig, logger deployer.Logger) (*AliyunOSSDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AliyunOSSDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyunOSSDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.Bucket == "" {
|
||||||
|
return nil, errors.New("config `bucket` is required")
|
||||||
|
}
|
||||||
|
if d.config.Domain == "" {
|
||||||
|
return nil, errors.New("config `domain` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为存储空间绑定自定义域名
|
||||||
|
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
|
||||||
|
err := d.sdkClient.PutBucketCnameWithCertificate(d.config.Bucket, oss.PutBucketCname{
|
||||||
|
Cname: d.config.Domain,
|
||||||
|
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||||
|
Certificate: certPem,
|
||||||
|
PrivateKey: privkeyPem,
|
||||||
|
Force: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) {
|
||||||
|
// 接入点一览 https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints
|
||||||
|
var endpoint string
|
||||||
|
switch region {
|
||||||
|
case "":
|
||||||
|
endpoint = "oss.aliyuncs.com"
|
||||||
|
case
|
||||||
|
"cn-hzjbp",
|
||||||
|
"cn-hzjbp-a",
|
||||||
|
"cn-hzjbp-b":
|
||||||
|
endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com"
|
||||||
|
case
|
||||||
|
"cn-shanghai-finance-1",
|
||||||
|
"cn-shenzhen-finance-1",
|
||||||
|
"cn-beijing-finance-1",
|
||||||
|
"cn-north-2-gov-1":
|
||||||
|
endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region)
|
||||||
|
default:
|
||||||
|
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package aliyunoss_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fAccessKeySecret string
|
||||||
|
fRegion string
|
||||||
|
fBucket string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNOSS_"
|
||||||
|
|
||||||
|
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(&fBucket, argsPrefix+"BUCKET", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v aliyun_oss_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYSECRET="your-access-key-secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-oss-bucket" \
|
||||||
|
--CERTIMATE_DEPLOYER_ALIYUNOSS_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("BUCKET: %v", fBucket),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.AliyunOSSDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
AccessKeySecret: fAccessKeySecret,
|
||||||
|
Region: fRegion,
|
||||||
|
Bucket: fBucket,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package baiducloudcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
bceCdn "github.com/baidubce/bce-sdk-go/services/cdn"
|
||||||
|
bceCdnApi "github.com/baidubce/bce-sdk-go/services/cdn/api"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaiduCloudCDNDeployerConfig struct {
|
||||||
|
// 百度智能云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 百度智能云 SecretAccessKey。
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
// 加速域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaiduCloudCDNDeployer struct {
|
||||||
|
config *BaiduCloudCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *bceCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*BaiduCloudCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *BaiduCloudCDNDeployerConfig) (*BaiduCloudCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *BaiduCloudCDNDeployerConfig, logger deployer.Logger) (*BaiduCloudCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BaiduCloudCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 修改域名证书
|
||||||
|
// REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8
|
||||||
|
putCertResp, err := d.sdkClient.PutCert(
|
||||||
|
d.config.Domain,
|
||||||
|
&bceCdnApi.UserCertificate{
|
||||||
|
CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
||||||
|
ServerData: certPem,
|
||||||
|
PrivateData: privkeyPem,
|
||||||
|
},
|
||||||
|
"ON",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已修改域名证书", putCertResp)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey string) (*bceCdn.Client, error) {
|
||||||
|
client, err := bceCdn.NewClient(accessKeyId, secretAccessKey, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package baiducloudcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fSecretAccessKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||||
|
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v baiducloud_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_BAIDUCLOUDCDN_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("SECRETACCESSKEY: %v", fSecretAccessKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.BaiduCloudCDNDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
SecretAccessKey: fSecretAccessKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package bytepluscdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
bpCdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BytePlusCDNDeployerConfig struct {
|
||||||
|
// BytePlus AccessKey。
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
// BytePlus SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BytePlusCDNDeployer struct {
|
||||||
|
config *BytePlusCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *bpCdn.CDN
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*BytePlusCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *BytePlusCDNDeployerConfig) (*BytePlusCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *BytePlusCDNDeployerConfig, logger deployer.Logger) (*BytePlusCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := bpCdn.NewInstance()
|
||||||
|
client.Client.SetAccessKey(config.AccessKey)
|
||||||
|
client.Client.SetSecretKey(config.SecretKey)
|
||||||
|
|
||||||
|
uploader, err := providerCdn.New(&providerCdn.ByteplusCDNUploaderConfig{
|
||||||
|
AccessKey: config.AccessKey,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BytePlusCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BytePlusCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CDN
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||||
|
// 获取指定证书可关联的域名
|
||||||
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17
|
||||||
|
describeCertConfigReq := &bpCdn.DescribeCertConfigRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
}
|
||||||
|
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeCertConfigResp.Result.CertNotConfig != nil {
|
||||||
|
for i := range describeCertConfigResp.Result.CertNotConfig {
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeCertConfigResp.Result.OtherCertConfig != nil {
|
||||||
|
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) == 0 {
|
||||||
|
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
||||||
|
// 所有可关联的域名都配置了该证书,跳过部署
|
||||||
|
} else {
|
||||||
|
return nil, xerrors.New("domain not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = append(domains, d.config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
// 关联证书与加速域名
|
||||||
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert
|
||||||
|
batchDeployCertReq := &bpCdn.BatchDeployCertRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
d.logger.Logt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package bytepluscdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKey string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_BYTEPLUSCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v byteplus_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_BYTEPLUSCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_BYTEPLUSCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_BYTEPLUSCDN_ACCESSKEY="your-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_BYTEPLUSCDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_BYTEPLUSCDN_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("ACCESSKEY: %v", fAccessKey),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.BytePlusCDNDeployerConfig{
|
||||||
|
AccessKey: fAccessKey,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package dogecloudcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
|
||||||
|
dogesdk "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DogeCloudCDNDeployerConfig struct {
|
||||||
|
// 多吉云 AccessKey。
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
// 多吉云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DogeCloudCDNDeployer struct {
|
||||||
|
config *DogeCloudCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *dogesdk.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*DogeCloudCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *DogeCloudCDNDeployerConfig) (*DogeCloudCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *DogeCloudCDNDeployerConfig, logger deployer.Logger) (*DogeCloudCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := dogesdk.NewClient(config.AccessKey, config.SecretKey)
|
||||||
|
|
||||||
|
uploader, err := providerDoge.New(&providerDoge.DogeCloudUploaderConfig{
|
||||||
|
AccessKey: config.AccessKey,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DogeCloudCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CDN
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 绑定证书
|
||||||
|
// REF: https://docs.dogecloud.com/cdn/api-cert-bind
|
||||||
|
bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
|
||||||
|
bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.config.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已绑定证书", bindCdnCertResp)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package dogecloudcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKey string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_DOGECLOUDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v dogecloud_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_DOGECLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_DOGECLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_DOGECLOUDCDN_ACCESSKEY="your-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_DOGECLOUDCDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_DOGECLOUDCDN_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("ACCESSKEY: %v", fAccessKey),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.DogeCloudCDNDeployerConfig{
|
||||||
|
AccessKey: fAccessKey,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package huaweicloudcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
|
||||||
|
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
|
||||||
|
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
|
||||||
|
hcCdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
huaweicloudsdk "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudCDNDeployerConfig struct {
|
||||||
|
// 华为云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 华为云 SecretAccessKey。
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
// 华为云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 加速域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HuaweiCloudCDNDeployer struct {
|
||||||
|
config *HuaweiCloudCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *huaweicloudsdk.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*HuaweiCloudCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *HuaweiCloudCDNDeployerConfig) (*HuaweiCloudCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *HuaweiCloudCDNDeployerConfig, logger deployer.Logger) (*HuaweiCloudCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(
|
||||||
|
config.AccessKeyId,
|
||||||
|
config.SecretAccessKey,
|
||||||
|
config.Region,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerScm.New(&providerScm.HuaweiCloudSCMUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKeyId,
|
||||||
|
SecretAccessKey: config.SecretAccessKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HuaweiCloudCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 查询加速域名配置
|
||||||
|
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
||||||
|
showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
|
||||||
|
DomainName: d.config.Domain,
|
||||||
|
}
|
||||||
|
showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到加速域名配置", showDomainFullConfigResp)
|
||||||
|
|
||||||
|
// 更新加速域名配置
|
||||||
|
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
||||||
|
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
|
||||||
|
updateDomainMultiCertificatesReqBodyContent := &huaweicloudsdk.UpdateDomainMultiCertificatesExRequestBodyContent{}
|
||||||
|
updateDomainMultiCertificatesReqBodyContent.DomainName = d.config.Domain
|
||||||
|
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
|
||||||
|
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
|
||||||
|
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(upres.CertId)
|
||||||
|
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(upres.CertName)
|
||||||
|
updateDomainMultiCertificatesReqBodyContent = updateDomainMultiCertificatesReqBodyContent.MergeConfig(showDomainFullConfigResp.Configs)
|
||||||
|
updateDomainMultiCertificatesReq := &huaweicloudsdk.UpdateDomainMultiCertificatesExRequest{
|
||||||
|
Body: &huaweicloudsdk.UpdateDomainMultiCertificatesExRequestBody{
|
||||||
|
Https: updateDomainMultiCertificatesReqBodyContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新加速域名配置", updateDomainMultiCertificatesResp)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*huaweicloudsdk.Client, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-1" // CDN 服务默认区域:华北一北京
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := global.NewCredentialsBuilder().
|
||||||
|
WithAk(accessKeyId).
|
||||||
|
WithSk(secretAccessKey).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcRegion, err := hcCdnRegion.SafeValueOf(region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hcClient, err := hcCdn.CdnClientBuilder().
|
||||||
|
WithRegion(hcRegion).
|
||||||
|
WithCredential(auth).
|
||||||
|
SafeBuild()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := huaweicloudsdk.NewClient(hcClient)
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package huaweicloudcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fSecretAccessKey string
|
||||||
|
fRegion string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||||
|
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v huaweicloud_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_REGION="cn-north-1" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDCDN_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("SECRETACCESSKEY: %v", fSecretAccessKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.HuaweiCloudCDNDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
SecretAccessKey: fSecretAccessKey,
|
||||||
|
Region: fRegion,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package huaweicloudelb
|
||||||
|
|
||||||
|
type DeployResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 资源类型:替换指定证书。
|
||||||
|
DEPLOY_RESOURCE_CERTIFICATE = DeployResourceType("certificate")
|
||||||
|
// 资源类型:部署到指定负载均衡器。
|
||||||
|
DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
|
||||||
|
// 资源类型:部署到指定监听器。
|
||||||
|
DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
|
||||||
|
)
|
@ -0,0 +1,395 @@
|
|||||||
|
package huaweicloudelb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HuaweiCloudELBDeployerConfig struct {
|
||||||
|
// 华为云 AccessKeyId。
|
||||||
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
|
// 华为云 SecretAccessKey。
|
||||||
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
// 华为云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 部署资源类型。
|
||||||
|
ResourceType DeployResourceType `json:"resourceType"`
|
||||||
|
// 证书 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_CERTIFICATE] 时必填。
|
||||||
|
CertificateId string `json:"certificateId,omitempty"`
|
||||||
|
// 负载均衡器 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。
|
||||||
|
LoadbalancerId string `json:"loadbalancerId,omitempty"`
|
||||||
|
// 负载均衡监听 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。
|
||||||
|
ListenerId string `json:"listenerId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HuaweiCloudELBDeployer struct {
|
||||||
|
config *HuaweiCloudELBDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *hcElb.ElbClient
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*HuaweiCloudELBDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *HuaweiCloudELBDeployerConfig) (*HuaweiCloudELBDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *HuaweiCloudELBDeployerConfig, logger deployer.Logger) (*HuaweiCloudELBDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerElb.New(&providerElb.HuaweiCloudELBUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKeyId,
|
||||||
|
SecretAccessKey: config.SecretAccessKey,
|
||||||
|
Region: config.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HuaweiCloudELBDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 根据部署资源类型决定部署方式
|
||||||
|
switch d.config.ResourceType {
|
||||||
|
case DEPLOY_RESOURCE_CERTIFICATE:
|
||||||
|
if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LOADBALANCER:
|
||||||
|
if err := d.deployToLoadbalancer(ctx, certPem, privkeyPem); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LISTENER:
|
||||||
|
if err := d.deployToListener(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 *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error {
|
||||||
|
if d.config.CertificateId == "" {
|
||||||
|
return errors.New("config `certificateId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新证书
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/UpdateCertificate.html
|
||||||
|
updateCertificateReq := &hcElbModel.UpdateCertificateRequest{
|
||||||
|
CertificateId: d.config.CertificateId,
|
||||||
|
Body: &hcElbModel.UpdateCertificateRequestBody{
|
||||||
|
Certificate: &hcElbModel.UpdateCertificateOption{
|
||||||
|
Certificate: cast.StringPtr(certPem),
|
||||||
|
PrivateKey: cast.StringPtr(privkeyPem),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新 ELB 证书", updateCertificateResp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context, certPem string, privkeyPem string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询负载均衡器详情
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ShowLoadBalancer.html
|
||||||
|
showLoadBalancerReq := &hcElbModel.ShowLoadBalancerRequest{
|
||||||
|
LoadbalancerId: d.config.LoadbalancerId,
|
||||||
|
}
|
||||||
|
showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ELB 负载均衡器", showLoadBalancerResp)
|
||||||
|
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ListListeners.html
|
||||||
|
listListenersLimit := int32(2000)
|
||||||
|
var listListenersMarker *string = nil
|
||||||
|
for {
|
||||||
|
listListenersReq := &hcElbModel.ListListenersRequest{
|
||||||
|
Limit: cast.Int32Ptr(listListenersLimit),
|
||||||
|
Marker: listListenersMarker,
|
||||||
|
Protocol: &[]string{"HTTPS", "TERMINATED_HTTPS"},
|
||||||
|
LoadbalancerId: &[]string{showLoadBalancerResp.Loadbalancer.Id},
|
||||||
|
}
|
||||||
|
listListenersResp, err := d.sdkClient.ListListeners(listListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListListeners'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Listeners != nil {
|
||||||
|
for _, listener := range *listListenersResp.Listeners {
|
||||||
|
listenerIds = append(listenerIds, listener.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listListenersMarker = listListenersResp.PageInfo.NextMarker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ELB 负载均衡器下的监听器", listenerIds)
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 批量更新监听器证书
|
||||||
|
var errs []error
|
||||||
|
for _, listenerId := range listenerIds {
|
||||||
|
if err := d.modifyListenerCertificate(ctx, listenerId, upres.CertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) deployToListener(ctx context.Context, certPem string, privkeyPem string) error {
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SCM
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 更新监听器证书
|
||||||
|
if err := d.modifyListenerCertificate(ctx, d.config.ListenerId, upres.CertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error {
|
||||||
|
// 查询监听器详情
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
|
||||||
|
showListenerReq := &hcElbModel.ShowListenerRequest{
|
||||||
|
ListenerId: cloudListenerId,
|
||||||
|
}
|
||||||
|
showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到 ELB 监听器", showListenerResp)
|
||||||
|
|
||||||
|
// 更新监听器
|
||||||
|
// REF: https://support.huaweicloud.com/api-elb/UpdateListener.html
|
||||||
|
updateListenerReq := &hcElbModel.UpdateListenerRequest{
|
||||||
|
ListenerId: cloudListenerId,
|
||||||
|
Body: &hcElbModel.UpdateListenerRequestBody{
|
||||||
|
Listener: &hcElbModel.UpdateListenerOption{
|
||||||
|
DefaultTlsContainerRef: cast.StringPtr(cloudCertId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if showListenerResp.Listener.SniContainerRefs != nil {
|
||||||
|
if len(showListenerResp.Listener.SniContainerRefs) > 0 {
|
||||||
|
// 如果开启 SNI,需替换同 SAN 的证书
|
||||||
|
sniCertIds := make([]string, 0)
|
||||||
|
sniCertIds = append(sniCertIds, cloudCertId)
|
||||||
|
|
||||||
|
listOldCertificateReq := &hcElbModel.ListCertificatesRequest{
|
||||||
|
Id: &showListenerResp.Listener.SniContainerRefs,
|
||||||
|
}
|
||||||
|
listOldCertificateResp, err := d.sdkClient.ListCertificates(listOldCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ListCertificates'")
|
||||||
|
}
|
||||||
|
|
||||||
|
showNewCertificateReq := &hcElbModel.ShowCertificateRequest{
|
||||||
|
CertificateId: cloudCertId,
|
||||||
|
}
|
||||||
|
showNewCertificateResp, err := d.sdkClient.ShowCertificate(showNewCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, certificate := range *listOldCertificateResp.Certificates {
|
||||||
|
oldCertificate := certificate
|
||||||
|
newCertificate := showNewCertificateResp.Certificate
|
||||||
|
|
||||||
|
if oldCertificate.SubjectAlternativeNames != nil && newCertificate.SubjectAlternativeNames != nil {
|
||||||
|
if slices.Equal(*oldCertificate.SubjectAlternativeNames, *newCertificate.SubjectAlternativeNames) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if oldCertificate.Domain == newCertificate.Domain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sniCertIds = append(sniCertIds, certificate.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateListenerReq.Body.Listener.SniContainerRefs = &sniCertIds
|
||||||
|
}
|
||||||
|
|
||||||
|
if showListenerResp.Listener.SniMatchAlgo != "" {
|
||||||
|
updateListenerReq.Body.Listener.SniMatchAlgo = cast.StringPtr(showListenerResp.Listener.SniMatchAlgo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateListenerResp, err := d.sdkClient.UpdateListener(updateListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已更新 ELB 监听器", updateListenerResp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
||||||
|
if region == "" {
|
||||||
|
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := 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,155 @@
|
|||||||
|
package huaweicloudelb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKeyId string
|
||||||
|
fSecretAccessKey string
|
||||||
|
fRegion string
|
||||||
|
fCertificateId string
|
||||||
|
fLoadbalancerId string
|
||||||
|
fListenerId string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_HUAWEICLOUDELB_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
|
||||||
|
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
|
||||||
|
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
|
||||||
|
flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
|
||||||
|
flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v huaweicloud_elb_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_ACCESSKEYID="your-access-key-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_SECRETACCESSKEY="your-secret-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_REGION="cn-north-1" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_CERTIFICATEID="your-elb-cert-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LOADBALANCERID="your-elb-loadbalancer-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_HUAWEICLOUDELB_LISTENERID="your-elb-listener-id"
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_ToCertificate", 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("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
SecretAccessKey: fSecretAccessKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_CERTIFICATE,
|
||||||
|
CertificateId: fCertificateId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToLoadbalancer", 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("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
SecretAccessKey: fSecretAccessKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToListenerId", 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("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LISTENERID: %v", fListenerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.HuaweiCloudELBDeployerConfig{
|
||||||
|
AccessKeyId: fAccessKeyId,
|
||||||
|
SecretAccessKey: fSecretAccessKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LISTENER,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
160
internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go
Normal file
160
internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package k8ssecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
k8sCore "k8s.io/api/core/v1"
|
||||||
|
k8sMeta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type K8sSecretDeployerConfig struct {
|
||||||
|
// kubeconfig 文件内容。
|
||||||
|
KubeConfig string `json:"kubeConfig,omitempty"`
|
||||||
|
// K8s 命名空间。
|
||||||
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
// K8s Secret 名称。
|
||||||
|
SecretName string `json:"secretName"`
|
||||||
|
// K8s Secret 中用于存放证书的 Key。
|
||||||
|
SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"`
|
||||||
|
// K8s Secret 中用于存放私钥的 Key。
|
||||||
|
SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type K8sSecretDeployer struct {
|
||||||
|
config *K8sSecretDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*K8sSecretDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *K8sSecretDeployerConfig) (*K8sSecretDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *K8sSecretDeployerConfig, logger deployer.Logger) (*K8sSecretDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &K8sSecretDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.Namespace == "" {
|
||||||
|
return nil, errors.New("config `namespace` is required")
|
||||||
|
}
|
||||||
|
if d.config.SecretName == "" {
|
||||||
|
return nil, errors.New("config `secretName` is required")
|
||||||
|
}
|
||||||
|
if d.config.SecretDataKeyForCrt == "" {
|
||||||
|
return nil, errors.New("config `secretDataKeyForCrt` is required")
|
||||||
|
}
|
||||||
|
if d.config.SecretDataKeyForKey == "" {
|
||||||
|
return nil, errors.New("config `secretDataKeyForKey` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接
|
||||||
|
client, err := createK8sClient(d.config.KubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create k8s client")
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretPayload *k8sCore.Secret
|
||||||
|
secretAnnotations := map[string]string{
|
||||||
|
"certimate/common-name": certX509.Subject.CommonName,
|
||||||
|
"certimate/subject-sn": certX509.Subject.SerialNumber,
|
||||||
|
"certimate/subject-alt-names": strings.Join(certX509.DNSNames, ","),
|
||||||
|
"certimate/issuer-sn": certX509.Issuer.SerialNumber,
|
||||||
|
"certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 Secret 实例,如果不存在则创建
|
||||||
|
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8sMeta.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
secretPayload = &k8sCore.Secret{
|
||||||
|
TypeMeta: k8sMeta.TypeMeta{
|
||||||
|
Kind: "Secret",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: k8sMeta.ObjectMeta{
|
||||||
|
Name: d.config.SecretName,
|
||||||
|
Annotations: secretAnnotations,
|
||||||
|
},
|
||||||
|
Type: k8sCore.SecretType("kubernetes.io/tls"),
|
||||||
|
}
|
||||||
|
secretPayload.Data = make(map[string][]byte)
|
||||||
|
secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPem)
|
||||||
|
secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPem)
|
||||||
|
|
||||||
|
_, err = client.CoreV1().Secrets(d.config.Namespace).Create(context.TODO(), secretPayload, k8sMeta.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create k8s secret")
|
||||||
|
} else {
|
||||||
|
d.logger.Logf("k8s secret created", secretPayload)
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Secret 实例
|
||||||
|
secretPayload.Type = k8sCore.SecretType("kubernetes.io/tls")
|
||||||
|
if secretPayload.ObjectMeta.Annotations == nil {
|
||||||
|
secretPayload.ObjectMeta.Annotations = secretAnnotations
|
||||||
|
} else {
|
||||||
|
for k, v := range secretAnnotations {
|
||||||
|
secretPayload.ObjectMeta.Annotations[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8sMeta.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to update k8s secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logf("k8s secret updated", secretPayload)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createK8sClient(kubeConfig string) (*kubernetes.Clientset, error) {
|
||||||
|
var config *rest.Config
|
||||||
|
var err error
|
||||||
|
if kubeConfig == "" {
|
||||||
|
config, err = rest.InClusterConfig()
|
||||||
|
} else {
|
||||||
|
kubeConfig, err := clientcmd.NewClientConfigFromBytes([]byte(kubeConfig))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config, err = kubeConfig.ClientConfig()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package k8ssecret_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fNamespace string
|
||||||
|
fSecretName string
|
||||||
|
fSecretDataKeyForCrt string
|
||||||
|
fSecretDataKeyForKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_K8SSECRET_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fNamespace, argsPrefix+"NAMESPACE", "default", "")
|
||||||
|
flag.StringVar(&fSecretName, argsPrefix+"SECRETNAME", "", "")
|
||||||
|
flag.StringVar(&fSecretDataKeyForCrt, argsPrefix+"SECRETDATAKEYFORCRT", "tls.crt", "")
|
||||||
|
flag.StringVar(&fSecretDataKeyForKey, argsPrefix+"SECRETDATAKEYFORKEY", "tls.key", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v k8s_secret_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_NAMESPACE="default" \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_SECRETNAME="secret" \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORCRT="tls.crt" \
|
||||||
|
--CERTIMATE_DEPLOYER_K8SSECRET_SECRETDATAKEYFORKEY="tls.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("NAMESPACE: %v", fNamespace),
|
||||||
|
fmt.Sprintf("SECRETNAME: %v", fSecretName),
|
||||||
|
fmt.Sprintf("SECRETDATAKEYFORCRT: %v", fSecretDataKeyForCrt),
|
||||||
|
fmt.Sprintf("SECRETDATAKEYFORKEY: %v", fSecretDataKeyForKey),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.K8sSecretDeployerConfig{
|
||||||
|
Namespace: fNamespace,
|
||||||
|
SecretName: fSecretName,
|
||||||
|
SecretDataKeyForCrt: fSecretDataKeyForCrt,
|
||||||
|
SecretDataKeyForKey: fSecretDataKeyForKey,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
17
internal/pkg/core/deployer/providers/local/defines.go
Normal file
17
internal/pkg/core/deployer/providers/local/defines.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
type OutputFormatType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OUTPUT_FORMAT_PEM = OutputFormatType("PEM")
|
||||||
|
OUTPUT_FORMAT_PFX = OutputFormatType("PFX")
|
||||||
|
OUTPUT_FORMAT_JKS = OutputFormatType("JKS")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShellEnvType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SHELL_ENV_SH = ShellEnvType("sh")
|
||||||
|
SHELL_ENV_CMD = ShellEnvType("cmd")
|
||||||
|
SHELL_ENV_POWERSHELL = ShellEnvType("powershell")
|
||||||
|
)
|
178
internal/pkg/core/deployer/providers/local/local.go
Normal file
178
internal/pkg/core/deployer/providers/local/local.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocalDeployerConfig struct {
|
||||||
|
// Shell 执行环境。
|
||||||
|
// 零值时默认根据操作系统决定。
|
||||||
|
ShellEnv ShellEnvType `json:"shellEnv,omitempty"`
|
||||||
|
// 前置命令。
|
||||||
|
PreCommand string `json:"preCommand,omitempty"`
|
||||||
|
// 后置命令。
|
||||||
|
PostCommand string `json:"postCommand,omitempty"`
|
||||||
|
// 输出证书格式。
|
||||||
|
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
||||||
|
// 输出证书文件路径。
|
||||||
|
OutputCertPath string `json:"outputCertPath,omitempty"`
|
||||||
|
// 输出私钥文件路径。
|
||||||
|
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||||
|
// PFX 导出密码。
|
||||||
|
// 证书格式为 PFX 时必填。
|
||||||
|
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||||
|
// JKS 别名。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksAlias string `json:"jksAlias,omitempty"`
|
||||||
|
// JKS 密钥密码。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksKeypass string `json:"jksKeypass,omitempty"`
|
||||||
|
// JKS 存储密码。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksStorepass string `json:"jksStorepass,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalDeployer struct {
|
||||||
|
config *LocalDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*LocalDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *LocalDeployerConfig) (*LocalDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *LocalDeployerConfig, logger deployer.Logger) (*LocalDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LocalDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 执行前置命令
|
||||||
|
if d.config.PreCommand != "" {
|
||||||
|
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("pre-command executed", stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入证书和私钥文件
|
||||||
|
switch d.config.OutputFormat {
|
||||||
|
case OUTPUT_FORMAT_PEM:
|
||||||
|
if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to save certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file saved")
|
||||||
|
|
||||||
|
if err := fs.WriteFileString(d.config.OutputKeyPath, privkeyPem); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to save private key file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("private key file saved")
|
||||||
|
|
||||||
|
case OUTPUT_FORMAT_PFX:
|
||||||
|
pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate transformed to PFX")
|
||||||
|
|
||||||
|
if err := fs.WriteFile(d.config.OutputCertPath, pfxData); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to save certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file saved")
|
||||||
|
|
||||||
|
case OUTPUT_FORMAT_JKS:
|
||||||
|
jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate transformed to JKS")
|
||||||
|
|
||||||
|
if err := fs.WriteFile(d.config.OutputCertPath, jksData); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to save certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行后置命令
|
||||||
|
if d.config.PostCommand != "" {
|
||||||
|
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PostCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("post-command executed", stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCommand(shellEnv ShellEnvType, command string) (string, string, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
|
switch shellEnv {
|
||||||
|
case SHELL_ENV_SH:
|
||||||
|
cmd = exec.Command("sh", "-c", command)
|
||||||
|
|
||||||
|
case SHELL_ENV_CMD:
|
||||||
|
cmd = exec.Command("cmd", "/C", command)
|
||||||
|
|
||||||
|
case SHELL_ENV_POWERSHELL:
|
||||||
|
cmd = exec.Command("powershell", "-Command", command)
|
||||||
|
|
||||||
|
case "":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd = exec.Command("cmd", "/C", command)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("sh", "-c", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("unsupported shell env: %s", shellEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdoutBuf bytes.Buffer
|
||||||
|
cmd.Stdout = &stdoutBuf
|
||||||
|
var stderrBuf bytes.Buffer
|
||||||
|
cmd.Stderr = &stderrBuf
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", xerrors.Wrap(err, "failed to execute shell command")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
|
}
|
186
internal/pkg/core/deployer/providers/local/local_test.go
Normal file
186
internal/pkg/core/deployer/providers/local/local_test.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package local_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fOutputCertPath string
|
||||||
|
fOutputKeyPath string
|
||||||
|
fPfxPassword string
|
||||||
|
fJksAlias string
|
||||||
|
fJksKeypass string
|
||||||
|
fJksStorepass string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_LOCAL_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fOutputKeyPath, argsPrefix+"OUTPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fPfxPassword, argsPrefix+"PFXPASSWORD", "", "")
|
||||||
|
flag.StringVar(&fJksAlias, argsPrefix+"JKSALIAS", "", "")
|
||||||
|
flag.StringVar(&fJksKeypass, argsPrefix+"JKSKEYPASS", "", "")
|
||||||
|
flag.StringVar(&fJksStorepass, argsPrefix+"JKSSTOREPASS", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v local_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_OUTPUTCERTPATH="/path/to/your-output-cert" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_OUTPUTKEYPATH="/path/to/your-output-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_PFXPASSWORD="your-pfx-password" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_JKSALIAS="your-jks-alias" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_JKSKEYPASS="your-jks-keypass" \
|
||||||
|
--CERTIMATE_DEPLOYER_LOCAL_JKSSTOREPASS="your-jks-storepass"
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_PEM", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath),
|
||||||
|
fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.LocalDeployerConfig{
|
||||||
|
OutputCertPath: fOutputCertPath,
|
||||||
|
OutputKeyPath: fOutputKeyPath,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fstat1, err := os.Stat(fOutputCertPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
} else if fstat1.Size() == 0 {
|
||||||
|
t.Errorf("err: empty output certificate file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fstat2, err := os.Stat(fOutputKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
} else if fstat2.Size() == 0 {
|
||||||
|
t.Errorf("err: empty output private key file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_PFX", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath),
|
||||||
|
fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath),
|
||||||
|
fmt.Sprintf("PFXPASSWORD: %v", fPfxPassword),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.LocalDeployerConfig{
|
||||||
|
OutputFormat: provider.OUTPUT_FORMAT_PFX,
|
||||||
|
OutputCertPath: fOutputCertPath,
|
||||||
|
OutputKeyPath: fOutputKeyPath,
|
||||||
|
PfxPassword: fPfxPassword,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fstat, err := os.Stat(fOutputCertPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
} else if fstat.Size() == 0 {
|
||||||
|
t.Errorf("err: empty output certificate file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_JKS", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath),
|
||||||
|
fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath),
|
||||||
|
fmt.Sprintf("JKSALIAS: %v", fJksAlias),
|
||||||
|
fmt.Sprintf("JKSKEYPASS: %v", fJksKeypass),
|
||||||
|
fmt.Sprintf("JKSSTOREPASS: %v", fJksStorepass),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.LocalDeployerConfig{
|
||||||
|
OutputFormat: provider.OUTPUT_FORMAT_JKS,
|
||||||
|
OutputCertPath: fOutputCertPath,
|
||||||
|
OutputKeyPath: fOutputKeyPath,
|
||||||
|
JksAlias: fJksAlias,
|
||||||
|
JksKeypass: fJksKeypass,
|
||||||
|
JksStorepass: fJksStorepass,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fstat, err := os.Stat(fOutputCertPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
} else if fstat.Size() == 0 {
|
||||||
|
t.Errorf("err: empty output certificate file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
106
internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go
Normal file
106
internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package qiniucdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/qiniu/go-sdk/v7/auth"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
|
||||||
|
qiniusdk "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QiniuCDNDeployerConfig struct {
|
||||||
|
// 七牛云 AccessKey。
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
// 七牛云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QiniuCDNDeployer struct {
|
||||||
|
config *QiniuCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *qiniusdk.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*QiniuCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *QiniuCDNDeployerConfig) (*QiniuCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *QiniuCDNDeployerConfig, logger deployer.Logger) (*QiniuCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := qiniusdk.NewClient(auth.New(config.AccessKey, config.SecretKey))
|
||||||
|
|
||||||
|
uploader, err := providerQiniu.New(&providerQiniu.QiniuSSLCertUploaderConfig{
|
||||||
|
AccessKey: config.AccessKey,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &QiniuCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *QiniuCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CDN
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// "*.example.com" → ".example.com",适配七牛云 CDN 要求的泛域名格式
|
||||||
|
domain := strings.TrimPrefix(d.config.Domain, "*")
|
||||||
|
|
||||||
|
// 获取域名信息
|
||||||
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
|
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已获取域名信息", getDomainInfoResp)
|
||||||
|
|
||||||
|
// 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS
|
||||||
|
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
||||||
|
if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" {
|
||||||
|
modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已修改域名证书", modifyDomainHttpsConfResp)
|
||||||
|
} else {
|
||||||
|
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已将域名升级为 HTTPS", enableDomainHttpsResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package qiniucdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKey string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_QINIUCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v qiniu_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_QINIUCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_QINIUCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_QINIUCDN_ACCESSKEY="your-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_QINIUCDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_QINIUCDN_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("ACCESSKEY: %v", fAccessKey),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.QiniuCDNDeployerConfig{
|
||||||
|
AccessKey: fAccessKey,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
9
internal/pkg/core/deployer/providers/ssh/defines.go
Normal file
9
internal/pkg/core/deployer/providers/ssh/defines.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
type OutputFormatType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OUTPUT_FORMAT_PEM = OutputFormatType("PEM")
|
||||||
|
OUTPUT_FORMAT_PFX = OutputFormatType("PFX")
|
||||||
|
OUTPUT_FORMAT_JKS = OutputFormatType("JKS")
|
||||||
|
)
|
252
internal/pkg/core/deployer/providers/ssh/ssh.go
Normal file
252
internal/pkg/core/deployer/providers/ssh/ssh.go
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SshDeployerConfig struct {
|
||||||
|
// SSH 主机。
|
||||||
|
// 零值时默认为 "localhost"。
|
||||||
|
SshHost string `json:"sshHost,omitempty"`
|
||||||
|
// SSH 端口。
|
||||||
|
// 零值时默认为 22。
|
||||||
|
SshPort int32 `json:"sshPort,omitempty"`
|
||||||
|
// SSH 登录用户名。
|
||||||
|
SshUsername string `json:"sshUsername,omitempty"`
|
||||||
|
// SSH 登录密码。
|
||||||
|
SshPassword string `json:"sshPassword,omitempty"`
|
||||||
|
// SSH 登录私钥。
|
||||||
|
SshKey string `json:"sshKey,omitempty"`
|
||||||
|
// SSH 登录私钥口令。
|
||||||
|
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
|
||||||
|
// 前置命令。
|
||||||
|
PreCommand string `json:"preCommand,omitempty"`
|
||||||
|
// 后置命令。
|
||||||
|
PostCommand string `json:"postCommand,omitempty"`
|
||||||
|
// 输出证书格式。
|
||||||
|
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
||||||
|
// 输出证书文件路径。
|
||||||
|
OutputCertPath string `json:"outputCertPath,omitempty"`
|
||||||
|
// 输出私钥文件路径。
|
||||||
|
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||||
|
// PFX 导出密码。
|
||||||
|
// 证书格式为 PFX 时必填。
|
||||||
|
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||||
|
// JKS 别名。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksAlias string `json:"jksAlias,omitempty"`
|
||||||
|
// JKS 密钥密码。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksKeypass string `json:"jksKeypass,omitempty"`
|
||||||
|
// JKS 存储密码。
|
||||||
|
// 证书格式为 JKS 时必填。
|
||||||
|
JksStorepass string `json:"jksStorepass,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SshDeployer struct {
|
||||||
|
config *SshDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*SshDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *SshDeployerConfig) (*SshDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *SshDeployerConfig, logger deployer.Logger) (*SshDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SshDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 连接
|
||||||
|
client, err := createSshClient(
|
||||||
|
d.config.SshHost,
|
||||||
|
d.config.SshPort,
|
||||||
|
d.config.SshUsername,
|
||||||
|
d.config.SshPassword,
|
||||||
|
d.config.SshKey,
|
||||||
|
d.config.SshKeyPassphrase,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssh client")
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
d.logger.Logt("SSH connected")
|
||||||
|
|
||||||
|
// 执行前置命令
|
||||||
|
if d.config.PreCommand != "" {
|
||||||
|
stdout, stderr, err := execSshCommand(client, d.config.PreCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("SSH pre-command executed", stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书和私钥文件
|
||||||
|
switch d.config.OutputFormat {
|
||||||
|
case OUTPUT_FORMAT_PEM:
|
||||||
|
if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded")
|
||||||
|
|
||||||
|
if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload private key file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("private key file uploaded")
|
||||||
|
|
||||||
|
case OUTPUT_FORMAT_PFX:
|
||||||
|
pfxData, err := x509.TransformCertificateFromPEMToPFX(certPem, privkeyPem, d.config.PfxPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to transform certificate to PFX")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate transformed to PFX")
|
||||||
|
|
||||||
|
if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded")
|
||||||
|
|
||||||
|
case OUTPUT_FORMAT_JKS:
|
||||||
|
jksData, err := x509.TransformCertificateFromPEMToJKS(certPem, privkeyPem, d.config.JksAlias, d.config.JksKeypass, d.config.JksStorepass)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to transform certificate to JKS")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate transformed to JKS")
|
||||||
|
|
||||||
|
if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported output format: %s", d.config.OutputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行后置命令
|
||||||
|
if d.config.PostCommand != "" {
|
||||||
|
stdout, stderr, err := execSshCommand(client, d.config.PostCommand)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("SSH post-command executed", stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
var authMethod ssh.AuthMethod
|
||||||
|
if key != "" {
|
||||||
|
var signer ssh.Signer
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if keyPassphrase != "" {
|
||||||
|
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase))
|
||||||
|
} else {
|
||||||
|
signer, err = ssh.ParsePrivateKey([]byte(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authMethod = ssh.PublicKeys(signer)
|
||||||
|
} else {
|
||||||
|
authMethod = ssh.Password(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
|
||||||
|
User: username,
|
||||||
|
Auth: []ssh.AuthMethod{authMethod},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
||||||
|
session, err := sshCli.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer session.Close()
|
||||||
|
var stdoutBuf bytes.Buffer
|
||||||
|
session.Stdout = &stdoutBuf
|
||||||
|
var stderrBuf bytes.Buffer
|
||||||
|
session.Stderr = &stderrBuf
|
||||||
|
err = session.Run(command)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
||||||
|
return writeSftpFile(sshCli, path, []byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
|
||||||
|
sftpCli, err := sftp.NewClient(sshCli)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create sftp client")
|
||||||
|
}
|
||||||
|
defer sftpCli.Close()
|
||||||
|
|
||||||
|
if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create remote directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to open remote file")
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to write to remote file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
90
internal/pkg/core/deployer/providers/ssh/ssh_test.go
Normal file
90
internal/pkg/core/deployer/providers/ssh/ssh_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSshHost string
|
||||||
|
fSshPort int
|
||||||
|
fSshUsername string
|
||||||
|
fSshPassword string
|
||||||
|
fOutputCertPath string
|
||||||
|
fOutputKeyPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_SSH_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSshHost, argsPrefix+"SSHHOST", "", "")
|
||||||
|
flag.IntVar(&fSshPort, argsPrefix+"SSHPORT", 0, "")
|
||||||
|
flag.StringVar(&fSshUsername, argsPrefix+"SSHUSERNAME", "", "")
|
||||||
|
flag.StringVar(&fSshPassword, argsPrefix+"SSHPASSWORD", "", "")
|
||||||
|
flag.StringVar(&fOutputCertPath, argsPrefix+"OUTPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fOutputKeyPath, argsPrefix+"OUTPUTKEYPATH", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v ssh_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_SSHHOST="localhost" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_SSHPORT=22 \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_SSHUSERNAME="root" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_SSHPASSWORD="password" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_OUTPUTCERTPATH="/path/to/your-output-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_SSH_OUTPUTKEYPATH="/path/to/your-output-key.pem"
|
||||||
|
*/
|
||||||
|
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("SSHHOST: %v", fSshHost),
|
||||||
|
fmt.Sprintf("SSHPORT: %v", fSshPort),
|
||||||
|
fmt.Sprintf("SSHUSERNAME: %v", fSshUsername),
|
||||||
|
fmt.Sprintf("SSHPASSWORD: %v", fSshPassword),
|
||||||
|
fmt.Sprintf("OUTPUTCERTPATH: %v", fOutputCertPath),
|
||||||
|
fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.SshDeployerConfig{
|
||||||
|
SshHost: fSshHost,
|
||||||
|
SshPort: int32(fSshPort),
|
||||||
|
SshUsername: fSshUsername,
|
||||||
|
SshPassword: fSshPassword,
|
||||||
|
OutputCertPath: fOutputCertPath,
|
||||||
|
OutputKeyPath: fOutputKeyPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
package tencentcloudcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||||
|
"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"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudCDNDeployerConfig struct {
|
||||||
|
// 腾讯云 SecretId。
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
// 腾讯云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudCDNDeployer struct {
|
||||||
|
config *TencentCloudCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClients *tencentCloudCDNDeployerSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*TencentCloudCDNDeployer)(nil)
|
||||||
|
|
||||||
|
type tencentCloudCDNDeployerSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *TencentCloudCDNDeployerConfig) (*TencentCloudCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *TencentCloudCDNDeployerConfig, logger deployer.Logger) (*TencentCloudCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := createSdkClients(config.SecretId, config.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: config.SecretId,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 获取待部署的 CDN 实例
|
||||||
|
// 如果是泛域名,根据证书匹配 CDN 实例
|
||||||
|
instanceIds := make([]string, 0)
|
||||||
|
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceIds = domains
|
||||||
|
} else {
|
||||||
|
instanceIds = append(instanceIds, d.config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过已部署的 CDN 实例
|
||||||
|
if len(instanceIds) > 0 {
|
||||||
|
deployedDomains, err := d.getDeployedDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := make([]string, 0)
|
||||||
|
for _, instanceId := range instanceIds {
|
||||||
|
if !slices.Contains(deployedDomains, instanceId) {
|
||||||
|
temp = append(temp, instanceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instanceIds = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instanceIds) == 0 {
|
||||||
|
d.logger.Logt("已部署过或没有要部署的 CDN 实例")
|
||||||
|
} else {
|
||||||
|
// 证书部署到 CDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCDNDeployer) getDomainsByCertificateId(cloudCertId string) ([]string, error) {
|
||||||
|
// 获取证书中的可用域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
|
describeCertDomainsReq.CertId = common.StringPtr(cloudCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("cdn")
|
||||||
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
|
domains = append(domains, *domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCDNDeployer) getDeployedDomainsByCertificateId(cloudCertId string) ([]string, error) {
|
||||||
|
// 根据证书查询关联 CDN 域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/62674
|
||||||
|
describeDeployedResourcesReq := tcSsl.NewDescribeDeployedResourcesRequest()
|
||||||
|
describeDeployedResourcesReq.CertificateIds = common.StringPtrs([]string{cloudCertId})
|
||||||
|
describeDeployedResourcesReq.ResourceType = common.StringPtr("cdn")
|
||||||
|
describeDeployedResourcesResp, err := d.sdkClients.ssl.DescribeDeployedResources(describeDeployedResourcesReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeDeployedResources'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeDeployedResourcesResp.Response.DeployedResources != nil {
|
||||||
|
for _, deployedResource := range describeDeployedResourcesResp.Response.DeployedResources {
|
||||||
|
for _, resource := range deployedResource.Resources {
|
||||||
|
domains = append(domains, *resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClients(secretId, secretKey string) (*tencentCloudCDNDeployerSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tencentCloudCDNDeployerSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package tencentcloudcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSecretId string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v tencentcloud_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_SECRETID="your-secret-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_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("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCDNDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package tencentcloudclb
|
||||||
|
|
||||||
|
type DeployResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 资源类型:通过 SSL 服务部署到云资源实例。
|
||||||
|
DEPLOY_RESOURCE_USE_SSLDEPLOY = DeployResourceType("ssl-deploy")
|
||||||
|
// 资源类型:部署到指定负载均衡器。
|
||||||
|
DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer")
|
||||||
|
// 资源类型:部署到指定监听器。
|
||||||
|
DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener")
|
||||||
|
// 资源类型:部署到指定转发规则域名。
|
||||||
|
DEPLOY_RESOURCE_RULEDOMAIN = DeployResourceType("ruledomain")
|
||||||
|
)
|
@ -0,0 +1,305 @@
|
|||||||
|
package tencentcloudclb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcClb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317"
|
||||||
|
"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/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudCLBDeployerConfig struct {
|
||||||
|
// 腾讯云 SecretId。
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
// 腾讯云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 腾讯云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 部署资源类型。
|
||||||
|
ResourceType DeployResourceType `json:"resourceType"`
|
||||||
|
// 负载均衡器 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
|
||||||
|
LoadbalancerId string `json:"loadbalancerId,omitempty"`
|
||||||
|
// 负载均衡监听 ID。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY]、[DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER]、[DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
|
||||||
|
ListenerId string `json:"listenerId,omitempty"`
|
||||||
|
// SNI 域名或七层转发规则域名(支持泛域名)。
|
||||||
|
// 部署资源类型为 [DEPLOY_RESOURCE_SSLDEPLOY] 时选填;部署资源类型为 [DEPLOY_RESOURCE_RULEDOMAIN] 时必填。
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudCLBDeployer struct {
|
||||||
|
config *TencentCloudCLBDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClients *wSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*TencentCloudCLBDeployer)(nil)
|
||||||
|
|
||||||
|
type wSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
clb *tcClb.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *TencentCloudCLBDeployerConfig) (*TencentCloudCLBDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *TencentCloudCLBDeployerConfig, logger deployer.Logger) (*TencentCloudCLBDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := createSdkClients(config.SecretId, config.SecretKey, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: config.SecretId,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudCLBDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 根据部署资源类型决定部署方式
|
||||||
|
switch d.config.ResourceType {
|
||||||
|
case DEPLOY_RESOURCE_USE_SSLDEPLOY:
|
||||||
|
if err := d.deployToInstanceUseSsl(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LOADBALANCER:
|
||||||
|
if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_LISTENER:
|
||||||
|
if err := d.deployToListener(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEPLOY_RESOURCE_RULEDOMAIN:
|
||||||
|
if err := d.deployToRuleDomain(ctx, upres.CertId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) deployToInstanceUseSsl(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 证书部署到 CLB 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(cloudCertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("clb")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
if d.config.Domain == "" {
|
||||||
|
// 未开启 SNI,只需指定到监听器
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", d.config.LoadbalancerId, d.config.ListenerId)})
|
||||||
|
} else {
|
||||||
|
// 开启 SNI,需指定到域名(支持泛域名)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", d.config.LoadbalancerId, d.config.ListenerId, d.config.Domain)})
|
||||||
|
}
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerIds := make([]string, 0)
|
||||||
|
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(d.config.LoadbalancerId)
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
} else {
|
||||||
|
if describeListenersResp.Response.Listeners != nil {
|
||||||
|
for _, listener := range describeListenersResp.Response.Listeners {
|
||||||
|
if listener.Protocol == nil || (*listener.Protocol != "HTTPS" && *listener.Protocol != "TCP_SSL" && *listener.Protocol != "QUIC") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerIds = append(listenerIds, *listener.ListenerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到负载均衡器下的监听器", listenerIds)
|
||||||
|
|
||||||
|
// 批量更新监听器证书
|
||||||
|
if len(listenerIds) > 0 {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for _, listenerId := range listenerIds {
|
||||||
|
if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, listenerId, cloudCertId); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新监听器证书
|
||||||
|
if err := d.modifyListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerId, cloudCertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) deployToRuleDomain(ctx context.Context, cloudCertId string) error {
|
||||||
|
if d.config.LoadbalancerId == "" {
|
||||||
|
return errors.New("config `loadbalancerId` is required")
|
||||||
|
}
|
||||||
|
if d.config.ListenerId == "" {
|
||||||
|
return errors.New("config `listenerId` is required")
|
||||||
|
}
|
||||||
|
if d.config.Domain == "" {
|
||||||
|
return errors.New("config `domain` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改负载均衡七层监听器转发规则的域名级别属性
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/38092
|
||||||
|
modifyDomainAttributesReq := tcClb.NewModifyDomainAttributesRequest()
|
||||||
|
modifyDomainAttributesReq.LoadBalancerId = common.StringPtr(d.config.LoadbalancerId)
|
||||||
|
modifyDomainAttributesReq.ListenerId = common.StringPtr(d.config.ListenerId)
|
||||||
|
modifyDomainAttributesReq.Domain = common.StringPtr(d.config.Domain)
|
||||||
|
modifyDomainAttributesReq.Certificate = &tcClb.CertificateInput{
|
||||||
|
SSLMode: common.StringPtr("UNIDIRECTIONAL"),
|
||||||
|
CertId: common.StringPtr(cloudCertId),
|
||||||
|
}
|
||||||
|
modifyDomainAttributesResp, err := d.sdkClients.clb.ModifyDomainAttributes(modifyDomainAttributesReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCLBDeployer) modifyListenerCertificate(ctx context.Context, cloudLoadbalancerId, cloudListenerId, cloudCertId string) error {
|
||||||
|
// 查询监听器列表
|
||||||
|
// REF: https://cloud.tencent.com/document/api/214/30686
|
||||||
|
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
||||||
|
describeListenersReq.LoadBalancerId = common.StringPtr(cloudLoadbalancerId)
|
||||||
|
describeListenersReq.ListenerIds = common.StringPtrs([]string{cloudListenerId})
|
||||||
|
describeListenersResp, err := d.sdkClients.clb.DescribeListeners(describeListenersReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeListeners'")
|
||||||
|
}
|
||||||
|
if len(describeListenersResp.Response.Listeners) == 0 {
|
||||||
|
return errors.New("listener not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已查询到监听器属性", describeListenersResp.Response)
|
||||||
|
|
||||||
|
// 修改监听器属性
|
||||||
|
// REF: https://cloud.tencent.com/document/product/214/30681
|
||||||
|
modifyListenerReq := tcClb.NewModifyListenerRequest()
|
||||||
|
modifyListenerReq.LoadBalancerId = common.StringPtr(cloudLoadbalancerId)
|
||||||
|
modifyListenerReq.ListenerId = common.StringPtr(cloudListenerId)
|
||||||
|
modifyListenerReq.Certificate = &tcClb.CertificateInput{CertId: common.StringPtr(cloudCertId)}
|
||||||
|
if describeListenersResp.Response.Listeners[0].Certificate != nil && describeListenersResp.Response.Listeners[0].Certificate.SSLMode != nil {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = describeListenersResp.Response.Listeners[0].Certificate.SSLMode
|
||||||
|
modifyListenerReq.Certificate.CertCaId = describeListenersResp.Response.Listeners[0].Certificate.CertCaId
|
||||||
|
} else {
|
||||||
|
modifyListenerReq.Certificate.SSLMode = common.StringPtr("UNIDIRECTIONAL")
|
||||||
|
}
|
||||||
|
modifyListenerResp, err := d.sdkClients.clb.ModifyListener(modifyListenerReq)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyListener'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已修改监听器属性", modifyListenerResp.Response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClients(secretId, secretKey, region string) (*wSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
// 注意虽然官方文档中地域无需指定,但实际需要部署到 CLB 时必传
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clbClient, err := tcClb.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
clb: clbClient,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
package tencentcloudclb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSecretId string
|
||||||
|
fSecretKey string
|
||||||
|
fRegion string
|
||||||
|
fLoadbalancerId string
|
||||||
|
fListenerId string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
|
||||||
|
flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "")
|
||||||
|
flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v tencentcloud_clb_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_SECRETID="your-secret-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_REGION="ap-guangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_LOADBALANCERID="your-clb-lb-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_LISTENERID="your-clb-lbl-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCLB_DOMAIN="example.com"
|
||||||
|
*/
|
||||||
|
func TestDeploy(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Deploy_UseSslDeploy", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
fmt.Sprintf("LISTENERID: %v", fListenerId),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_USE_SSLDEPLOY,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToLoadbalancer", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LOADBALANCER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToListener", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
fmt.Sprintf("LISTENERID: %v", fListenerId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_LISTENER,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Deploy_ToRuleDomain", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
|
||||||
|
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
|
||||||
|
fmt.Sprintf("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId),
|
||||||
|
fmt.Sprintf("LISTENERID: %v", fListenerId),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCLBDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
Region: fRegion,
|
||||||
|
ResourceType: provider.DEPLOY_RESOURCE_RULEDOMAIN,
|
||||||
|
LoadbalancerId: fLoadbalancerId,
|
||||||
|
ListenerId: fListenerId,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package tencentcloudcdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"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/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudCOSDeployerConfig struct {
|
||||||
|
// 腾讯云 SecretId。
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
// 腾讯云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 腾讯云地域。
|
||||||
|
Region string `json:"region"`
|
||||||
|
// 存储桶名。
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
// 自定义域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudCOSDeployer struct {
|
||||||
|
config *TencentCloudCOSDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *tcSsl.Client
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*TencentCloudCOSDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *TencentCloudCOSDeployerConfig) (*TencentCloudCOSDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *TencentCloudCOSDeployerConfig, logger deployer.Logger) (*TencentCloudCOSDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.SecretId, config.SecretKey, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: config.SecretId,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudCOSDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudCOSDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.Bucket == "" {
|
||||||
|
return nil, errors.New("config `bucket` is required")
|
||||||
|
}
|
||||||
|
if d.config.Domain == "" {
|
||||||
|
return nil, errors.New("config `domain` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 证书部署到 COS 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("cos")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", d.config.Region, d.config.Bucket, d.config.Domain)})
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(secretId, secretKey, region string) (*tcSsl.Client, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package tencentcloudcdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSecretId string
|
||||||
|
fSecretKey string
|
||||||
|
fRegion string
|
||||||
|
fBucket string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fRegion, argsPrefix+"REGION", "", "")
|
||||||
|
flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v tencentcloud_cos_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_SECRETID="your-secret-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_REGION="ap-guangzhou" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_BUCKET="your-cos-bucket" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDCOS_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("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("REGION: %v", fRegion),
|
||||||
|
fmt.Sprintf("BUCKET: %v", fBucket),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudCOSDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
Region: fRegion,
|
||||||
|
Bucket: fBucket,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package tencentcloudecdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
tcCdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||||
|
"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/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudECDNDeployerConfig struct {
|
||||||
|
// 腾讯云 SecretId。
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
// 腾讯云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudECDNDeployer struct {
|
||||||
|
config *TencentCloudECDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClients *wSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*TencentCloudECDNDeployer)(nil)
|
||||||
|
|
||||||
|
type wSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
cdn *tcCdn.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *TencentCloudECDNDeployerConfig) (*TencentCloudECDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *TencentCloudECDNDeployerConfig, logger deployer.Logger) (*TencentCloudECDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := createSdkClients(config.SecretId, config.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: config.SecretId,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudECDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudECDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 获取待部署的 CDN 实例
|
||||||
|
// 如果是泛域名,根据证书匹配 CDN 实例
|
||||||
|
instanceIds := make([]string, 0)
|
||||||
|
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||||
|
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceIds = domains
|
||||||
|
} else {
|
||||||
|
instanceIds = append(instanceIds, d.config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instanceIds) == 0 {
|
||||||
|
d.logger.Logt("已部署过或没有要部署的 ECDN 实例")
|
||||||
|
} else {
|
||||||
|
// 证书部署到 ECDN 实例
|
||||||
|
// REF: https://cloud.tencent.com/document/product/400/91667
|
||||||
|
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
||||||
|
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
||||||
|
deployCertificateInstanceReq.ResourceType = common.StringPtr("ecdn")
|
||||||
|
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
||||||
|
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(instanceIds)
|
||||||
|
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudECDNDeployer) getDomainsByCertificateId(cloudCertId string) ([]string, error) {
|
||||||
|
// 获取证书中的可用域名
|
||||||
|
// REF: https://cloud.tencent.com/document/product/228/42491
|
||||||
|
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
||||||
|
describeCertDomainsReq.CertId = common.StringPtr(cloudCertId)
|
||||||
|
describeCertDomainsReq.Product = common.StringPtr("ecdn")
|
||||||
|
describeCertDomainsResp, err := d.sdkClients.cdn.DescribeCertDomains(describeCertDomainsReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertDomains'")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if describeCertDomainsResp.Response.Domains == nil {
|
||||||
|
for _, domain := range describeCertDomainsResp.Response.Domains {
|
||||||
|
domains = append(domains, *domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClients(secretId, secretKey string) (*wSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cdnClient, err := tcCdn.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
cdn: cdnClient,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package tencentcloudecdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSecretId string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v tencentcloud_ecdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_SECRETID="your-secret-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDECDN_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("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudECDNDeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package tencentcloudeteo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
"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"
|
||||||
|
tcTeo "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TencentCloudTEODeployerConfig struct {
|
||||||
|
// 腾讯云 SecretId。
|
||||||
|
SecretId string `json:"secretId"`
|
||||||
|
// 腾讯云 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 站点 ID。
|
||||||
|
ZoneId string `json:"zoneId"`
|
||||||
|
// 加速域名(不支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TencentCloudTEODeployer struct {
|
||||||
|
config *TencentCloudTEODeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClients *wSdkClients
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*TencentCloudTEODeployer)(nil)
|
||||||
|
|
||||||
|
type wSdkClients struct {
|
||||||
|
ssl *tcSsl.Client
|
||||||
|
teo *tcTeo.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *TencentCloudTEODeployerConfig) (*TencentCloudTEODeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *TencentCloudTEODeployerConfig, logger deployer.Logger) (*TencentCloudTEODeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := createSdkClients(config.SecretId, config.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploader, err := providerSsl.New(&providerSsl.TencentCloudSSLUploaderConfig{
|
||||||
|
SecretId: config.SecretId,
|
||||||
|
SecretKey: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TencentCloudTEODeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClients: clients,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TencentCloudTEODeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.ZoneId == "" {
|
||||||
|
return nil, xerrors.New("config `zoneId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传证书到 SSL
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
// 配置域名证书
|
||||||
|
// REF: https://cloud.tencent.com/document/product/1552/80764
|
||||||
|
modifyHostsCertificateReq := tcTeo.NewModifyHostsCertificateRequest()
|
||||||
|
modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId)
|
||||||
|
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||||
|
modifyHostsCertificateReq.Hosts = common.StringPtrs([]string{d.config.Domain})
|
||||||
|
modifyHostsCertificateReq.ServerCertInfo = []*tcTeo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||||
|
modifyHostsCertificateResp, err := d.sdkClients.teo.ModifyHostsCertificate(modifyHostsCertificateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("已配置域名证书", modifyHostsCertificateResp.Response)
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClients(secretId, secretKey string) (*wSdkClients, error) {
|
||||||
|
credential := common.NewCredential(secretId, secretKey)
|
||||||
|
|
||||||
|
sslClient, err := tcSsl.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
teoClient, err := tcTeo.NewClient(credential, "", profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wSdkClients{
|
||||||
|
ssl: sslClient,
|
||||||
|
teo: teoClient,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package tencentcloudeteo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-teo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fSecretId string
|
||||||
|
fSecretKey string
|
||||||
|
fZoneId string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fZoneId, argsPrefix+"ZONEID", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v tencentcloud_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_SECRETID="your-secret-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_ZONEID="your-zone-id" \
|
||||||
|
--CERTIMATE_DEPLOYER_TENCENTCLOUDETEO_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("SECRETID: %v", fSecretId),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("ZONEID: %v", fZoneId),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.TencentCloudTEODeployerConfig{
|
||||||
|
SecretId: fSecretId,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
ZoneId: fZoneId,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package volcenginecdn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
veCdn "github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerCdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VolcEngineCDNDeployerConfig struct {
|
||||||
|
// 火山引擎 AccessKey。
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
// 火山引擎 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolcEngineCDNDeployer struct {
|
||||||
|
config *VolcEngineCDNDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *veCdn.CDN
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*VolcEngineCDNDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *VolcEngineCDNDeployerConfig) (*VolcEngineCDNDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *VolcEngineCDNDeployerConfig, logger deployer.Logger) (*VolcEngineCDNDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := veCdn.NewInstance()
|
||||||
|
client.Client.SetAccessKey(config.AccessKey)
|
||||||
|
client.Client.SetSecretKey(config.SecretKey)
|
||||||
|
|
||||||
|
uploader, err := providerCdn.New(&providerCdn.VolcEngineCDNUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKey,
|
||||||
|
AccessKeySecret: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VolcEngineCDNDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VolcEngineCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 CDN
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||||
|
// 获取指定证书可关联的域名
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/125711
|
||||||
|
describeCertConfigReq := &veCdn.DescribeCertConfigRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
}
|
||||||
|
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeCertConfigResp.Result.CertNotConfig != nil {
|
||||||
|
for i := range describeCertConfigResp.Result.CertNotConfig {
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if describeCertConfigResp.Result.OtherCertConfig != nil {
|
||||||
|
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
||||||
|
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) == 0 {
|
||||||
|
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
||||||
|
// 所有可关联的域名都配置了该证书,跳过部署
|
||||||
|
} else {
|
||||||
|
return nil, xerrors.New("domain not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = append(domains, d.config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
// 关联证书与加速域名
|
||||||
|
// REF: https://www.volcengine.com/docs/6454/125712
|
||||||
|
batchDeployCertReq := &veCdn.BatchDeployCertRequest{
|
||||||
|
CertId: upres.CertId,
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
d.logger.Logt(fmt.Sprintf("已关联证书到域名 %s", domain), batchDeployCertResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package volcenginecdn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKey string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_VOLCENGINECDN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v volcengine_cdn_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINECDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINECDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINECDN_ACCESSKEY="your-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINECDN_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINECDN_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("ACCESSKEY: %v", fAccessKey),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.VolcEngineCDNDeployerConfig{
|
||||||
|
AccessKey: fAccessKey,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
package volcenginelive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
veLive "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
providerLive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VolcEngineLiveDeployerConfig struct {
|
||||||
|
// 火山引擎 AccessKey。
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
// 火山引擎 SecretKey。
|
||||||
|
SecretKey string `json:"secretKey"`
|
||||||
|
// 加速域名(支持泛域名)。
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolcEngineLiveDeployer struct {
|
||||||
|
config *VolcEngineLiveDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
sdkClient *veLive.Live
|
||||||
|
sslUploader uploader.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*VolcEngineLiveDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *VolcEngineLiveDeployerConfig) (*VolcEngineLiveDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *VolcEngineLiveDeployerConfig, logger deployer.Logger) (*VolcEngineLiveDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := veLive.NewInstance()
|
||||||
|
client.SetAccessKey(config.AccessKey)
|
||||||
|
client.SetSecretKey(config.SecretKey)
|
||||||
|
|
||||||
|
uploader, err := providerLive.New(&providerLive.VolcEngineLiveUploaderConfig{
|
||||||
|
AccessKeyId: config.AccessKey,
|
||||||
|
AccessKeySecret: config.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VolcEngineLiveDeployer{
|
||||||
|
logger: logger,
|
||||||
|
config: config,
|
||||||
|
sdkClient: client,
|
||||||
|
sslUploader: uploader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VolcEngineLiveDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
// 上传证书到 Live
|
||||||
|
upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("certificate file uploaded", upres)
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
if strings.HasPrefix(d.config.Domain, "*.") {
|
||||||
|
listDomainDetailPageNum := int32(1)
|
||||||
|
listDomainDetailPageSize := int32(1000)
|
||||||
|
listDomainDetailTotal := 0
|
||||||
|
for {
|
||||||
|
// 查询域名列表
|
||||||
|
// REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8
|
||||||
|
listDomainDetailReq := &veLive.ListDomainDetailBody{
|
||||||
|
PageNum: listDomainDetailPageNum,
|
||||||
|
PageSize: listDomainDetailPageSize,
|
||||||
|
}
|
||||||
|
listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListDomainDetail'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if listDomainDetailResp.Result.DomainList != nil {
|
||||||
|
for _, item := range listDomainDetailResp.Result.DomainList {
|
||||||
|
// 仅匹配泛域名的下一级子域名
|
||||||
|
wildcardDomain := strings.TrimPrefix(d.config.Domain, "*")
|
||||||
|
if strings.HasSuffix(item.Domain, wildcardDomain) && !strings.Contains(strings.TrimSuffix(item.Domain, wildcardDomain), ".") {
|
||||||
|
domains = append(domains, item.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listDomainDetailLen := len(listDomainDetailResp.Result.DomainList)
|
||||||
|
if listDomainDetailLen < int(listDomainDetailPageSize) || int(listDomainDetailResp.Result.Total) <= listDomainDetailTotal+listDomainDetailLen {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
listDomainDetailPageNum++
|
||||||
|
listDomainDetailTotal += listDomainDetailLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) == 0 {
|
||||||
|
return nil, xerrors.Errorf("未查询到匹配的域名: %s", d.config.Domain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = append(domains, d.config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
// 绑定证书
|
||||||
|
// REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6
|
||||||
|
bindCertReq := &veLive.BindCertBody{
|
||||||
|
ChainID: upres.CertId,
|
||||||
|
Domain: domain,
|
||||||
|
HTTPS: cast.BoolPtr(true),
|
||||||
|
}
|
||||||
|
bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
} else {
|
||||||
|
d.logger.Logt(fmt.Sprintf("已绑定证书到域名 %s", domain), bindCertResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package volcenginelive_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fAccessKey string
|
||||||
|
fSecretKey string
|
||||||
|
fDomain string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_VOLCENGINELIVE_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "")
|
||||||
|
flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "")
|
||||||
|
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v volcengine_live_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINELIVE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINELIVE_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINELIVE_ACCESSKEY="your-access-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINELIVE_SECRETKEY="your-secret-key" \
|
||||||
|
--CERTIMATE_DEPLOYER_VOLCENGINELIVE_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("ACCESSKEY: %v", fAccessKey),
|
||||||
|
fmt.Sprintf("SECRETKEY: %v", fSecretKey),
|
||||||
|
fmt.Sprintf("DOMAIN: %v", fDomain),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.VolcEngineLiveDeployerConfig{
|
||||||
|
AccessKey: fAccessKey,
|
||||||
|
SecretKey: fSecretKey,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
85
internal/pkg/core/deployer/providers/webhook/webhook.go
Normal file
85
internal/pkg/core/deployer/providers/webhook/webhook.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
|
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebhookDeployerConfig struct {
|
||||||
|
// Webhook URL。
|
||||||
|
Url string `json:"url"`
|
||||||
|
// Webhook 变量字典。
|
||||||
|
Variables map[string]string `json:"variables,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookDeployer struct {
|
||||||
|
config *WebhookDeployerConfig
|
||||||
|
logger deployer.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*WebhookDeployer)(nil)
|
||||||
|
|
||||||
|
func New(config *WebhookDeployerConfig) (*WebhookDeployer, error) {
|
||||||
|
return NewWithLogger(config, deployer.NewNilLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithLogger(config *WebhookDeployerConfig, logger deployer.Logger) (*WebhookDeployer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebhookDeployer{
|
||||||
|
config: config,
|
||||||
|
logger: logger,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type webhookData struct {
|
||||||
|
SubjectAltNames string `json:"subjectAltNames"`
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
PrivateKey string `json:"privateKey"`
|
||||||
|
Variables map[string]string `json:"variables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
|
||||||
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to parse x509")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &webhookData{
|
||||||
|
SubjectAltNames: strings.Join(certX509.DNSNames, ","),
|
||||||
|
Certificate: certPem,
|
||||||
|
PrivateKey: privkeyPem,
|
||||||
|
Variables: d.config.Variables,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(data)
|
||||||
|
resp, err := xhttp.Req(d.config.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Wrap(err, "failed to send webhook request")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Logt("Webhook Response", string(resp))
|
||||||
|
|
||||||
|
return &deployer.DeployResult{
|
||||||
|
DeploymentData: map[string]any{
|
||||||
|
"responseText": string(resp),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
65
internal/pkg/core/deployer/providers/webhook/webhook_test.go
Normal file
65
internal/pkg/core/deployer/providers/webhook/webhook_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package webhook_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fUrl string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_WEBHOOK_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fUrl, argsPrefix+"URL", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v webhook_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_WEBHOOK_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_WEBHOOK_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_WEBHOOK_URL="https://example.com/your-webhook-url"
|
||||||
|
*/
|
||||||
|
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("URL: %v", fUrl),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.New(&provider.WebhookDeployerConfig{
|
||||||
|
Url: fUrl,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
64
internal/pkg/core/notifier/providers/bark/bark_test.go
Normal file
64
internal/pkg/core/notifier/providers/bark/bark_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package bark_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fServerUrl string
|
||||||
|
fDeviceKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_BARK_"
|
||||||
|
|
||||||
|
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
|
||||||
|
flag.StringVar(&fDeviceKey, argsPrefix+"DEVICEKEY", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v bark_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_BARK_SERVERURL="https://example.com/your-server-url" \
|
||||||
|
--CERTIMATE_NOTIFIER_BARK_DEVICEKEY="your-device-key"
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("SERVERURL: %v", fServerUrl),
|
||||||
|
fmt.Sprintf("DEVICEKEY: %v", fDeviceKey),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.BarkNotifierConfig{
|
||||||
|
ServerUrl: fServerUrl,
|
||||||
|
DeviceKey: fDeviceKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package dingtalk_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fAccessToken string
|
||||||
|
fSecret string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_DINGTALK_"
|
||||||
|
|
||||||
|
flag.StringVar(&fAccessToken, argsPrefix+"ACCESSTOKEN", "", "")
|
||||||
|
flag.StringVar(&fSecret, argsPrefix+"SECRET", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v dingtalk_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_DINGTALK_URL="https://example.com/your-webhook-url"
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("ACCESSTOKEN: %v", fAccessToken),
|
||||||
|
fmt.Sprintf("SECRET: %v", fSecret),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.DingTalkNotifierConfig{
|
||||||
|
AccessToken: fAccessToken,
|
||||||
|
Secret: fSecret,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -1,51 +1,89 @@
|
|||||||
package email_test
|
package email_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"context"
|
||||||
"strconv"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
notifierEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fSmtpHost string
|
||||||
|
fSmtpPort int
|
||||||
|
fSmtpTLS bool
|
||||||
|
fUsername string
|
||||||
|
fPassword string
|
||||||
|
fSenderAddress string
|
||||||
|
fReceiverAddress string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_EMAIL_"
|
||||||
|
|
||||||
|
flag.StringVar(&fSmtpHost, argsPrefix+"SMTPHOST", "", "")
|
||||||
|
flag.IntVar(&fSmtpPort, argsPrefix+"SMTPPORT", 0, "")
|
||||||
|
flag.BoolVar(&fSmtpTLS, argsPrefix+"SMTPTLS", false, "")
|
||||||
|
flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
|
||||||
|
flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
|
||||||
|
flag.StringVar(&fSenderAddress, argsPrefix+"SENDERADDRESS", "", "")
|
||||||
|
flag.StringVar(&fReceiverAddress, argsPrefix+"RECEIVERADDRESS", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Shell command to run this test:
|
Shell command to run this test:
|
||||||
|
|
||||||
CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \
|
go test -v email_test.go -args \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \
|
--CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_SMTPHOST="smtp.example.com" \
|
--CERTIMATE_NOTIFIER_EMAIL_SMTPPORT=465 \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \
|
--CERTIMATE_NOTIFIER_EMAIL_SMTPTLS=true \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \
|
--CERTIMATE_NOTIFIER_EMAIL_USERNAME="your-username" \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \
|
--CERTIMATE_NOTIFIER_EMAIL_PASSWORD="your-password" \
|
||||||
CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com" \
|
--CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS="sender@example.com" \
|
||||||
go test -v -run TestNotify email_test.go
|
--CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS="receiver@example.com"
|
||||||
*/
|
*/
|
||||||
func TestNotify(t *testing.T) {
|
func TestNotify(t *testing.T) {
|
||||||
smtpPort, err := strconv.ParseInt(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPPORT"), 10, 32)
|
flag.Parse()
|
||||||
if err != nil {
|
|
||||||
t.Errorf("invalid envvar: %+v", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
smtpTLS, err := strconv.ParseBool(os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPTLS"))
|
t.Run("Notify", func(t *testing.T) {
|
||||||
if err != nil {
|
t.Log(strings.Join([]string{
|
||||||
t.Errorf("invalid envvar: %+v", err)
|
"args:",
|
||||||
panic(err)
|
fmt.Sprintf("SMTPHOST: %v", fSmtpHost),
|
||||||
}
|
fmt.Sprintf("SMTPPORT: %v", fSmtpPort),
|
||||||
|
fmt.Sprintf("SMTPTLS: %v", fSmtpTLS),
|
||||||
|
fmt.Sprintf("USERNAME: %v", fUsername),
|
||||||
|
fmt.Sprintf("PASSWORD: %v", fPassword),
|
||||||
|
fmt.Sprintf("SENDERADDRESS: %v", fSenderAddress),
|
||||||
|
fmt.Sprintf("RECEIVERADDRESS: %v", fReceiverAddress),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
res, err := notifierEmail.New(¬ifierEmail.EmailNotifierConfig{
|
notifier, err := provider.New(&provider.EmailNotifierConfig{
|
||||||
SmtpHost: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SMTPHOST"),
|
SmtpHost: fSmtpHost,
|
||||||
SmtpPort: int32(smtpPort),
|
SmtpPort: int32(fSmtpPort),
|
||||||
SmtpTLS: smtpTLS,
|
SmtpTLS: fSmtpTLS,
|
||||||
Username: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_USERNAME"),
|
Username: fUsername,
|
||||||
Password: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_PASSWORD"),
|
Password: fPassword,
|
||||||
SenderAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_SENDERADDRESS"),
|
SenderAddress: fSenderAddress,
|
||||||
ReceiverAddress: os.Getenv("CERTIMATE_NOTIFIER_EMAIL_RECEIVERADDRESS"),
|
ReceiverAddress: fReceiverAddress,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
t.Errorf("invalid envvar: %+v", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("notify result: %v", res)
|
|
||||||
}
|
}
|
||||||
|
57
internal/pkg/core/notifier/providers/lark/lark_test.go
Normal file
57
internal/pkg/core/notifier/providers/lark/lark_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package lark_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fWebhookUrl string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_LARK_"
|
||||||
|
|
||||||
|
flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v lark_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_LARK_WEBHOOKURL="https://example.com/your-webhook-url"
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.LarkNotifierConfig{
|
||||||
|
WebhookUrl: fWebhookUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package serverchan_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fUrl string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_SERVERCHAN_"
|
||||||
|
|
||||||
|
flag.StringVar(&fUrl, argsPrefix+"URL", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v serverchan_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_SERVERCHAN_URL="https://example.com/your-webhook-url" \
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("URL: %v", fUrl),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.ServerChanNotifierConfig{
|
||||||
|
Url: fUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package telegram_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fApiToken string
|
||||||
|
fChartId int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_TELEGRAM_"
|
||||||
|
|
||||||
|
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
|
||||||
|
flag.Int64Var(&fChartId, argsPrefix+"CHATID", 0, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v telegram_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_TELEGRAM_APITOKEN="your-api-token" \
|
||||||
|
--CERTIMATE_NOTIFIER_TELEGRAM_CHATID=123456
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("APITOKEN: %v", fApiToken),
|
||||||
|
fmt.Sprintf("CHATID: %v", fChartId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.TelegramNotifierConfig{
|
||||||
|
ApiToken: fApiToken,
|
||||||
|
ChatId: fChartId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
57
internal/pkg/core/notifier/providers/webhook/webhook_test.go
Normal file
57
internal/pkg/core/notifier/providers/webhook/webhook_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package webhook_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mockSubject = "test_subject"
|
||||||
|
mockMessage = "test_message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fUrl string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_NOTIFIER_WEBHOOK_"
|
||||||
|
|
||||||
|
flag.StringVar(&fUrl, argsPrefix+"URL", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v webhook_test.go -args \
|
||||||
|
--CERTIMATE_NOTIFIER_WEBHOOK_URL="https://example.com/your-webhook-url"
|
||||||
|
*/
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
t.Run("Notify", func(t *testing.T) {
|
||||||
|
t.Log(strings.Join([]string{
|
||||||
|
"args:",
|
||||||
|
fmt.Sprintf("URL: %v", fUrl),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
notifier, err := provider.New(&provider.WebhookNotifierConfig{
|
||||||
|
Url: fUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("ok: %v", res)
|
||||||
|
})
|
||||||
|
}
|
@ -144,11 +144,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl
|
|||||||
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &aliyunOpen.Config{
|
// 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoint string
|
var endpoint string
|
||||||
switch region {
|
switch region {
|
||||||
case "cn-hangzhou":
|
case "cn-hangzhou":
|
||||||
@ -156,9 +152,14 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl
|
|||||||
default:
|
default:
|
||||||
endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region)
|
endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region)
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
|
||||||
|
|
||||||
client, err := aliyunCas.NewClient(aConfig)
|
config := &aliyunOpen.Config{
|
||||||
|
Endpoint: tea.String(endpoint),
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunCas.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -121,11 +121,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl
|
|||||||
region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州
|
region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州
|
||||||
}
|
}
|
||||||
|
|
||||||
aConfig := &aliyunOpen.Config{
|
// 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoint string
|
var endpoint string
|
||||||
switch region {
|
switch region {
|
||||||
case
|
case
|
||||||
@ -137,9 +133,14 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl
|
|||||||
default:
|
default:
|
||||||
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region)
|
||||||
}
|
}
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
|
||||||
|
|
||||||
client, err := aliyunSlb.NewClient(aConfig)
|
config := &aliyunOpen.Config{
|
||||||
|
Endpoint: tea.String(endpoint),
|
||||||
|
AccessKeyId: tea.String(accessKeyId),
|
||||||
|
AccessKeySecret: tea.String(accessKeySecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := aliyunSlb.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package bytepluscdn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
@ -9,9 +10,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
bpCdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +25,7 @@ type ByteplusCDNUploaderConfig struct {
|
|||||||
|
|
||||||
type ByteplusCDNUploader struct {
|
type ByteplusCDNUploader struct {
|
||||||
config *ByteplusCDNUploaderConfig
|
config *ByteplusCDNUploaderConfig
|
||||||
sdkClient *cdn.CDN
|
sdkClient *bpCdn.CDN
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ uploader.Uploader = (*ByteplusCDNUploader)(nil)
|
var _ uploader.Uploader = (*ByteplusCDNUploader)(nil)
|
||||||
@ -32,14 +35,13 @@ func New(config *ByteplusCDNUploaderConfig) (*ByteplusCDNUploader, error) {
|
|||||||
return nil, errors.New("config is nil")
|
return nil, errors.New("config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := cdn.NewInstance()
|
client := bpCdn.NewInstance()
|
||||||
client := instance.Client
|
client.Client.SetAccessKey(config.AccessKey)
|
||||||
client.SetAccessKey(config.AccessKey)
|
client.Client.SetSecretKey(config.SecretKey)
|
||||||
client.SetSecretKey(config.SecretKey)
|
|
||||||
|
|
||||||
return &ByteplusCDNUploader{
|
return &ByteplusCDNUploader{
|
||||||
config: config,
|
config: config,
|
||||||
sdkClient: instance,
|
sdkClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,17 +51,17 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询证书列表,避免重复上传
|
// 查询证书列表,避免重复上传
|
||||||
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-listcertinfo
|
||||||
pageNum := int64(1)
|
listCertInfoPageNum := int64(1)
|
||||||
pageSize := int64(100)
|
listCertInfoPageSize := int64(100)
|
||||||
certSource := "cert_center"
|
listCertInfoTotal := 0
|
||||||
listCertInfoReq := &cdn.ListCertInfoRequest{
|
listCertInfoReq := &bpCdn.ListCertInfoRequest{
|
||||||
PageNum: &pageNum,
|
PageNum: cast.Int64Ptr(listCertInfoPageNum),
|
||||||
PageSize: &pageSize,
|
PageSize: cast.Int64Ptr(listCertInfoPageSize),
|
||||||
Source: &certSource,
|
Source: cast.StringPtr("cert_center"),
|
||||||
}
|
}
|
||||||
searchTotal := 0
|
|
||||||
for {
|
for {
|
||||||
listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq)
|
listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,8 +70,10 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke
|
|||||||
|
|
||||||
if listCertInfoResp.Result.CertInfo != nil {
|
if listCertInfoResp.Result.CertInfo != nil {
|
||||||
for _, certDetail := range listCertInfoResp.Result.CertInfo {
|
for _, certDetail := range listCertInfoResp.Result.CertInfo {
|
||||||
hash := sha256.Sum256(certX509.Raw)
|
fingerprintSha1 := sha1.Sum(certX509.Raw)
|
||||||
isSameCert := strings.EqualFold(hex.EncodeToString(hash[:]), certDetail.CertFingerprint.Sha256)
|
fingerprintSha256 := sha256.Sum256(certX509.Raw)
|
||||||
|
isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) &&
|
||||||
|
strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256)
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &uploader.UploadResult{
|
return &uploader.UploadResult{
|
||||||
@ -80,23 +84,26 @@ func (u *ByteplusCDNUploader) Upload(ctx context.Context, certPem string, privke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTotal += len(listCertInfoResp.Result.CertInfo)
|
listCertInfoLen := len(listCertInfoResp.Result.CertInfo)
|
||||||
if int(listCertInfoResp.Result.Total) > searchTotal {
|
if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen {
|
||||||
pageNum++
|
|
||||||
} else {
|
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
listCertInfoPageNum++
|
||||||
|
listCertInfoTotal += listCertInfoLen
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成新证书名(需符合 BytePlus 命名规则)
|
||||||
var certId, certName string
|
var certId, certName string
|
||||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
// 上传新证书
|
// 上传新证书
|
||||||
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-addcertificate
|
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-addcertificate
|
||||||
addCertificateReq := &cdn.AddCertificateRequest{
|
addCertificateReq := &bpCdn.AddCertificateRequest{
|
||||||
Certificate: certPem,
|
Certificate: certPem,
|
||||||
PrivateKey: privkeyPem,
|
PrivateKey: privkeyPem,
|
||||||
Source: &certSource,
|
Source: cast.StringPtr("cert_center"),
|
||||||
Desc: &certName,
|
Desc: cast.StringPtr(certName),
|
||||||
}
|
}
|
||||||
addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,7 +56,7 @@ func New(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error)
|
|||||||
|
|
||||||
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
newCert, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -83,12 +83,12 @@ func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, pri
|
|||||||
if certDetail.Certificate == certPem {
|
if certDetail.Certificate == certPem {
|
||||||
isSameCert = true
|
isSameCert = true
|
||||||
} else {
|
} else {
|
||||||
cert, err := x509.ParseCertificateFromPEM(certDetail.Certificate)
|
oldCertX509, err := x509.ParseCertificateFromPEM(certDetail.Certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isSameCert = x509.EqualCertificate(cert, newCert)
|
isSameCert = x509.EqualCertificate(certX509, oldCertX509)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
@ -205,9 +205,6 @@ func getSdkProjectId(accessKeyId, secretAccessKey, region string) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := hcIam.NewIamClient(hcClient)
|
client := hcIam.NewIamClient(hcClient)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &hcIamModel.KeystoneListProjectsRequest{
|
request := &hcIamModel.KeystoneListProjectsRequest{
|
||||||
Name: ®ion,
|
Name: ®ion,
|
||||||
|
@ -92,12 +92,12 @@ func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, pri
|
|||||||
if *exportCertificateResp.Certificate == certPem {
|
if *exportCertificateResp.Certificate == certPem {
|
||||||
isSameCert = true
|
isSameCert = true
|
||||||
} else {
|
} else {
|
||||||
cert, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
|
oldCertX509, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isSameCert = x509.EqualCertificate(certX509, cert)
|
isSameCert = x509.EqualCertificate(certX509, oldCertX509)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
|
@ -2,6 +2,7 @@ package volcenginecdn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
@ -10,56 +11,57 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
veCdn "github.com/volcengine/volc-sdk-golang/service/cdn"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type VolcengineCDNUploaderConfig struct {
|
type VolcEngineCDNUploaderConfig struct {
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
AccessKeySecret string `json:"accessKeySecret"`
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolcengineCDNUploader struct {
|
type VolcEngineCDNUploader struct {
|
||||||
config *VolcengineCDNUploaderConfig
|
config *VolcEngineCDNUploaderConfig
|
||||||
sdkClient *cdn.CDN
|
sdkClient *veCdn.CDN
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ uploader.Uploader = (*VolcengineCDNUploader)(nil)
|
var _ uploader.Uploader = (*VolcEngineCDNUploader)(nil)
|
||||||
|
|
||||||
func New(config *VolcengineCDNUploaderConfig) (*VolcengineCDNUploader, error) {
|
func New(config *VolcEngineCDNUploaderConfig) (*VolcEngineCDNUploader, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil, errors.New("config is nil")
|
return nil, errors.New("config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := cdn.NewInstance()
|
client := veCdn.NewInstance()
|
||||||
client := instance.Client
|
client.Client.SetAccessKey(config.AccessKeyId)
|
||||||
client.SetAccessKey(config.AccessKeyId)
|
client.Client.SetSecretKey(config.AccessKeySecret)
|
||||||
client.SetSecretKey(config.AccessKeySecret)
|
|
||||||
|
|
||||||
return &VolcengineCDNUploader{
|
return &VolcEngineCDNUploader{
|
||||||
config: config,
|
config: config,
|
||||||
sdkClient: instance,
|
sdkClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
func (u *VolcEngineCDNUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询证书列表,避免重复上传
|
// 查询证书列表,避免重复上传
|
||||||
// REF: https://www.volcengine.com/docs/6454/125709
|
// REF: https://www.volcengine.com/docs/6454/125709
|
||||||
pageNum := int64(1)
|
listCertInfoPageNum := int64(1)
|
||||||
pageSize := int64(100)
|
listCertInfoPageSize := int64(100)
|
||||||
certSource := "volc_cert_center"
|
listCertInfoTotal := 0
|
||||||
listCertInfoReq := &cdn.ListCertInfoRequest{
|
listCertInfoReq := &veCdn.ListCertInfoRequest{
|
||||||
PageNum: &pageNum,
|
PageNum: cast.Int64Ptr(listCertInfoPageNum),
|
||||||
PageSize: &pageSize,
|
PageSize: cast.Int64Ptr(listCertInfoPageSize),
|
||||||
Source: certSource,
|
Source: "volc_cert_center",
|
||||||
}
|
}
|
||||||
searchTotal := 0
|
|
||||||
for {
|
for {
|
||||||
listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq)
|
listCertInfoResp, err := u.sdkClient.ListCertInfo(listCertInfoReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,8 +70,10 @@ func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, priv
|
|||||||
|
|
||||||
if listCertInfoResp.Result.CertInfo != nil {
|
if listCertInfoResp.Result.CertInfo != nil {
|
||||||
for _, certDetail := range listCertInfoResp.Result.CertInfo {
|
for _, certDetail := range listCertInfoResp.Result.CertInfo {
|
||||||
hash := sha256.Sum256(certX509.Raw)
|
fingerprintSha1 := sha1.Sum(certX509.Raw)
|
||||||
isSameCert := strings.EqualFold(hex.EncodeToString(hash[:]), certDetail.CertFingerprint.Sha256)
|
fingerprintSha256 := sha256.Sum256(certX509.Raw)
|
||||||
|
isSameCert := strings.EqualFold(hex.EncodeToString(fingerprintSha1[:]), certDetail.CertFingerprint.Sha1) &&
|
||||||
|
strings.EqualFold(hex.EncodeToString(fingerprintSha256[:]), certDetail.CertFingerprint.Sha256)
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &uploader.UploadResult{
|
return &uploader.UploadResult{
|
||||||
@ -80,24 +84,26 @@ func (u *VolcengineCDNUploader) Upload(ctx context.Context, certPem string, priv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTotal += len(listCertInfoResp.Result.CertInfo)
|
listCertInfoLen := len(listCertInfoResp.Result.CertInfo)
|
||||||
if int(listCertInfoResp.Result.Total) > searchTotal {
|
if listCertInfoLen < int(listCertInfoPageSize) || int(listCertInfoResp.Result.Total) <= listCertInfoTotal+listCertInfoLen {
|
||||||
pageNum++
|
|
||||||
} else {
|
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
listCertInfoPageNum++
|
||||||
|
listCertInfoTotal += listCertInfoLen
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成新证书名(需符合火山引擎命名规则)
|
// 生成新证书名(需符合火山引擎命名规则)
|
||||||
var certId, certName string
|
var certId, certName string
|
||||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
||||||
|
|
||||||
// 上传新证书
|
// 上传新证书
|
||||||
// REF: https://www.volcengine.com/docs/6454/1245763
|
// REF: https://www.volcengine.com/docs/6454/1245763
|
||||||
addCertificateReq := &cdn.AddCertificateRequest{
|
addCertificateReq := &veCdn.AddCertificateRequest{
|
||||||
Certificate: certPem,
|
Certificate: certPem,
|
||||||
PrivateKey: privkeyPem,
|
PrivateKey: privkeyPem,
|
||||||
Source: &certSource,
|
Source: cast.StringPtr("volc_cert_center"),
|
||||||
Desc: &certName,
|
Desc: cast.StringPtr(certName),
|
||||||
}
|
}
|
||||||
addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
addCertificateResp, err := u.sdkClient.AddCertificate(addCertificateReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,77 +8,79 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
|
veLive "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
||||||
live "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type VolcengineLiveUploaderConfig struct {
|
type VolcEngineLiveUploaderConfig struct {
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
AccessKeySecret string `json:"accessKeySecret"`
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolcengineLiveUploader struct {
|
type VolcEngineLiveUploader struct {
|
||||||
config *VolcengineLiveUploaderConfig
|
config *VolcEngineLiveUploaderConfig
|
||||||
sdkClient *live.Live
|
sdkClient *veLive.Live
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ uploader.Uploader = (*VolcengineLiveUploader)(nil)
|
var _ uploader.Uploader = (*VolcEngineLiveUploader)(nil)
|
||||||
|
|
||||||
func New(config *VolcengineLiveUploaderConfig) (*VolcengineLiveUploader, error) {
|
func New(config *VolcEngineLiveUploaderConfig) (*VolcEngineLiveUploader, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil, errors.New("config is nil")
|
return nil, errors.New("config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := live.NewInstance()
|
client := veLive.NewInstance()
|
||||||
client.SetAccessKey(config.AccessKeyId)
|
client.SetAccessKey(config.AccessKeyId)
|
||||||
client.SetSecretKey(config.AccessKeySecret)
|
client.SetSecretKey(config.AccessKeySecret)
|
||||||
|
|
||||||
return &VolcengineLiveUploader{
|
return &VolcEngineLiveUploader{
|
||||||
config: config,
|
config: config,
|
||||||
sdkClient: client,
|
sdkClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *VolcengineLiveUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
func (u *VolcEngineLiveUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
|
||||||
// 解析证书内容
|
// 解析证书内容
|
||||||
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
certX509, err := x509.ParseCertificateFromPEM(certPem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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 := &live.ListCertV2Body{}
|
listCertReq := &veLive.ListCertV2Body{}
|
||||||
listCertResp, err := u.sdkClient.ListCertV2(ctx, listCertReq)
|
listCertResp, err := u.sdkClient.ListCertV2(ctx, listCertReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListCertV2'")
|
return nil, xerrors.Wrap(err, "failed to execute sdk request 'live.ListCertV2'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listCertResp.Result.CertList != nil {
|
if listCertResp.Result.CertList != nil {
|
||||||
for _, certDetail := range listCertResp.Result.CertList {
|
for _, certDetail := range listCertResp.Result.CertList {
|
||||||
|
|
||||||
describeCertDetailSecretReq := &live.DescribeCertDetailSecretV2Body{
|
|
||||||
ChainID: cast.StringPtr(certDetail.ChainID),
|
|
||||||
}
|
|
||||||
// 查询证书详细信息
|
// 查询证书详细信息
|
||||||
// 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
|
// 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
|
||||||
describeCertDetailSecretResp, detailErr := u.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq)
|
describeCertDetailSecretReq := &veLive.DescribeCertDetailSecretV2Body{
|
||||||
if detailErr != nil {
|
ChainID: cast.StringPtr(certDetail.ChainID),
|
||||||
|
}
|
||||||
|
describeCertDetailSecretResp, err := u.sdkClient.DescribeCertDetailSecretV2(ctx, describeCertDetailSecretReq)
|
||||||
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSameCert bool
|
var isSameCert bool
|
||||||
certificate := strings.Join(describeCertDetailSecretResp.Result.SSL.Chain, "\n\n")
|
certificate := strings.Join(describeCertDetailSecretResp.Result.SSL.Chain, "\n\n")
|
||||||
if certificate == certPem {
|
if certificate == certPem {
|
||||||
isSameCert = true
|
isSameCert = true
|
||||||
} else {
|
} else {
|
||||||
cert, err := x509.ParseCertificateFromPEM(certificate)
|
oldCertX509, err := x509.ParseCertificateFromPEM(certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isSameCert = x509.EqualCertificate(cert, certX509)
|
isSameCert = x509.EqualCertificate(certX509, oldCertX509)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已存在相同证书,直接返回已有的证书信息
|
// 如果已存在相同证书,直接返回已有的证书信息
|
||||||
if isSameCert {
|
if isSameCert {
|
||||||
return &uploader.UploadResult{
|
return &uploader.UploadResult{
|
||||||
@ -92,13 +94,14 @@ func (u *VolcengineLiveUploader) Upload(ctx context.Context, certPem string, pri
|
|||||||
// 生成新证书名(需符合火山引擎命名规则)
|
// 生成新证书名(需符合火山引擎命名规则)
|
||||||
var certId, certName string
|
var certId, certName string
|
||||||
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
|
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
|
// REF: https://www.volcengine.com/docs/6469/1186278#%E6%B7%BB%E5%8A%A0%E8%AF%81%E4%B9%A6
|
||||||
createCertReq := &live.CreateCertBody{
|
createCertReq := &veLive.CreateCertBody{
|
||||||
CertName: &certName,
|
CertName: &certName,
|
||||||
UseWay: "https",
|
UseWay: "https",
|
||||||
ProjectName: cast.StringPtr("default"),
|
ProjectName: cast.StringPtr("default"),
|
||||||
Rsa: live.CreateCertBodyRsa{
|
Rsa: veLive.CreateCertBodyRsa{
|
||||||
Prikey: privkeyPem,
|
Prikey: privkeyPem,
|
||||||
Pubkey: certPem,
|
Pubkey: certPem,
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ func GetValueAsString(dict map[string]any, key string) string {
|
|||||||
// - defaultValue: 默认值。
|
// - defaultValue: 默认值。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回默认值。
|
// - 字典中键对应的值。如果指定键不存在、值的类型不是字符串或者值为零值,则返回默认值。
|
||||||
func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string {
|
func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue string) string {
|
||||||
if dict == nil {
|
if dict == nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
@ -30,7 +30,9 @@ func GetValueOrDefaultAsString(dict map[string]any, key string, defaultValue str
|
|||||||
|
|
||||||
if value, ok := dict[key]; ok {
|
if value, ok := dict[key]; ok {
|
||||||
if result, ok := value.(string); ok {
|
if result, ok := value.(string); ok {
|
||||||
return result
|
if result != "" {
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ func GetValueAsInt32(dict map[string]any, key string) int32 {
|
|||||||
// - defaultValue: 默认值。
|
// - defaultValue: 默认值。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回默认值。
|
// - 字典中键对应的值。如果指定键不存在、值的类型不是 32 位整数或者值为零值,则返回默认值。
|
||||||
func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 {
|
func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int32) int32 {
|
||||||
if dict == nil {
|
if dict == nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
@ -65,13 +67,17 @@ func GetValueOrDefaultAsInt32(dict map[string]any, key string, defaultValue int3
|
|||||||
|
|
||||||
if value, ok := dict[key]; ok {
|
if value, ok := dict[key]; ok {
|
||||||
if result, ok := value.(int32); ok {
|
if result, ok := value.(int32); ok {
|
||||||
return result
|
if result != 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容字符串类型的值
|
// 兼容字符串类型的值
|
||||||
if str, ok := value.(string); ok {
|
if str, ok := value.(string); ok {
|
||||||
if result, err := strconv.ParseInt(str, 10, 32); err == nil {
|
if result, err := strconv.ParseInt(str, 10, 32); err == nil {
|
||||||
return int32(result)
|
if result != 0 {
|
||||||
|
return int32(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,7 +105,7 @@ func GetValueAsInt64(dict map[string]any, key string) int64 {
|
|||||||
// - defaultValue: 默认值。
|
// - defaultValue: 默认值。
|
||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回默认值。
|
// - 字典中键对应的值。如果指定键不存在、值的类型不是 64 位整数或者值为零值,则返回默认值。
|
||||||
func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 {
|
func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int64) int64 {
|
||||||
if dict == nil {
|
if dict == nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
@ -107,13 +113,17 @@ func GetValueOrDefaultAsInt64(dict map[string]any, key string, defaultValue int6
|
|||||||
|
|
||||||
if value, ok := dict[key]; ok {
|
if value, ok := dict[key]; ok {
|
||||||
if result, ok := value.(int64); ok {
|
if result, ok := value.(int64); ok {
|
||||||
return result
|
if result != 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容字符串类型的值
|
// 兼容字符串类型的值
|
||||||
if str, ok := value.(string); ok {
|
if str, ok := value.(string); ok {
|
||||||
if result, err := strconv.ParseInt(str, 10, 64); err == nil {
|
if result, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||||
return result
|
if result != 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
internal/pkg/utils/x509/transformer.go
Normal file
87
internal/pkg/utils/x509/transformer.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package x509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 将 PEM 编码的证书字符串转换为 PFX 格式。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - certPem: 证书 PEM 内容。
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
// - pfxPassword: PFX 导出密码。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - data: PFX 格式的证书数据。
|
||||||
|
// - err: 错误。
|
||||||
|
func TransformCertificateFromPEMToPFX(certPem string, privkeyPem string, pfxPassword string) ([]byte, error) {
|
||||||
|
cert, err := ParseCertificateFromPEM(certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err := ParsePKCS1PrivateKeyFromPEM(privkeyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfxData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 PEM 编码的证书字符串转换为 JKS 格式。
|
||||||
|
//
|
||||||
|
// 入参:
|
||||||
|
// - certPem: 证书 PEM 内容。
|
||||||
|
// - privkeyPem: 私钥 PEM 内容。
|
||||||
|
// - jksAlias: JKS 别名。
|
||||||
|
// - jksKeypass: JKS 密钥密码。
|
||||||
|
// - jksStorepass: JKS 存储密码。
|
||||||
|
//
|
||||||
|
// 出参:
|
||||||
|
// - data: JKS 格式的证书数据。
|
||||||
|
// - err: 错误。
|
||||||
|
func TransformCertificateFromPEMToJKS(certPem string, privkeyPem string, jksAlias string, jksKeypass string, jksStorepass string) ([]byte, error) {
|
||||||
|
certBlock, _ := pem.Decode([]byte(certPem))
|
||||||
|
if certBlock == nil {
|
||||||
|
return nil, errors.New("failed to decode certificate PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
privkeyBlock, _ := pem.Decode([]byte(privkeyPem))
|
||||||
|
if privkeyBlock == nil {
|
||||||
|
return nil, errors.New("failed to decode private key PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
ks := keystore.New()
|
||||||
|
entry := keystore.PrivateKeyEntry{
|
||||||
|
CreationTime: time.Now(),
|
||||||
|
PrivateKey: privkeyBlock.Bytes,
|
||||||
|
CertificateChain: []keystore.Certificate{
|
||||||
|
{
|
||||||
|
Type: "X509",
|
||||||
|
Content: certBlock.Bytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ks.SetPrivateKeyEntry(jksAlias, entry, []byte(jksKeypass)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := ks.Store(&buf, []byte(jksStorepass)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user