mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-23 20:59:55 +00:00
refactor(deployer): reimplement deploy service
This commit is contained in:
parent
e2af21e0e1
commit
c846945905
internal/deployer
aliyun_alb.goaliyun_cdn.goaliyun_clb.goaliyun_dcdn.goaliyun_nlb.goaliyun_oss.gobaiducloud_cdn.gobyteplus_cdn.godeployer.godogecloud_cdn.gofactory.gohuaweicloud_cdn.gohuaweicloud_elb.gok8s_secret.golocal.goqiniu_cdn.gossh.gossh_test.gotencent_cdn.gotencent_clb.gotencent_cos.gotencent_ecdn.gotencent_teo.govolcengine_cdn.govolcengine_live.gowebhook.go
@ -1,281 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunALBDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *aliyunAlb.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunALBDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
|
||||||
if aliCasRegion != "" {
|
|
||||||
// 阿里云 CAS 服务接入点是独立于 ALB 服务的
|
|
||||||
// 国内版接入点:华东一杭州
|
|
||||||
// 国际版接入点:亚太东南一新加坡
|
|
||||||
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
|
||||||
aliCasRegion = "ap-southeast-1"
|
|
||||||
} else {
|
|
||||||
aliCasRegion = "cn-hangzhou"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
|
||||||
Region: aliCasRegion,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunALBDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) Deploy(ctx context.Context) error {
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
|
||||||
case "loadbalancer":
|
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "listener":
|
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported resource type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) {
|
|
||||||
if region == "" {
|
|
||||||
region = "cn-hangzhou" // ALB 服务默认区域:华东一杭州
|
|
||||||
}
|
|
||||||
|
|
||||||
aConfig := &aliyunOpen.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoint string
|
|
||||||
switch region {
|
|
||||||
case "cn-hangzhou-finance":
|
|
||||||
endpoint = "alb.cn-hangzhou.aliyuncs.com"
|
|
||||||
default:
|
|
||||||
endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region)
|
|
||||||
}
|
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
|
||||||
|
|
||||||
client, err := aliyunAlb.NewClient(aConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
|
||||||
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
if aliLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
aliListenerIds := 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(aliLoadbalancerId),
|
|
||||||
}
|
|
||||||
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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(aliLoadbalancerId)},
|
|
||||||
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 {
|
|
||||||
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if listListenersResp.Body.NextToken == nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
listListenersToken = listListenersResp.Body.NextToken
|
|
||||||
listListenersPage += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", aliListenerIds))
|
|
||||||
|
|
||||||
// 查询 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(aliLoadbalancerId)},
|
|
||||||
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 {
|
|
||||||
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if listListenersResp.Body.NextToken == nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
listListenersToken = listListenersResp.Body.NextToken
|
|
||||||
listListenersPage += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", aliListenerIds))
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 批量更新监听证书
|
|
||||||
var errs []error
|
|
||||||
for _, aliListenerId := range aliListenerIds {
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) deployToListener(ctx context.Context) error {
|
|
||||||
aliListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
if aliListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 更新监听
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId 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(aliListenerId),
|
|
||||||
}
|
|
||||||
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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(aliListenerId),
|
|
||||||
Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{
|
|
||||||
CertificateId: tea.String(aliCertId),
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 ALB 监听配置", updateListenerAttributeResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *aliyunCdn.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 设置 CDN 域名域名证书
|
|
||||||
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
|
|
||||||
setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{
|
|
||||||
DomainName: tea.String(d.option.DeployConfig.GetConfigAsString("domain")),
|
|
||||||
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
|
||||||
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
|
||||||
CertType: tea.String("upload"),
|
|
||||||
SSLProtocol: tea.String("on"),
|
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
|
||||||
}
|
|
||||||
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) {
|
|
||||||
aConfig := &aliyunOpen.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
Endpoint: tea.String("cdn.aliyuncs.com"),
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := aliyunCdn.NewClient(aConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,286 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunCLBDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *aliyunSlb.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunCLBDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunCLBDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderAliyunSlb.New(&uploaderAliyunSlb.AliyunSLBUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunCLBDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) Deploy(ctx context.Context) error {
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
|
||||||
case "loadbalancer":
|
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "listener":
|
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported resource type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) {
|
|
||||||
if region == "" {
|
|
||||||
region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州
|
|
||||||
}
|
|
||||||
|
|
||||||
aConfig := &aliyunOpen.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
|
||||||
|
|
||||||
client, err := aliyunSlb.NewClient(aConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
|
||||||
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
aliListenerPorts := make([]int32, 0)
|
|
||||||
if aliLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询负载均衡实例的详细信息
|
|
||||||
// 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.option.DeployConfig.GetConfigAsString("region")),
|
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
|
||||||
}
|
|
||||||
describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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.option.DeployConfig.GetConfigAsString("region")),
|
|
||||||
MaxResults: tea.Int32(listListenersLimit),
|
|
||||||
NextToken: listListenersToken,
|
|
||||||
LoadBalancerId: []*string{tea.String(aliLoadbalancerId)},
|
|
||||||
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 {
|
|
||||||
aliListenerPorts = append(aliListenerPorts, *listener.ListenerPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if describeLoadBalancerListenersResp.Body.NextToken == nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
listListenersToken = describeLoadBalancerListenersResp.Body.NextToken
|
|
||||||
listListenersPage += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", aliListenerPorts))
|
|
||||||
|
|
||||||
// 上传证书到 SLB
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 批量更新监听证书
|
|
||||||
var errs []error
|
|
||||||
for _, aliListenerPort := range aliListenerPorts {
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) deployToListener(ctx context.Context) error {
|
|
||||||
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
if aliLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
aliListenerPort := d.option.DeployConfig.GetConfigAsInt32("listenerPort")
|
|
||||||
if aliListenerPort == 0 {
|
|
||||||
return errors.New("`listenerPort` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SLB
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 更新监听
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliLoadbalancerId, aliListenerPort, upres.CertId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, aliLoadbalancerId string, aliListenerPort int32, aliCertId 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(aliLoadbalancerId),
|
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
|
||||||
}
|
|
||||||
describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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.option.DeployConfig.GetConfigAsString("region")),
|
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
|
||||||
}
|
|
||||||
describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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.option.DeployConfig.GetConfigAsString("region")),
|
|
||||||
DomainExtensionId: tea.String(*domainExtension.DomainExtensionId),
|
|
||||||
ServerCertificateId: tea.String(aliCertId),
|
|
||||||
}
|
|
||||||
_, 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.option.DeployConfig.GetConfigAsString("region")),
|
|
||||||
LoadBalancerId: tea.String(aliLoadbalancerId),
|
|
||||||
ListenerPort: tea.Int32(aliListenerPort),
|
|
||||||
ServerCertificateId: tea.String(aliCertId),
|
|
||||||
}
|
|
||||||
setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunDCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *aliyunDcdn.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunDCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunDCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunDCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunDCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunDCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunDCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 支持泛解析域名,在 Aliyun DCDN 中泛解析域名表示为 .example.com
|
|
||||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domain = strings.TrimPrefix(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),
|
|
||||||
CertRegion: tea.String(d.option.DeployConfig.GetConfigOrDefaultAsString("region", "cn-hangzhou")),
|
|
||||||
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
|
|
||||||
CertType: tea.String("upload"),
|
|
||||||
SSLProtocol: tea.String("on"),
|
|
||||||
SSLPub: tea.String(d.option.Certificate.Certificate),
|
|
||||||
SSLPri: tea.String(d.option.Certificate.PrivateKey),
|
|
||||||
}
|
|
||||||
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunDCDNDeployer) createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) {
|
|
||||||
aConfig := &aliyunOpen.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
Endpoint: tea.String("dcdn.aliyuncs.com"),
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := aliyunDcdn.NewClient(aConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,245 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunNLBDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *aliyunNlb.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunNLBDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
aliCasRegion := option.DeployConfig.GetConfigAsString("region")
|
|
||||||
if aliCasRegion != "" {
|
|
||||||
// 阿里云 CAS 服务接入点是独立于 NLB 服务的
|
|
||||||
// 国内版接入点:华东一杭州
|
|
||||||
// 国际版接入点:亚太东南一新加坡
|
|
||||||
if !strings.HasPrefix(aliCasRegion, "cn-") {
|
|
||||||
aliCasRegion = "ap-southeast-1"
|
|
||||||
} else {
|
|
||||||
aliCasRegion = "cn-hangzhou"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uploader, err := uploaderAliyunCas.New(&uploaderAliyunCas.AliyunCASUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
AccessKeySecret: access.AccessKeySecret,
|
|
||||||
Region: aliCasRegion,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunNLBDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) Deploy(ctx context.Context) error {
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
|
||||||
case "loadbalancer":
|
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "listener":
|
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported resource type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) {
|
|
||||||
if region == "" {
|
|
||||||
region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
|
|
||||||
}
|
|
||||||
|
|
||||||
aConfig := &aliyunOpen.Config{
|
|
||||||
AccessKeyId: tea.String(accessKeyId),
|
|
||||||
AccessKeySecret: tea.String(accessKeySecret),
|
|
||||||
}
|
|
||||||
|
|
||||||
var endpoint string
|
|
||||||
switch region {
|
|
||||||
default:
|
|
||||||
endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region)
|
|
||||||
}
|
|
||||||
aConfig.Endpoint = tea.String(endpoint)
|
|
||||||
|
|
||||||
client, err := aliyunNlb.NewClient(aConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
|
||||||
aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
if aliLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
aliListenerIds := 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(aliLoadbalancerId),
|
|
||||||
}
|
|
||||||
getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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(aliLoadbalancerId)},
|
|
||||||
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 {
|
|
||||||
aliListenerIds = append(aliListenerIds, *listener.ListenerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if listListenersResp.Body.NextToken == nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
listListenersToken = listListenersResp.Body.NextToken
|
|
||||||
listListenersPage += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", aliListenerIds))
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 批量更新监听证书
|
|
||||||
var errs []error
|
|
||||||
for _, aliListenerId := range aliListenerIds {
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) deployToListener(ctx context.Context) error {
|
|
||||||
aliListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
if aliListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 更新监听
|
|
||||||
if err := d.updateListenerCertificate(ctx, aliListenerId, upres.CertId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId 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(aliListenerId),
|
|
||||||
}
|
|
||||||
getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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(aliListenerId),
|
|
||||||
CertificateIds: []*string{tea.String(aliCertId)},
|
|
||||||
}
|
|
||||||
updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliyunOSSDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *oss.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.AliyunAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&AliyunOSSDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.AccessKeySecret,
|
|
||||||
option.DeployConfig.GetConfigAsString("endpoint"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AliyunOSSDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
|
|
||||||
aliBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
|
||||||
if aliBucket == "" {
|
|
||||||
return errors.New("`bucket` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为存储空间绑定自定义域名
|
|
||||||
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
|
|
||||||
err := d.sdkClient.PutBucketCnameWithCertificate(aliBucket, oss.PutBucketCname{
|
|
||||||
Cname: d.option.DeployConfig.GetConfigAsString("domain"),
|
|
||||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
|
||||||
Certificate: d.option.Certificate.Certificate,
|
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
|
||||||
Force: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *AliyunOSSDeployer) createSdkClient(accessKeyId, accessKeySecret, endpoint string) (*oss.Client, error) {
|
|
||||||
if endpoint == "" {
|
|
||||||
endpoint = "oss.aliyuncs.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaiduCloudCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *bceCdn.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBaiduCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.BaiduCloudAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&BaiduCloudCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.SecretAccessKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &BaiduCloudCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *BaiduCloudCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *BaiduCloudCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *BaiduCloudCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 修改域名证书
|
|
||||||
// REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8
|
|
||||||
putCertResp, err := d.sdkClient.PutCert(
|
|
||||||
d.option.DeployConfig.GetConfigAsString("domain"),
|
|
||||||
&bceCdnApi.UserCertificate{
|
|
||||||
CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()),
|
|
||||||
ServerData: d.option.Certificate.Certificate,
|
|
||||||
PrivateData: d.option.Certificate.PrivateKey,
|
|
||||||
},
|
|
||||||
"ON",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.PutCert'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已修改域名证书", putCertResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *BaiduCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey string) (*bceCdn.Client, error) {
|
|
||||||
client, err := bceCdn.NewClient(accessKeyId, secretAccessKey, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
bytepluscdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/byteplus-cdn"
|
|
||||||
|
|
||||||
"github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn"
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ByteplusCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
sdkClient *cdn.CDN
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewByteplusCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.ByteplusAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
client := cdn.NewInstance()
|
|
||||||
client.Client.SetAccessKey(access.AccessKey)
|
|
||||||
client.Client.SetSecretKey(access.SecretKey)
|
|
||||||
uploader, err := bytepluscdn.New(&bytepluscdn.ByteplusCDNUploaderConfig{
|
|
||||||
AccessKey: access.AccessKey,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
return &ByteplusCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ByteplusCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ByteplusCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ByteplusCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
apiCtx := context.Background()
|
|
||||||
// 上传证书
|
|
||||||
upres, err := d.sslUploader.Upload(apiCtx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
domains := make([]string, 0)
|
|
||||||
configDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(configDomain, "*.") {
|
|
||||||
// 获取证书可以部署的域名
|
|
||||||
// REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17
|
|
||||||
describeCertConfigReq := &cdn.DescribeCertConfigRequest{
|
|
||||||
CertId: upres.CertId,
|
|
||||||
}
|
|
||||||
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.CertNotConfig {
|
|
||||||
// 当前未启用 HTTPS 的加速域名列表。
|
|
||||||
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
|
||||||
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联的证书不是您指定的证书。
|
|
||||||
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.SpecifiedCertConfig {
|
|
||||||
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联了您指定的证书。
|
|
||||||
d.infos = append(d.infos, fmt.Sprintf("%s域名已配置该证书", describeCertConfigResp.Result.SpecifiedCertConfig[i].Domain))
|
|
||||||
}
|
|
||||||
if len(domains) == 0 {
|
|
||||||
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
|
||||||
// 所有匹配的域名都配置了该证书,跳过部署
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
domains = append(domains, configDomain)
|
|
||||||
}
|
|
||||||
// 部署证书
|
|
||||||
// REF: https://github.com/byteplus-sdk/byteplus-sdk-golang/blob/master/service/cdn/api_list.go#L306
|
|
||||||
for i := range domains {
|
|
||||||
batchDeployCertReq := &cdn.BatchDeployCertRequest{
|
|
||||||
CertId: upres.CertId,
|
|
||||||
Domain: domains[i],
|
|
||||||
}
|
|
||||||
batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BatchDeployCert'")
|
|
||||||
} else {
|
|
||||||
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), batchDeployCertResp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -2,14 +2,13 @@ package deployer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
|
||||||
"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/core/deployer"
|
||||||
"github.com/usual2970/certimate/internal/repository"
|
"github.com/usual2970/certimate/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, deployConfig := range deployConfigs {
|
for _, deployConfig := range deployConfigs {
|
||||||
deployer, err := getWithDeployConfig(record, cert, deployConfig)
|
deployer, err := newWithDeployConfig(record, cert, deployConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -91,10 +90,10 @@ func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetWithTypeAndOption(deployType string, option *DeployerOption) (Deployer, error) {
|
func GetWithTypeAndOption(deployType string, option *DeployerOption) (Deployer, error) {
|
||||||
return getWithTypeAndOption(deployType, option)
|
return newWithTypeAndOption(deployType, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
|
func newWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
|
||||||
accessRepo := repository.NewAccessRepository()
|
accessRepo := repository.NewAccessRepository()
|
||||||
access, err := accessRepo.GetById(context.Background(), deployConfig.Access)
|
access, err := accessRepo.GetById(context.Background(), deployConfig.Access)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,65 +116,38 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return getWithTypeAndOption(deployConfig.Type, option)
|
return newWithTypeAndOption(deployConfig.Type, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWithTypeAndOption(deployType string, option *DeployerOption) (Deployer, error) {
|
func newWithTypeAndOption(deployType string, option *DeployerOption) (Deployer, error) {
|
||||||
switch deployType {
|
deployer, logger, err := createDeployer(deployType, option.AccessRecord.Config, option.DeployConfig.Config)
|
||||||
case targetAliyunOSS:
|
if err != nil {
|
||||||
return NewAliyunOSSDeployer(option)
|
return nil, err
|
||||||
case targetAliyunCDN:
|
|
||||||
return NewAliyunCDNDeployer(option)
|
|
||||||
case targetAliyunDCDN:
|
|
||||||
return NewAliyunDCDNDeployer(option)
|
|
||||||
case targetAliyunCLB:
|
|
||||||
return NewAliyunCLBDeployer(option)
|
|
||||||
case targetAliyunALB:
|
|
||||||
return NewAliyunALBDeployer(option)
|
|
||||||
case targetAliyunNLB:
|
|
||||||
return NewAliyunNLBDeployer(option)
|
|
||||||
case targetTencentCloudCDN:
|
|
||||||
return NewTencentCDNDeployer(option)
|
|
||||||
case targetTencentCloudECDN:
|
|
||||||
return NewTencentECDNDeployer(option)
|
|
||||||
case targetTencentCloudCLB:
|
|
||||||
return NewTencentCLBDeployer(option)
|
|
||||||
case targetTencentCloudCOS:
|
|
||||||
return NewTencentCOSDeployer(option)
|
|
||||||
case targetTencentCloudEO:
|
|
||||||
return NewTencentTEODeployer(option)
|
|
||||||
case targetHuaweiCloudCDN:
|
|
||||||
return NewHuaweiCloudCDNDeployer(option)
|
|
||||||
case targetHuaweiCloudELB:
|
|
||||||
return NewHuaweiCloudELBDeployer(option)
|
|
||||||
case targetBaiduCloudCDN:
|
|
||||||
return NewBaiduCloudCDNDeployer(option)
|
|
||||||
case targetQiniuCDN:
|
|
||||||
return NewQiniuCDNDeployer(option)
|
|
||||||
case targetDogeCloudCDN:
|
|
||||||
return NewDogeCloudCDNDeployer(option)
|
|
||||||
case targetLocal:
|
|
||||||
return NewLocalDeployer(option)
|
|
||||||
case targetSSH:
|
|
||||||
return NewSSHDeployer(option)
|
|
||||||
case targetWebhook:
|
|
||||||
return NewWebhookDeployer(option)
|
|
||||||
case targetK8sSecret:
|
|
||||||
return NewK8sSecretDeployer(option)
|
|
||||||
case targetVolcEngineLive:
|
|
||||||
return NewVolcengineLiveDeployer(option)
|
|
||||||
case targetVolcEngineCDN:
|
|
||||||
return NewVolcengineCDNDeployer(option)
|
|
||||||
case targetBytePlusCDN:
|
|
||||||
return NewByteplusCDNDeployer(option)
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("unsupported deploy target")
|
|
||||||
|
return &proxyDeployer{
|
||||||
|
option: option,
|
||||||
|
logger: logger,
|
||||||
|
deployer: deployer,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toStr(tag string, data any) string {
|
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
|
||||||
if data == nil {
|
type proxyDeployer struct {
|
||||||
return tag
|
option *DeployerOption
|
||||||
}
|
logger deployer.Logger
|
||||||
byts, _ := json.Marshal(data)
|
deployer deployer.Deployer
|
||||||
return tag + ":" + string(byts)
|
}
|
||||||
|
|
||||||
|
func (d *proxyDeployer) GetID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *proxyDeployer) GetInfos() []string {
|
||||||
|
return d.logger.GetRecords()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *proxyDeployer) Deploy(ctx context.Context) error {
|
||||||
|
_, err := d.deployer.Deploy(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderDoge "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/dogecloud"
|
|
||||||
doge "github.com/usual2970/certimate/internal/pkg/vendors/dogecloud-sdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DogeCloudCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *doge.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDogeCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.DogeCloudAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&DogeCloudCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKey,
|
|
||||||
access.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderDoge.New(&uploaderDoge.DogeCloudUploaderConfig{
|
|
||||||
AccessKey: access.AccessKey,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DogeCloudCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DogeCloudCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DogeCloudCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DogeCloudCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 上传证书到 CDN
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 绑定证书
|
|
||||||
// REF: https://docs.dogecloud.com/cdn/api-cert-bind
|
|
||||||
bindCdnCertId, _ := strconv.ParseInt(upres.CertId, 10, 64)
|
|
||||||
bindCdnCertResp, err := d.sdkClient.BindCdnCertWithDomain(bindCdnCertId, d.option.DeployConfig.GetConfigAsString("domain"))
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BindCdnCert'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已绑定证书", bindCdnCertResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DogeCloudCDNDeployer) createSdkClient(accessKey, secretKey string) (*doge.Client, error) {
|
|
||||||
client := doge.NewClient(accessKey, secretKey)
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -362,9 +362,23 @@ func createDeployer(target string, accessConfig string, deployConfig map[string]
|
|||||||
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
return nil, nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variables := make(map[string]string)
|
||||||
|
if deployConfig != nil {
|
||||||
|
value, ok := deployConfig["variables"]
|
||||||
|
if ok {
|
||||||
|
kvs := make([]domain.KV, 0)
|
||||||
|
bts, _ := json.Marshal(value)
|
||||||
|
if err := json.Unmarshal(bts, &kvs); err == nil {
|
||||||
|
for _, kv := range kvs {
|
||||||
|
variables[kv.Key] = kv.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{
|
deployer, err := providerWebhook.NewWithLogger(&providerWebhook.WebhookDeployerConfig{
|
||||||
Url: access.Url,
|
Url: access.Url,
|
||||||
Variables: nil, // TODO: 尚未实现
|
Variables: variables,
|
||||||
}, logger)
|
}, logger)
|
||||||
return deployer, logger, err
|
return deployer, logger, err
|
||||||
}
|
}
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderHcScm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-scm"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
|
||||||
hcCdnEx "github.com/usual2970/certimate/internal/pkg/vendors/huaweicloud-cdn-sdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HuaweiCloudCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *hcCdnEx.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.HuaweiCloudAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&HuaweiCloudCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.SecretAccessKey,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderHcScm.New(&uploaderHcScm.HuaweiCloudSCMUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
|
||||||
Region: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &HuaweiCloudCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 上传证书到 SCM
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 查询加速域名配置
|
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
|
|
||||||
showDomainFullConfigReq := &hcCdnModel.ShowDomainFullConfigRequest{
|
|
||||||
DomainName: d.option.DeployConfig.GetConfigAsString("domain"),
|
|
||||||
}
|
|
||||||
showDomainFullConfigResp, err := d.sdkClient.ShowDomainFullConfig(showDomainFullConfigReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.ShowDomainFullConfig'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
|
|
||||||
|
|
||||||
// 更新加速域名配置
|
|
||||||
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
|
|
||||||
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
|
|
||||||
updateDomainMultiCertificatesReqBodyContent := &hcCdnEx.UpdateDomainMultiCertificatesExRequestBodyContent{}
|
|
||||||
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("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 := &hcCdnEx.UpdateDomainMultiCertificatesExRequest{
|
|
||||||
Body: &hcCdnEx.UpdateDomainMultiCertificatesExRequestBody{
|
|
||||||
Https: updateDomainMultiCertificatesReqBodyContent,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
updateDomainMultiCertificatesResp, err := d.sdkClient.UploadDomainMultiCertificatesEx(updateDomainMultiCertificatesReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.UploadDomainMultiCertificatesEx'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudCDNDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcCdnEx.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 := hcCdnEx.NewClient(hcClient)
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,378 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderHcElb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/huaweicloud-elb"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HuaweiCloudELBDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *hcElb.ElbClient
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHuaweiCloudELBDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.HuaweiCloudAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&HuaweiCloudELBDeployer{}).createSdkClient(
|
|
||||||
access.AccessKeyId,
|
|
||||||
access.SecretAccessKey,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderHcElb.New(&uploaderHcElb.HuaweiCloudELBUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
|
||||||
Region: option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &HuaweiCloudELBDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) Deploy(ctx context.Context) error {
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
|
||||||
case "certificate":
|
|
||||||
// 部署到指定证书
|
|
||||||
if err := d.deployToCertificate(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "loadbalancer":
|
|
||||||
// 部署到指定负载均衡器
|
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "listener":
|
|
||||||
// 部署到指定监听器
|
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported resource type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) createSdkClient(accessKeyId, secretAccessKey, region string) (*hcElb.ElbClient, error) {
|
|
||||||
if region == "" {
|
|
||||||
region = "cn-north-4" // ELB 服务默认区域:华北四北京
|
|
||||||
}
|
|
||||||
|
|
||||||
projectId, err := (&HuaweiCloudELBDeployer{}).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 (u *HuaweiCloudELBDeployer) 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) deployToCertificate(ctx context.Context) error {
|
|
||||||
hcCertId := d.option.DeployConfig.GetConfigAsString("certificateId")
|
|
||||||
if hcCertId == "" {
|
|
||||||
return errors.New("`certificateId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新证书
|
|
||||||
// REF: https://support.huaweicloud.com/api-elb/UpdateCertificate.html
|
|
||||||
updateCertificateReq := &hcElbModel.UpdateCertificateRequest{
|
|
||||||
CertificateId: hcCertId,
|
|
||||||
Body: &hcElbModel.UpdateCertificateRequestBody{
|
|
||||||
Certificate: &hcElbModel.UpdateCertificateOption{
|
|
||||||
Certificate: cast.StringPtr(d.option.Certificate.Certificate),
|
|
||||||
PrivateKey: cast.StringPtr(d.option.Certificate.PrivateKey),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'elb.UpdateCertificate'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已更新 ELB 证书", updateCertificateResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
|
||||||
hcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
if hcLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
hcListenerIds := make([]string, 0)
|
|
||||||
|
|
||||||
// 查询负载均衡器详情
|
|
||||||
// REF: https://support.huaweicloud.com/api-elb/ShowLoadBalancer.html
|
|
||||||
showLoadBalancerReq := &hcElbModel.ShowLoadBalancerRequest{
|
|
||||||
LoadbalancerId: hcLoadbalancerId,
|
|
||||||
}
|
|
||||||
showLoadBalancerResp, err := d.sdkClient.ShowLoadBalancer(showLoadBalancerReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowLoadBalancer'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 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 {
|
|
||||||
hcListenerIds = append(hcListenerIds, listener.Id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if listListenersResp.Listeners == nil || len(*listListenersResp.Listeners) < int(listListenersLimit) {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
listListenersMarker = listListenersResp.PageInfo.NextMarker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器下的监听器", hcListenerIds))
|
|
||||||
|
|
||||||
// 上传证书到 SCM
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 批量更新监听器证书
|
|
||||||
var errs []error
|
|
||||||
for _, hcListenerId := range hcListenerIds {
|
|
||||||
if err := d.modifyListenerCertificate(ctx, hcListenerId, 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) error {
|
|
||||||
hcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
if hcListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SCM
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 更新监听器证书
|
|
||||||
if err := d.modifyListenerCertificate(ctx, hcListenerId, upres.CertId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HuaweiCloudELBDeployer) modifyListenerCertificate(ctx context.Context, hcListenerId string, hcCertId string) error {
|
|
||||||
// 查询监听器详情
|
|
||||||
// REF: https://support.huaweicloud.com/api-elb/ShowListener.html
|
|
||||||
showListenerReq := &hcElbModel.ShowListenerRequest{
|
|
||||||
ListenerId: hcListenerId,
|
|
||||||
}
|
|
||||||
showListenerResp, err := d.sdkClient.ShowListener(showListenerReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'elb.ShowListener'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到 ELB 监听器", showListenerResp))
|
|
||||||
|
|
||||||
// 更新监听器
|
|
||||||
// REF: https://support.huaweicloud.com/api-elb/UpdateListener.html
|
|
||||||
updateListenerReq := &hcElbModel.UpdateListenerRequest{
|
|
||||||
ListenerId: hcListenerId,
|
|
||||||
Body: &hcElbModel.UpdateListenerRequestBody{
|
|
||||||
Listener: &hcElbModel.UpdateListenerOption{
|
|
||||||
DefaultTlsContainerRef: cast.StringPtr(hcCertId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if showListenerResp.Listener.SniContainerRefs != nil {
|
|
||||||
if len(showListenerResp.Listener.SniContainerRefs) > 0 {
|
|
||||||
// 如果开启 SNI,需替换同 SAN 的证书
|
|
||||||
sniCertIds := make([]string, 0)
|
|
||||||
sniCertIds = append(sniCertIds, hcCertId)
|
|
||||||
|
|
||||||
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: hcCertId,
|
|
||||||
}
|
|
||||||
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.infos = append(d.infos, toStr("已更新 ELB 监听器", updateListenerResp))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
|
||||||
)
|
|
||||||
|
|
||||||
type K8sSecretDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
k8sClient *kubernetes.Clientset
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.KubernetesAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&K8sSecretDeployer{}).createK8sClient(access)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create k8s client")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &K8sSecretDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
k8sClient: client,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
|
|
||||||
namespace := d.option.DeployConfig.GetConfigAsString("namespace")
|
|
||||||
secretName := d.option.DeployConfig.GetConfigAsString("secretName")
|
|
||||||
secretDataKeyForCrt := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForCrt", "tls.crt")
|
|
||||||
secretDataKeyForKey := d.option.DeployConfig.GetConfigOrDefaultAsString("secretDataKeyForKey", "tls.key")
|
|
||||||
if namespace == "" {
|
|
||||||
namespace = "default"
|
|
||||||
}
|
|
||||||
if secretName == "" {
|
|
||||||
return errors.New("`secretName` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
certX509, err := x509.ParseCertificateFromPEM(d.option.Certificate.Certificate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
secretPayload := k8sCore.Secret{
|
|
||||||
TypeMeta: k8sMeta.TypeMeta{
|
|
||||||
Kind: "Secret",
|
|
||||||
APIVersion: "v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: k8sMeta.ObjectMeta{
|
|
||||||
Name: secretName,
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"certimate/domains": d.option.Domain,
|
|
||||||
"certimate/alt-names": strings.Join(certX509.DNSNames, ","),
|
|
||||||
"certimate/common-name": certX509.Subject.CommonName,
|
|
||||||
"certimate/issuer-organization": strings.Join(certX509.Issuer.Organization, ","),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: k8sCore.SecretType("kubernetes.io/tls"),
|
|
||||||
}
|
|
||||||
secretPayload.Data = make(map[string][]byte)
|
|
||||||
secretPayload.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
|
|
||||||
secretPayload.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
|
|
||||||
|
|
||||||
// 获取 Secret 实例
|
|
||||||
_, err = d.k8sClient.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMeta.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
_, err = d.k8sClient.CoreV1().Secrets(namespace).Create(context.TODO(), &secretPayload, k8sMeta.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to create k8s secret")
|
|
||||||
} else {
|
|
||||||
d.infos = append(d.infos, toStr("Certificate has been created in K8s Secret", nil))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新 Secret 实例
|
|
||||||
_, err = d.k8sClient.CoreV1().Secrets(namespace).Update(context.TODO(), &secretPayload, k8sMeta.UpdateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to update k8s secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("Certificate has been updated to K8s Secret", nil))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *K8sSecretDeployer) createK8sClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
|
|
||||||
var config *rest.Config
|
|
||||||
var err error
|
|
||||||
if access.KubeConfig == "" {
|
|
||||||
config, err = rest.InClusterConfig()
|
|
||||||
} else {
|
|
||||||
kubeConfig, err := clientcmd.NewClientConfigFromBytes([]byte(access.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
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/fs"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LocalDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
certFormatPEM = "pem"
|
|
||||||
certFormatPFX = "pfx"
|
|
||||||
certFormatJKS = "jks"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
shellEnvSh = "sh"
|
|
||||||
shellEnvCmd = "cmd"
|
|
||||||
shellEnvPowershell = "powershell"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
return &LocalDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *LocalDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *LocalDeployer) GetInfos() []string {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 执行前置命令
|
|
||||||
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
|
||||||
if preCommand != "" {
|
|
||||||
stdout, stderr, err := d.execCommand(preCommand)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrapf(err, "failed to run pre-command, stdout: %s, stderr: %s", stdout, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("执行前置命令成功", stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入证书和私钥文件
|
|
||||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
|
||||||
case certFormatPEM:
|
|
||||||
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
|
||||||
|
|
||||||
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
|
||||||
|
|
||||||
case certFormatPFX:
|
|
||||||
pfxData, err := x509.TransformCertificateFromPEMToPFX(
|
|
||||||
d.option.Certificate.Certificate,
|
|
||||||
d.option.Certificate.PrivateKey,
|
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
|
||||||
|
|
||||||
case certFormatJKS:
|
|
||||||
jksData, err := x509.TransformCertificateFromPEMToJKS(
|
|
||||||
d.option.Certificate.Certificate,
|
|
||||||
d.option.Certificate.PrivateKey,
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksKeypass"),
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported format")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行命令
|
|
||||||
command := d.option.DeployConfig.GetConfigAsString("command")
|
|
||||||
if command != "" {
|
|
||||||
stdout, stderr, err := d.execCommand(command)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("执行命令成功", stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *LocalDeployer) execCommand(command string) (string, string, error) {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("shell") {
|
|
||||||
case shellEnvSh:
|
|
||||||
cmd = exec.Command("sh", "-c", command)
|
|
||||||
|
|
||||||
case shellEnvCmd:
|
|
||||||
cmd = exec.Command("cmd", "/C", command)
|
|
||||||
|
|
||||||
case shellEnvPowershell:
|
|
||||||
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 "", "", errors.New("unsupported shell")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 script")
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdoutBuf.String(), stderrBuf.String(), nil
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
"github.com/qiniu/go-sdk/v7/auth"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderQiniu "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/qiniu-sslcert"
|
|
||||||
qiniuEx "github.com/usual2970/certimate/internal/pkg/vendors/qiniu-sdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
type QiniuCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *qiniuEx.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewQiniuCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.QiniuAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&QiniuCDNDeployer{}).createSdkClient(
|
|
||||||
access.AccessKey,
|
|
||||||
access.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderQiniu.New(&uploaderQiniu.QiniuSSLCertUploaderConfig{
|
|
||||||
AccessKey: access.AccessKey,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &QiniuCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 上传证书
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 在七牛 CDN 中泛域名表示为 .example.com,需去除前缀星号
|
|
||||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domain = strings.TrimPrefix(domain, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取域名信息
|
|
||||||
// REF: https://developer.qiniu.com/fusion/4246/the-domain-name
|
|
||||||
getDomainInfoResp, err := d.sdkClient.GetDomainInfo(domain)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.GetDomainInfo'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已获取域名信息", 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 xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已修改域名证书", modifyDomainHttpsConfResp))
|
|
||||||
} else {
|
|
||||||
enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(domain, upres.CertId, true, true)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已将域名升级为 HTTPS", enableDomainHttpsResp))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *QiniuCDNDeployer) createSdkClient(accessKey, secretKey string) (*qiniuEx.Client, error) {
|
|
||||||
credential := auth.New(accessKey, secretKey)
|
|
||||||
client := qiniuEx.NewClient(credential)
|
|
||||||
return client, nil
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/x509"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SSHDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSSHDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
return &SSHDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
|
||||||
access := &domain.SSHAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接
|
|
||||||
client, err := d.createSshClient(access)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 连接成功", nil))
|
|
||||||
|
|
||||||
// 执行前置命令
|
|
||||||
preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
|
|
||||||
if preCommand != "" {
|
|
||||||
stdout, stderr, err := d.sshExecCommand(client, preCommand)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrapf(err, "failed to run pre-command: stdout: %s, stderr: %s", stdout, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书和私钥文件
|
|
||||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", certFormatPEM) {
|
|
||||||
case certFormatPEM:
|
|
||||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
|
||||||
|
|
||||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
|
||||||
|
|
||||||
case certFormatPFX:
|
|
||||||
pfxData, err := x509.TransformCertificateFromPEMToPFX(
|
|
||||||
d.option.Certificate.Certificate,
|
|
||||||
d.option.Certificate.PrivateKey,
|
|
||||||
d.option.DeployConfig.GetConfigAsString("pfxPassword"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
|
||||||
|
|
||||||
case certFormatJKS:
|
|
||||||
jksData, err := x509.TransformCertificateFromPEMToJKS(
|
|
||||||
d.option.Certificate.Certificate,
|
|
||||||
d.option.Certificate.PrivateKey,
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksAlias"),
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksKeypass"),
|
|
||||||
d.option.DeployConfig.GetConfigAsString("jksStorepass"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), jksData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported format")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行命令
|
|
||||||
command := d.option.DeployConfig.GetConfigAsString("command")
|
|
||||||
if command != "" {
|
|
||||||
stdout, stderr, err := d.sshExecCommand(client, command)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrapf(err, "failed to run command, stdout: %s, stderr: %s", stdout, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("SSH 执行命令成功", stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) createSshClient(access *domain.SSHAccess) (*ssh.Client, error) {
|
|
||||||
var authMethod ssh.AuthMethod
|
|
||||||
|
|
||||||
if access.Key != "" {
|
|
||||||
var signer ssh.Signer
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if access.KeyPassphrase != "" {
|
|
||||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
|
|
||||||
} else {
|
|
||||||
signer, err = ssh.ParsePrivateKey([]byte(access.Key))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
authMethod = ssh.PublicKeys(signer)
|
|
||||||
} else {
|
|
||||||
authMethod = ssh.Password(access.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ssh.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &ssh.ClientConfig{
|
|
||||||
User: access.Username,
|
|
||||||
Auth: []ssh.AuthMethod{
|
|
||||||
authMethod,
|
|
||||||
},
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) sshExecCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
|
||||||
session, err := sshCli.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", xerrors.Wrap(err, "failed to create ssh session")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "", "", xerrors.Wrap(err, "failed to execute ssh script")
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdoutBuf.String(), stderrBuf.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
|
||||||
return d.writeSftpFile(sshCli, path, []byte(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSHDeployer) 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
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPath(t *testing.T) {
|
|
||||||
dir := path.Dir("./a/b/c")
|
|
||||||
os.MkdirAll(dir, 0o755)
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClients *tencentCDNDeployerSdkClients
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
type tencentCDNDeployerSdkClients struct {
|
|
||||||
ssl *tcSsl.Client
|
|
||||||
cdn *tcCdn.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := (&TencentCDNDeployer{}).createSdkClients(
|
|
||||||
access.SecretId,
|
|
||||||
access.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
|
||||||
SecretId: access.SecretId,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClients: clients,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 获取待部署的 CDN 实例
|
|
||||||
// 如果是泛域名,根据证书匹配 CDN 实例
|
|
||||||
tcInstanceIds := make([]string, 0)
|
|
||||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tcInstanceIds = domains
|
|
||||||
} else {
|
|
||||||
tcInstanceIds = append(tcInstanceIds, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳过已部署的 CDN 实例
|
|
||||||
if len(tcInstanceIds) > 0 {
|
|
||||||
deployedDomains, err := d.getDeployedDomainsByCertificateId(upres.CertId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
temp := make([]string, 0)
|
|
||||||
for _, tcInstanceId := range tcInstanceIds {
|
|
||||||
if !slices.Contains(deployedDomains, tcInstanceId) {
|
|
||||||
temp = append(temp, tcInstanceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tcInstanceIds = temp
|
|
||||||
}
|
|
||||||
if len(tcInstanceIds) == 0 {
|
|
||||||
d.infos = append(d.infos, "已部署过或没有要部署的 CDN 实例")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 证书部署到 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(tcInstanceIds)
|
|
||||||
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) createSdkClients(secretId, secretKey string) (*tencentCDNDeployerSdkClients, 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 &tencentCDNDeployerSdkClients{
|
|
||||||
ssl: sslClient,
|
|
||||||
cdn: cdnClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
|
||||||
// 获取证书中的可用域名
|
|
||||||
// REF: https://cloud.tencent.com/document/product/228/42491
|
|
||||||
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
|
||||||
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
|
||||||
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 *TencentCDNDeployer) getDeployedDomainsByCertificateId(tcCertId string) ([]string, error) {
|
|
||||||
// 根据证书查询关联 CDN 域名
|
|
||||||
// REF: https://cloud.tencent.com/document/product/400/62674
|
|
||||||
describeDeployedResourcesReq := tcSsl.NewDescribeDeployedResourcesRequest()
|
|
||||||
describeDeployedResourcesReq.CertificateIds = common.StringPtrs([]string{tcCertId})
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,328 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentCLBDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClients *tencentCLBDeployerSdkClients
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
type tencentCLBDeployerSdkClients struct {
|
|
||||||
ssl *tcSsl.Client
|
|
||||||
clb *tcClb.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentCLBDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := (&TencentCLBDeployer{}).createSdkClients(
|
|
||||||
access.SecretId,
|
|
||||||
access.SecretKey,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
|
||||||
SecretId: access.SecretId,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentCLBDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClients: clients,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) Deploy(ctx context.Context) error {
|
|
||||||
switch d.option.DeployConfig.GetConfigAsString("resourceType") {
|
|
||||||
case "ssl-deploy":
|
|
||||||
// 通过 SSL 服务部署到云资源实例
|
|
||||||
err := d.deployToInstanceUseSsl(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "loadbalancer":
|
|
||||||
// 部署到指定负载均衡器
|
|
||||||
if err := d.deployToLoadbalancer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "listener":
|
|
||||||
// 部署到指定监听器
|
|
||||||
if err := d.deployToListener(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "ruledomain":
|
|
||||||
// 部署到指定七层监听转发规则域名
|
|
||||||
if err := d.deployToRuleDomain(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("unsupported resource type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) createSdkClients(secretId, secretKey, region string) (*tencentCLBDeployerSdkClients, error) {
|
|
||||||
credential := common.NewCredential(secretId, secretKey)
|
|
||||||
|
|
||||||
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 &tencentCLBDeployerSdkClients{
|
|
||||||
ssl: sslClient,
|
|
||||||
clb: clbClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) deployToInstanceUseSsl(ctx context.Context) error {
|
|
||||||
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if tcLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
if tcListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 证书部署到 CLB 实例
|
|
||||||
// REF: https://cloud.tencent.com/document/product/400/91667
|
|
||||||
deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest()
|
|
||||||
deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId)
|
|
||||||
deployCertificateInstanceReq.ResourceType = common.StringPtr("clb")
|
|
||||||
deployCertificateInstanceReq.Status = common.Int64Ptr(1)
|
|
||||||
if tcDomain == "" {
|
|
||||||
// 未开启 SNI,只需指定到监听器
|
|
||||||
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s", tcLoadbalancerId, tcListenerId)})
|
|
||||||
} else {
|
|
||||||
// 开启 SNI,需指定到域名(支持泛域名)
|
|
||||||
deployCertificateInstanceReq.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", tcLoadbalancerId, tcListenerId, tcDomain)})
|
|
||||||
}
|
|
||||||
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
|
|
||||||
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
tcListenerIds := make([]string, 0)
|
|
||||||
if tcLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询负载均衡器详细信息
|
|
||||||
// REF: https://cloud.tencent.com/document/api/214/46916
|
|
||||||
describeLoadBalancersDetailReq := tcClb.NewDescribeLoadBalancersDetailRequest()
|
|
||||||
describeLoadBalancersDetailResp, err := d.sdkClients.clb.DescribeLoadBalancersDetail(describeLoadBalancersDetailReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'clb.DescribeLoadBalancersDetail'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到负载均衡详细信息", describeLoadBalancersDetailResp))
|
|
||||||
|
|
||||||
// 查询监听器列表
|
|
||||||
// REF: https://cloud.tencent.com/document/api/214/30686
|
|
||||||
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
|
||||||
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
tcListenerIds = append(tcListenerIds, *listener.ListenerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到负载均衡器下的监听器", tcListenerIds))
|
|
||||||
|
|
||||||
// 上传证书到 SCM
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 批量更新监听器证书
|
|
||||||
var errs []error
|
|
||||||
for _, tcListenerId := range tcListenerIds {
|
|
||||||
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) deployToListener(ctx context.Context) error {
|
|
||||||
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
if tcLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
if tcListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 更新监听器证书
|
|
||||||
if err := d.modifyListenerCertificate(ctx, tcLoadbalancerId, tcListenerId, upres.CertId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) deployToRuleDomain(ctx context.Context) error {
|
|
||||||
tcLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId")
|
|
||||||
tcListenerId := d.option.DeployConfig.GetConfigAsString("listenerId")
|
|
||||||
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if tcLoadbalancerId == "" {
|
|
||||||
return errors.New("`loadbalancerId` is required")
|
|
||||||
}
|
|
||||||
if tcListenerId == "" {
|
|
||||||
return errors.New("`listenerId` is required")
|
|
||||||
}
|
|
||||||
if tcDomain == "" {
|
|
||||||
return errors.New("`domain` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 修改负载均衡七层监听器转发规则的域名级别属性
|
|
||||||
// REF: https://cloud.tencent.com/document/api/214/38092
|
|
||||||
modifyDomainAttributesReq := tcClb.NewModifyDomainAttributesRequest()
|
|
||||||
modifyDomainAttributesReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
|
||||||
modifyDomainAttributesReq.ListenerId = common.StringPtr(tcListenerId)
|
|
||||||
modifyDomainAttributesReq.Domain = common.StringPtr(tcDomain)
|
|
||||||
modifyDomainAttributesReq.Certificate = &tcClb.CertificateInput{
|
|
||||||
SSLMode: common.StringPtr("UNIDIRECTIONAL"),
|
|
||||||
CertId: common.StringPtr(upres.CertId),
|
|
||||||
}
|
|
||||||
modifyDomainAttributesResp, err := d.sdkClients.clb.ModifyDomainAttributes(modifyDomainAttributesReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'clb.ModifyDomainAttributes'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已修改七层监听器转发规则的域名级别属性", modifyDomainAttributesResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCLBDeployer) modifyListenerCertificate(ctx context.Context, tcLoadbalancerId, tcListenerId, tcCertId string) error {
|
|
||||||
// 查询监听器列表
|
|
||||||
// REF: https://cloud.tencent.com/document/api/214/30686
|
|
||||||
describeListenersReq := tcClb.NewDescribeListenersRequest()
|
|
||||||
describeListenersReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
|
||||||
describeListenersReq.ListenerIds = common.StringPtrs([]string{tcListenerId})
|
|
||||||
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 {
|
|
||||||
d.infos = append(d.infos, toStr("未找到监听器", nil))
|
|
||||||
return errors.New("listener not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已查询到监听器属性", describeListenersResp.Response))
|
|
||||||
|
|
||||||
// 修改监听器属性
|
|
||||||
// REF: https://cloud.tencent.com/document/product/214/30681
|
|
||||||
modifyListenerReq := tcClb.NewModifyListenerRequest()
|
|
||||||
modifyListenerReq.LoadBalancerId = common.StringPtr(tcLoadbalancerId)
|
|
||||||
modifyListenerReq.ListenerId = common.StringPtr(tcListenerId)
|
|
||||||
modifyListenerReq.Certificate = &tcClb.CertificateInput{CertId: common.StringPtr(tcCertId)}
|
|
||||||
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.infos = append(d.infos, toStr("已修改监听器属性", modifyListenerResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentCOSDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClient *tcSsl.Client
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := (&TencentCOSDeployer{}).createSdkClient(
|
|
||||||
access.SecretId,
|
|
||||||
access.SecretKey,
|
|
||||||
option.DeployConfig.GetConfigAsString("region"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
|
||||||
SecretId: access.SecretId,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentCOSDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
|
|
||||||
tcRegion := d.option.DeployConfig.GetConfigAsString("region")
|
|
||||||
tcBucket := d.option.DeployConfig.GetConfigAsString("bucket")
|
|
||||||
tcDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if tcBucket == "" {
|
|
||||||
return errors.New("`bucket` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", 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", tcRegion, tcBucket, tcDomain)})
|
|
||||||
deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentCOSDeployer) 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
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentECDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClients *tencentECDNDeployerSdkClients
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
type tencentECDNDeployerSdkClients struct {
|
|
||||||
ssl *tcSsl.Client
|
|
||||||
cdn *tcCdn.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentECDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := (&TencentECDNDeployer{}).createSdkClients(
|
|
||||||
access.SecretId,
|
|
||||||
access.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
|
||||||
SecretId: access.SecretId,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentECDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClients: clients,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 获取待部署的 ECDN 实例
|
|
||||||
// 如果是泛域名,根据证书匹配 ECDN 实例
|
|
||||||
aliInstanceIds := make([]string, 0)
|
|
||||||
domain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(domain, "*") {
|
|
||||||
domains, err := d.getDomainsByCertificateId(upres.CertId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aliInstanceIds = domains
|
|
||||||
} else {
|
|
||||||
aliInstanceIds = append(aliInstanceIds, domain)
|
|
||||||
}
|
|
||||||
if len(aliInstanceIds) == 0 {
|
|
||||||
d.infos = append(d.infos, "没有要部署的 ECDN 实例")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 证书部署到 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(aliInstanceIds)
|
|
||||||
deployCertificateInstanceResp, err := d.sdkClients.ssl.DeployCertificateInstance(deployCertificateInstanceReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已部署证书到云资源实例", deployCertificateInstanceResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) createSdkClients(secretId, secretKey string) (*tencentECDNDeployerSdkClients, 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 &tencentECDNDeployerSdkClients{
|
|
||||||
ssl: sslClient,
|
|
||||||
cdn: cdnClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentECDNDeployer) getDomainsByCertificateId(tcCertId string) ([]string, error) {
|
|
||||||
// 获取证书中的可用域名
|
|
||||||
// REF: https://cloud.tencent.com/document/product/228/42491
|
|
||||||
describeCertDomainsReq := tcCdn.NewDescribeCertDomainsRequest()
|
|
||||||
describeCertDomainsReq.CertId = common.StringPtr(tcCertId)
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
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/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
uploaderTcSsl "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TencentTEODeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
|
|
||||||
sdkClients *tencentTEODeployerSdkClients
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
type tencentTEODeployerSdkClients struct {
|
|
||||||
ssl *tcSsl.Client
|
|
||||||
teo *tcTeo.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencentTEODeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := (&TencentTEODeployer{}).createSdkClients(
|
|
||||||
access.SecretId,
|
|
||||||
access.SecretKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create sdk clients")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploaderTcSsl.New(&uploaderTcSsl.TencentCloudSSLUploaderConfig{
|
|
||||||
SecretId: access.SecretId,
|
|
||||||
SecretKey: access.SecretKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TencentTEODeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClients: clients,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentTEODeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentTEODeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentTEODeployer) Deploy(ctx context.Context) error {
|
|
||||||
tcZoneId := d.option.DeployConfig.GetConfigAsString("zoneId")
|
|
||||||
if tcZoneId == "" {
|
|
||||||
return xerrors.New("`zoneId` is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传证书到 SSL
|
|
||||||
upres, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
// 配置域名证书
|
|
||||||
// REF: https://cloud.tencent.com/document/product/1552/80764
|
|
||||||
modifyHostsCertificateReq := tcTeo.NewModifyHostsCertificateRequest()
|
|
||||||
modifyHostsCertificateReq.ZoneId = common.StringPtr(tcZoneId)
|
|
||||||
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
|
||||||
modifyHostsCertificateReq.Hosts = common.StringPtrs(strings.Split(strings.ReplaceAll(d.option.Domain, "\r\n", "\n"), "\n"))
|
|
||||||
modifyHostsCertificateReq.ServerCertInfo = []*tcTeo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
|
||||||
modifyHostsCertificateResp, err := d.sdkClients.teo.ModifyHostsCertificate(modifyHostsCertificateReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'teo.ModifyHostsCertificate'")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已配置域名证书", modifyHostsCertificateResp.Response))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *TencentTEODeployer) createSdkClients(secretId, secretKey string) (*tencentTEODeployerSdkClients, 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 &tencentTEODeployerSdkClients{
|
|
||||||
ssl: sslClient,
|
|
||||||
teo: teoClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
volcenginecdn "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-cdn"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
"github.com/volcengine/volc-sdk-golang/service/cdn"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VolcengineCDNDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
sdkClient *cdn.CDN
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVolcengineCDNDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.VolcEngineAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
client := cdn.NewInstance()
|
|
||||||
client.Client.SetAccessKey(access.AccessKeyId)
|
|
||||||
client.Client.SetSecretKey(access.SecretAccessKey)
|
|
||||||
uploader, err := volcenginecdn.New(&volcenginecdn.VolcEngineCDNUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
AccessKeySecret: access.SecretAccessKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
return &VolcengineCDNDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineCDNDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineCDNDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineCDNDeployer) Deploy(ctx context.Context) error {
|
|
||||||
apiCtx := context.Background()
|
|
||||||
// 上传证书
|
|
||||||
upres, err := d.sslUploader.Upload(apiCtx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
domains := make([]string, 0)
|
|
||||||
configDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(configDomain, "*.") {
|
|
||||||
// 获取证书可以部署的域名
|
|
||||||
// REF: https://www.volcengine.com/docs/6454/125711
|
|
||||||
describeCertConfigReq := &cdn.DescribeCertConfigRequest{
|
|
||||||
CertId: upres.CertId,
|
|
||||||
}
|
|
||||||
describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.DescribeCertConfig'")
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.CertNotConfig {
|
|
||||||
// 当前未启用 HTTPS 的加速域名列表。
|
|
||||||
domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain)
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.OtherCertConfig {
|
|
||||||
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联的证书不是您指定的证书。
|
|
||||||
domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain)
|
|
||||||
}
|
|
||||||
for i := range describeCertConfigResp.Result.SpecifiedCertConfig {
|
|
||||||
// 已启用了 HTTPS 的加速域名列表。这些加速域名关联了您指定的证书。
|
|
||||||
d.infos = append(d.infos, fmt.Sprintf("%s域名已配置该证书", describeCertConfigResp.Result.SpecifiedCertConfig[i].Domain))
|
|
||||||
}
|
|
||||||
if len(domains) == 0 {
|
|
||||||
if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 {
|
|
||||||
// 所有匹配的域名都配置了该证书,跳过部署
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
domains = append(domains, configDomain)
|
|
||||||
}
|
|
||||||
// 部署证书
|
|
||||||
// REF: https://www.volcengine.com/docs/6454/125712
|
|
||||||
for i := range domains {
|
|
||||||
batchDeployCertReq := &cdn.BatchDeployCertRequest{
|
|
||||||
CertId: upres.CertId,
|
|
||||||
Domain: domains[i],
|
|
||||||
}
|
|
||||||
batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'cdn.BatchDeployCert'")
|
|
||||||
} else {
|
|
||||||
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), batchDeployCertResp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
|
||||||
volcenginelive "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/volcengine-live"
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/cast"
|
|
||||||
"github.com/volcengine/volc-sdk-golang/base"
|
|
||||||
live "github.com/volcengine/volc-sdk-golang/service/live/v20230101"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VolcengineLiveDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
sdkClient *live.Live
|
|
||||||
sslUploader uploader.Uploader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVolcengineLiveDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
access := &domain.VolcEngineAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
client := live.NewInstance()
|
|
||||||
client.SetCredential(base.Credentials{
|
|
||||||
AccessKeyID: access.AccessKeyId,
|
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
|
||||||
})
|
|
||||||
uploader, err := volcenginelive.New(&volcenginelive.VolcEngineLiveUploaderConfig{
|
|
||||||
AccessKeyId: access.AccessKeyId,
|
|
||||||
AccessKeySecret: access.SecretAccessKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
|
|
||||||
}
|
|
||||||
return &VolcengineLiveDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
sdkClient: client,
|
|
||||||
sslUploader: uploader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineLiveDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineLiveDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineLiveDeployer) Deploy(ctx context.Context) error {
|
|
||||||
apiCtx := context.Background()
|
|
||||||
// 上传证书
|
|
||||||
upres, err := d.sslUploader.Upload(apiCtx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("已上传证书", upres))
|
|
||||||
|
|
||||||
domains := make([]string, 0)
|
|
||||||
configDomain := d.option.DeployConfig.GetConfigAsString("domain")
|
|
||||||
if strings.HasPrefix(configDomain, "*.") {
|
|
||||||
// 如果是泛域名,获取所有的域名并匹配
|
|
||||||
matchDomains, err := d.getDomainsByWildcardDomain(apiCtx, configDomain)
|
|
||||||
if err != nil {
|
|
||||||
d.infos = append(d.infos, toStr("获取域名列表失败", upres))
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'live.ListDomainDetail'")
|
|
||||||
}
|
|
||||||
if len(matchDomains) == 0 {
|
|
||||||
return xerrors.Errorf("未查询到匹配的域名: %s", configDomain)
|
|
||||||
}
|
|
||||||
domains = matchDomains
|
|
||||||
} else {
|
|
||||||
domains = append(domains, configDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部署证书
|
|
||||||
// REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6d
|
|
||||||
for i := range domains {
|
|
||||||
bindCertReq := &live.BindCertBody{
|
|
||||||
ChainID: upres.CertId,
|
|
||||||
Domain: domains[i],
|
|
||||||
HTTPS: cast.BoolPtr(true),
|
|
||||||
}
|
|
||||||
bindCertResp, err := d.sdkClient.BindCert(apiCtx, bindCertReq)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to execute sdk request 'live.BindCert'")
|
|
||||||
} else {
|
|
||||||
d.infos = append(d.infos, toStr(fmt.Sprintf("%s域名的证书已修改", domains[i]), bindCertResp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *VolcengineLiveDeployer) getDomainsByWildcardDomain(ctx context.Context, wildcardDomain string) ([]string, error) {
|
|
||||||
pageNum := int32(1)
|
|
||||||
searchTotal := 0
|
|
||||||
domains := make([]string, 0)
|
|
||||||
for {
|
|
||||||
listDomainDetailReq := &live.ListDomainDetailBody{
|
|
||||||
PageNum: pageNum,
|
|
||||||
PageSize: 1000,
|
|
||||||
}
|
|
||||||
// 查询域名列表
|
|
||||||
// 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
|
|
||||||
listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq)
|
|
||||||
if err != nil {
|
|
||||||
return domains, err
|
|
||||||
}
|
|
||||||
if listDomainDetailResp.Result.DomainList != nil {
|
|
||||||
for _, item := range listDomainDetailResp.Result.DomainList {
|
|
||||||
if matchWildcardDomain(item.Domain, wildcardDomain) {
|
|
||||||
domains = append(domains, item.Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
searchTotal += len(listDomainDetailResp.Result.DomainList)
|
|
||||||
if int(listDomainDetailResp.Result.Total) > searchTotal {
|
|
||||||
pageNum++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchWildcardDomain(domain, wildcardDomain string) bool {
|
|
||||||
if strings.HasPrefix(wildcardDomain, "*.") {
|
|
||||||
if "*."+domain == wildcardDomain {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
regexPattern := "^([a-zA-Z0-9_-]+)\\." + regexp.QuoteMeta(wildcardDomain[2:]) + "$"
|
|
||||||
regex := regexp.MustCompile(regexPattern)
|
|
||||||
return regex.MatchString(domain)
|
|
||||||
}
|
|
||||||
return domain == wildcardDomain
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package deployer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
xhttp "github.com/usual2970/certimate/internal/utils/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebhookDeployer struct {
|
|
||||||
option *DeployerOption
|
|
||||||
infos []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWebhookDeployer(option *DeployerOption) (Deployer, error) {
|
|
||||||
return &WebhookDeployer{
|
|
||||||
option: option,
|
|
||||||
infos: make([]string, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetID() string {
|
|
||||||
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WebhookDeployer) GetInfos() []string {
|
|
||||||
return d.infos
|
|
||||||
}
|
|
||||||
|
|
||||||
type webhookData struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
Certificate string `json:"certificate"`
|
|
||||||
PrivateKey string `json:"privateKey"`
|
|
||||||
Variables map[string]string `json:"variables"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
|
|
||||||
access := &domain.WebhookAccess{}
|
|
||||||
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to get access")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &webhookData{
|
|
||||||
Domain: d.option.Domain,
|
|
||||||
Certificate: d.option.Certificate.Certificate,
|
|
||||||
PrivateKey: d.option.Certificate.PrivateKey,
|
|
||||||
Variables: d.option.DeployConfig.GetConfigAsVariables(),
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(data)
|
|
||||||
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Wrap(err, "failed to send webhook request")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.infos = append(d.infos, toStr("Webhook Response", string(resp)))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user