diff --git a/internal/domain/domains.go b/internal/domain/domains.go index 2bc07074..6a228fff 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -41,7 +41,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string { // - defaultValue: 默认值。 // // 出参: -// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。 +// - 配置项的值。如果配置项不存在、类型不是字符串或者值为零值,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string { return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue) } @@ -64,7 +64,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 { // - defaultValue: 默认值。 // // 出参: -// - 配置项的值。如果配置项不存在或者类型不是 32 位整数,则返回默认值。 +// - 配置项的值。如果配置项不存在、类型不是 32 位整数或者值为零值,则返回默认值。 func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32) int32 { return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue) } diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go new file mode 100644 index 00000000..cda2011e --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -0,0 +1,289 @@ +package aliyunalb + +import ( + "context" + "errors" + "fmt" + "strings" + + aliyunAlb "github.com/alibabacloud-go/alb-20200616/v2/client" + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunALBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type AliyunALBDeployer struct { + config *AliyunALBDeployerConfig + logger deployer.Logger + sdkClient *aliyunAlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunALBDeployer)(nil) + +func New(config *AliyunALBDeployerConfig) (*AliyunALBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunALBDeployerConfig, logger deployer.Logger) (*AliyunALBDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + aliyunCasRegion := config.Region + if aliyunCasRegion != "" { + // 阿里云 CAS 服务接入点是独立于 ALB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if !strings.HasPrefix(aliyunCasRegion, "cn-") { + aliyunCasRegion = "ap-southeast-1" + } else { + aliyunCasRegion = "cn-hangzhou" + } + } + uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: aliyunCasRegion, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunALBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunALBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunALBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute + getLoadBalancerAttributeReq := &aliyunAlb.GetLoadBalancerAttributeRequest{ + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp) + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + listListenersReq := &aliyunAlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("HTTPS"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", listenerIds) + + // 查询 QUIC 监听列表 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners + listListenersPage = 1 + listListenersToken = nil + for { + listListenersReq := &aliyunAlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("QUIC"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 ALB 负载均衡实例下的全部 QUIC 监听", listenerIds) + + // 批量更新监听证书 + var errs []error + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunALBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunALBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 查询监听的属性 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getlistenerattribute + getListenerAttributeReq := &aliyunAlb.GetListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + } + getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.GetListenerAttribute'") + } + + d.logger.Appendt("已查询到 ALB 监听配置", getListenerAttributeResp) + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-updatelistenerattribute + updateListenerAttributeReq := &aliyunAlb.UpdateListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + Certificates: []*aliyunAlb.UpdateListenerAttributeRequestCertificates{{ + CertificateId: tea.String(cloudCertId), + }}, + } + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'alb.UpdateListenerAttribute'") + } + + d.logger.Appendt("已更新 ALB 监听配置", updateListenerAttributeResp) + + // TODO: #347 + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunAlb.Client, error) { + if region == "" { + region = "cn-hangzhou" + } + + // 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-endpoint + var endpoint string + switch region { + case "cn-hangzhou-finance": + endpoint = "alb.cn-hangzhou.aliyuncs.com" + default: + endpoint = fmt.Sprintf("alb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunAlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go new file mode 100644 index 00000000..8c8962ba --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb_test.go @@ -0,0 +1,118 @@ +package aliyunalb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunAlb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNALB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_alb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNALB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_LOADBALANCERID="your-alb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNALB_LISTENERID="your-alb-listener-id" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := dpAliyunAlb.New(&dpAliyunAlb.AliyunALBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunAlb.DEPLOY_RESOURCE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/defines.go b/internal/pkg/core/deployer/providers/aliyun-alb/defines.go new file mode 100644 index 00000000..927e990a --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-alb/defines.go @@ -0,0 +1,10 @@ +package aliyunalb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go new file mode 100644 index 00000000..caf97753 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -0,0 +1,93 @@ +package aliyuncdn + +import ( + "context" + "errors" + "fmt" + "time" + + aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client" + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunCDNDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunCDNDeployer struct { + config *AliyunCDNDeployerConfig + logger deployer.Logger + sdkClient *aliyunCdn.Client +} + +var _ deployer.Deployer = (*AliyunCDNDeployer)(nil) + +func New(config *AliyunCDNDeployerConfig) (*AliyunCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunCDNDeployerConfig, logger deployer.Logger) (*AliyunCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 设置 CDN 域名域名证书 + // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate + setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{ + DomainName: tea.String(d.config.Domain), + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + CertType: tea.String("upload"), + SSLProtocol: tea.String("on"), + SSLPub: tea.String(certPem), + SSLPri: tea.String(privkeyPem), + } + setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.SetCdnDomainSSLCertificate'") + } + + d.logger.Appendt("已设置 CDN 域名证书", setCdnDomainSSLCertificateResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunCdn.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("cdn.aliyuncs.com"), + } + + client, err := aliyunCdn.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go new file mode 100644 index 00000000..57c15943 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn_test.go @@ -0,0 +1,75 @@ +package aliyuncdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunCdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNCDN_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunCdn.New(&dpAliyunCdn.AliyunCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go new file mode 100644 index 00000000..395b9bb5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -0,0 +1,291 @@ +package aliyunclb + +import ( + "context" + "errors" + "fmt" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunSlb "github.com/alibabacloud-go/slb-20140515/v4/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunSlb "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-slb" +) + +type AliyunCLBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER]、[DEPLOY_RESOURCE_LISTENER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听端口。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerPort int32 `json:"listenerPort,omitempty"` +} + +type AliyunCLBDeployer struct { + config *AliyunCLBDeployerConfig + logger deployer.Logger + sdkClient *aliyunSlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunCLBDeployer)(nil) + +func New(config *AliyunCLBDeployerConfig) (*AliyunCLBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunCLBDeployerConfig, logger deployer.Logger) (*AliyunCLBDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := upAliyunSlb.New(&upAliyunSlb.AliyunSLBUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: config.Region, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunCLBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunCLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SLB + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerPorts := make([]int32, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerattribute + describeLoadBalancerAttributeReq := &aliyunSlb.DescribeLoadBalancerAttributeRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + describeLoadBalancerAttributeResp, err := d.sdkClient.DescribeLoadBalancerAttribute(describeLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp) + + // 查询 HTTPS 监听列表 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + describeLoadBalancerListenersReq := &aliyunSlb.DescribeLoadBalancerListenersRequest{ + RegionId: tea.String(d.config.Region), + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerId: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("https"), + } + describeLoadBalancerListenersResp, err := d.sdkClient.DescribeLoadBalancerListeners(describeLoadBalancerListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerListeners'") + } + + if describeLoadBalancerListenersResp.Body.Listeners != nil { + for _, listener := range describeLoadBalancerListenersResp.Body.Listeners { + listenerPorts = append(listenerPorts, *listener.ListenerPort) + } + } + + if describeLoadBalancerListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = describeLoadBalancerListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", listenerPorts) + + // 批量更新监听证书 + var errs []error + for _, listenerPort := range listenerPorts { + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, listenerPort, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunCLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + if d.config.ListenerPort == 0 { + return errors.New("config `listenerPort` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.LoadbalancerId, d.config.ListenerPort, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunCLBDeployer) updateListenerCertificate(ctx context.Context, cloudLoadbalancerId string, cloudListenerPort int32, cloudCertId string) error { + // 查询监听配置 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerhttpslistenerattribute + describeLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.DescribeLoadBalancerHTTPSListenerAttributeRequest{ + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + } + describeLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.DescribeLoadBalancerHTTPSListenerAttribute(describeLoadBalancerHTTPSListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeLoadBalancerHTTPSListenerAttribute'") + } + + d.logger.Appendt("已查询到 CLB HTTPS 监听配置", describeLoadBalancerHTTPSListenerAttributeResp) + + // 查询扩展域名 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describedomainextensions + describeDomainExtensionsReq := &aliyunSlb.DescribeDomainExtensionsRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + } + describeDomainExtensionsResp, err := d.sdkClient.DescribeDomainExtensions(describeDomainExtensionsReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.DescribeDomainExtensions'") + } + + d.logger.Appendt("已查询到 CLB 扩展域名", describeDomainExtensionsResp) + + // 遍历修改扩展域名 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setdomainextensionattribute + // + // 这里仅修改跟被替换证书一致的扩展域名 + if describeDomainExtensionsResp.Body.DomainExtensions != nil && describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension != nil { + for _, domainExtension := range describeDomainExtensionsResp.Body.DomainExtensions.DomainExtension { + if *domainExtension.ServerCertificateId != *describeLoadBalancerHTTPSListenerAttributeResp.Body.ServerCertificateId { + continue + } + + setDomainExtensionAttributeReq := &aliyunSlb.SetDomainExtensionAttributeRequest{ + RegionId: tea.String(d.config.Region), + DomainExtensionId: tea.String(*domainExtension.DomainExtensionId), + ServerCertificateId: tea.String(cloudCertId), + } + _, err := d.sdkClient.SetDomainExtensionAttribute(setDomainExtensionAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetDomainExtensionAttribute'") + } + } + } + + // 修改监听配置 + // REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-setloadbalancerhttpslistenerattribute + // + // 注意修改监听配置要放在修改扩展域名之后 + setLoadBalancerHTTPSListenerAttributeReq := &aliyunSlb.SetLoadBalancerHTTPSListenerAttributeRequest{ + RegionId: tea.String(d.config.Region), + LoadBalancerId: tea.String(cloudLoadbalancerId), + ListenerPort: tea.Int32(cloudListenerPort), + ServerCertificateId: tea.String(cloudCertId), + } + setLoadBalancerHTTPSListenerAttributeResp, err := d.sdkClient.SetLoadBalancerHTTPSListenerAttribute(setLoadBalancerHTTPSListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'slb.SetLoadBalancerHTTPSListenerAttribute'") + } + + d.logger.Appendt("已更新 CLB HTTPS 监听配置", setLoadBalancerHTTPSListenerAttributeResp) + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) { + if region == "" { + region = "cn-hangzhou" // CLB(SLB) 服务默认区域:华东一杭州 + } + + // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint + var endpoint string + switch region { + case + "cn-hangzhou", + "cn-hangzhou-finance", + "cn-shanghai-finance-1", + "cn-shenzhen-finance-1": + endpoint = "slb.aliyuncs.com" + default: + endpoint = fmt.Sprintf("slb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunSlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go new file mode 100644 index 00000000..22a1168c --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb_test.go @@ -0,0 +1,120 @@ +package aliyunclb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerPort int +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNCLB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.IntVar(&fListenerPort, argsPrefix+"LISTENERPORT", 443, "") +} + +/* +Shell command to run this test: + + go test -v aliyun_clb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_LOADBALANCERID="your-clb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNCLB_LISTENERPORT=443 +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERPORT: %v", fListenerPort), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunCLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + LoadbalancerId: fLoadbalancerId, + ListenerPort: int32(fListenerPort), + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/defines.go b/internal/pkg/core/deployer/providers/aliyun-clb/defines.go new file mode 100644 index 00000000..4a02ab20 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-clb/defines.go @@ -0,0 +1,10 @@ +package aliyunclb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go new file mode 100644 index 00000000..d71d24c0 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -0,0 +1,97 @@ +package aliyundcdn + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunDcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunDCDNDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunDCDNDeployer struct { + config *AliyunDCDNDeployerConfig + logger deployer.Logger + sdkClient *aliyunDcdn.Client +} + +var _ deployer.Deployer = (*AliyunDCDNDeployer)(nil) + +func New(config *AliyunDCDNDeployerConfig) (*AliyunDCDNDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunDCDNDeployerConfig, logger deployer.Logger) (*AliyunDCDNDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunDCDNDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunDCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + + // 配置域名证书 + // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate + setDcdnDomainSSLCertificateReq := &aliyunDcdn.SetDcdnDomainSSLCertificateRequest{ + DomainName: tea.String(domain), + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + CertType: tea.String("upload"), + SSLProtocol: tea.String("on"), + SSLPub: tea.String(certPem), + SSLPri: tea.String(privkeyPem), + } + setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'dcdn.SetDcdnDomainSSLCertificate'") + } + + d.logger.Appendt("已配置 DCDN 域名证书", setDcdnDomainSSLCertificateResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret string) (*aliyunDcdn.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String("dcdn.aliyuncs.com"), + } + + client, err := aliyunDcdn.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go new file mode 100644 index 00000000..6418fa4d --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-dcdn/aliyun_dcdn_test.go @@ -0,0 +1,75 @@ +package aliyundcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunDcdn "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNDCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_dcdn_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNDCDN_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunDcdn.New(&dpAliyunDcdn.AliyunDCDNDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go new file mode 100644 index 00000000..5a8e9b38 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -0,0 +1,251 @@ +package aliyunnlb + +import ( + "context" + "errors" + "fmt" + "strings" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunNlb "github.com/alibabacloud-go/nlb-20220430/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + upAliyunCas "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunNLBDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 部署资源类型。 + ResourceType DeployResourceType `json:"resourceType"` + // 负载均衡实例 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LOADBALANCER] 时必填。 + LoadbalancerId string `json:"loadbalancerId,omitempty"` + // 负载均衡监听 ID。 + // 部署资源类型为 [DEPLOY_RESOURCE_LISTENER] 时必填。 + ListenerId string `json:"listenerId,omitempty"` +} + +type AliyunNLBDeployer struct { + config *AliyunNLBDeployerConfig + logger deployer.Logger + sdkClient *aliyunNlb.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunNLBDeployer)(nil) + +func New(config *AliyunNLBDeployerConfig) (*AliyunNLBDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunNLBDeployerConfig, logger deployer.Logger) (*AliyunNLBDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + aliyunCasRegion := config.Region + if aliyunCasRegion != "" { + // 阿里云 CAS 服务接入点是独立于 NLB 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if !strings.HasPrefix(aliyunCasRegion, "cn-") { + aliyunCasRegion = "ap-southeast-1" + } else { + aliyunCasRegion = "cn-hangzhou" + } + } + uploader, err := upAliyunCas.New(&upAliyunCas.AliyunCASUploaderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + Region: aliyunCasRegion, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunNLBDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunNLBDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Appendt("certificate file uploaded", upres) + + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case DEPLOY_RESOURCE_LOADBALANCER: + if err := d.deployToLoadbalancer(ctx, upres.CertId); err != nil { + return nil, err + } + + case DEPLOY_RESOURCE_LISTENER: + if err := d.deployToListener(ctx, upres.CertId); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context, cloudCertId string) error { + if d.config.LoadbalancerId == "" { + return errors.New("config `loadbalancerId` is required") + } + + listenerIds := make([]string, 0) + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute + getLoadBalancerAttributeReq := &aliyunNlb.GetLoadBalancerAttributeRequest{ + LoadBalancerId: tea.String(d.config.LoadbalancerId), + } + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetLoadBalancerAttribute'") + } + + d.logger.Appendt("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp) + + // 查询 TCPSSL 监听列表 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + listListenersReq := &aliyunNlb.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(d.config.LoadbalancerId)}, + ListenerProtocol: tea.String("TCPSSL"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.ListListeners'") + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + listenerIds = append(listenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.logger.Appendt("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", listenerIds) + + // 批量更新监听证书 + var errs []error + for _, listenerId := range listenerIds { + if err := d.updateListenerCertificate(ctx, listenerId, cloudCertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunNLBDeployer) deployToListener(ctx context.Context, cloudCertId string) error { + if d.config.ListenerId == "" { + return errors.New("config `listenerId` is required") + } + + // 更新监听 + if err := d.updateListenerCertificate(ctx, d.config.ListenerId, cloudCertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, cloudListenerId string, cloudCertId string) error { + // 查询监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute + getListenerAttributeReq := &aliyunNlb.GetListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + } + getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.GetListenerAttribute'") + } + + d.logger.Appendt("已查询到 NLB 监听配置", getListenerAttributeResp) + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute + updateListenerAttributeReq := &aliyunNlb.UpdateListenerAttributeRequest{ + ListenerId: tea.String(cloudListenerId), + CertificateIds: []*string{tea.String(cloudCertId)}, + } + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'nlb.UpdateListenerAttribute'") + } + + d.logger.Appendt("已更新 NLB 监听配置", updateListenerAttributeResp) + + return nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) { + if region == "" { + region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州 + } + + // 接入点一览 https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-endpoint + var endpoint string + switch region { + default: + endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunNlb.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go new file mode 100644 index 00000000..a1b84257 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb_test.go @@ -0,0 +1,119 @@ +package aliyunnlb_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunClb "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fLoadbalancerId string + fListenerId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNNLB_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fLoadbalancerId, argsPrefix+"LOADBALANCERID", "", "") + flag.StringVar(&fListenerId, argsPrefix+"LISTENERID", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_nlb_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_LOADBALANCERID="your-nlb-instance-id" \ + --CERTIMATE_DEPLOYER_ALIYUNNLB_LISTENERID="your-nlb-listener-id" +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy_ToLoadbalancer", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LOADBALANCER, + LoadbalancerId: fLoadbalancerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) + + t.Run("Deploy_ToListener", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("LOADBALANCERID: %v", fLoadbalancerId), + fmt.Sprintf("LISTENERID: %v", fListenerId), + }, "\n")) + + deployer, err := dpAliyunClb.New(&dpAliyunClb.AliyunNLBDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + ResourceType: dpAliyunClb.DEPLOY_RESOURCE_LISTENER, + ListenerId: fListenerId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go b/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go new file mode 100644 index 00000000..14e3c2b2 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/defines.go @@ -0,0 +1,10 @@ +package aliyunnlb + +type DeployResourceType string + +const ( + // 资源类型:部署到指定负载均衡器。 + DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") + // 资源类型:部署到指定监听器。 + DEPLOY_RESOURCE_LISTENER = DeployResourceType("listener") +) diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go new file mode 100644 index 00000000..656c9e10 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go @@ -0,0 +1,112 @@ +package aliyunoss + +import ( + "context" + "errors" + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" +) + +type AliyunOSSDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 存储桶名。 + Bucket string `json:"bucket"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type AliyunOSSDeployer struct { + config *AliyunOSSDeployerConfig + logger deployer.Logger + sdkClient *oss.Client +} + +var _ deployer.Deployer = (*AliyunOSSDeployer)(nil) + +func New(config *AliyunOSSDeployerConfig) (*AliyunOSSDeployer, error) { + return NewWithLogger(config, deployer.NewNilLogger()) +} + +func NewWithLogger(config *AliyunOSSDeployerConfig, logger deployer.Logger) (*AliyunOSSDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &AliyunOSSDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *AliyunOSSDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.Bucket == "" { + return nil, errors.New("config `bucket` is required") + } + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // 为存储空间绑定自定义域名 + // REF: https://help.aliyun.com/zh/oss/developer-reference/putcname + err := d.sdkClient.PutBucketCnameWithCertificate(d.config.Bucket, oss.PutBucketCname{ + Cname: d.config.Domain, + CertificateConfiguration: &oss.CertificateConfiguration{ + Certificate: certPem, + PrivateKey: privkeyPem, + Force: true, + }, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'oss.PutBucketCnameWithCertificate'") + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) { + // 接入点一览 https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints + var endpoint string + switch region { + case "": + endpoint = "oss.aliyuncs.com" + case + "cn-hzjbp", + "cn-hzjbp-a", + "cn-hzjbp-b": + endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com" + case + "cn-shanghai-finance-1", + "cn-shenzhen-finance-1", + "cn-beijing-finance-1", + "cn-north-2-gov-1": + endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region) + default: + endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region) + } + + client, err := oss.New(endpoint, accessKeyId, accessKeySecret) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go new file mode 100644 index 00000000..50c707e5 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss_test.go @@ -0,0 +1,85 @@ +package aliyunoss_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + dpAliyunOss "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fBucket string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNOSS_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v aliyun_oss_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_BUCKET="your-bucket" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_DOMAIN="example.com" \ +*/ +func Test(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("BUCKET: %v", fBucket), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := dpAliyunOss.New(&dpAliyunOss.AliyunOSSDeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + Bucket: fBucket, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go index 1e75b615..7e5a353a 100644 --- a/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go +++ b/internal/pkg/core/deployer/providers/k8s-secret/k8s_secret.go @@ -34,6 +34,8 @@ type K8sSecretDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*K8sSecretDeployer)(nil) + func New(config *K8sSecretDeployerConfig) (*K8sSecretDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go index 9770025d..f8fb1f23 100644 --- a/internal/pkg/core/deployer/providers/local/local.go +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -17,14 +17,13 @@ import ( type LocalDeployerConfig struct { // Shell 执行环境。 - // 空值时默认根据操作系统决定。 + // 零值时默认根据操作系统决定。 ShellEnv ShellEnvType `json:"shellEnv,omitempty"` // 前置命令。 PreCommand string `json:"preCommand,omitempty"` // 后置命令。 PostCommand string `json:"postCommand,omitempty"` // 输出证书格式。 - // 空值时默认为 [OUTPUT_FORMAT_PEM]。 OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` @@ -49,6 +48,8 @@ type LocalDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*LocalDeployer)(nil) + func New(config *LocalDeployerConfig) (*LocalDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } @@ -81,7 +82,7 @@ func (d *LocalDeployer) Deploy(ctx context.Context, certPem string, privkeyPem s // 写入证书和私钥文件 switch d.config.OutputFormat { - case "", OUTPUT_FORMAT_PEM: + case OUTPUT_FORMAT_PEM: if err := fs.WriteFileString(d.config.OutputCertPath, certPem); err != nil { return nil, xerrors.Wrap(err, "failed to save certificate file") } diff --git a/internal/pkg/core/deployer/providers/local/local_test.go b/internal/pkg/core/deployer/providers/local/local_test.go index 74307734..c49df4a0 100644 --- a/internal/pkg/core/deployer/providers/local/local_test.go +++ b/internal/pkg/core/deployer/providers/local/local_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" + dpLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local" ) var ( @@ -60,7 +60,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, }) @@ -108,8 +108,8 @@ func Test(t *testing.T) { fmt.Sprintf("PFXPASSWORD: %v", fPfxPassword), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_PFX, + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ + OutputFormat: dpLocal.OUTPUT_FORMAT_PFX, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, PfxPassword: fPfxPassword, @@ -151,8 +151,8 @@ func Test(t *testing.T) { fmt.Sprintf("JKSSTOREPASS: %v", fJksStorepass), }, "\n")) - deployer, err := dLocal.New(&dLocal.LocalDeployerConfig{ - OutputFormat: dLocal.OUTPUT_FORMAT_JKS, + deployer, err := dpLocal.New(&dpLocal.LocalDeployerConfig{ + OutputFormat: dpLocal.OUTPUT_FORMAT_JKS, OutputCertPath: fOutputCertPath, OutputKeyPath: fOutputKeyPath, JksAlias: fJksAlias, diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 982e1ff4..147662f8 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -18,10 +18,10 @@ import ( type SshDeployerConfig struct { // SSH 主机。 - // 空值时默认为 "localhost"。 + // 零值时默认为 "localhost"。 SshHost string `json:"sshHost,omitempty"` // SSH 端口。 - // 空值时默认为 22。 + // 零值时默认为 22。 SshPort int32 `json:"sshPort,omitempty"` // SSH 登录用户名。 SshUsername string `json:"sshUsername,omitempty"` @@ -36,7 +36,6 @@ type SshDeployerConfig struct { // 后置命令。 PostCommand string `json:"postCommand,omitempty"` // 输出证书格式。 - // 空值时默认为 [OUTPUT_FORMAT_PEM]。 OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` @@ -61,6 +60,8 @@ type SshDeployer struct { logger deployer.Logger } +var _ deployer.Deployer = (*SshDeployer)(nil) + func New(config *SshDeployerConfig) (*SshDeployer, error) { return NewWithLogger(config, deployer.NewNilLogger()) } @@ -109,7 +110,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str // 上传证书和私钥文件 switch d.config.OutputFormat { - case "", OUTPUT_FORMAT_PEM: + case OUTPUT_FORMAT_PEM: if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil { return nil, xerrors.Wrap(err, "failed to upload certificate file") } diff --git a/internal/pkg/core/deployer/providers/ssh/ssh_test.go b/internal/pkg/core/deployer/providers/ssh/ssh_test.go index dbc0109c..bd31609a 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh_test.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - dSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" + dpSsh "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh" ) var ( @@ -64,7 +64,7 @@ func Test(t *testing.T) { fmt.Sprintf("OUTPUTKEYPATH: %v", fOutputKeyPath), }, "\n")) - deployer, err := dSsh.New(&dSsh.SshDeployerConfig{ + deployer, err := dpSsh.New(&dpSsh.SshDeployerConfig{ SshHost: fSshHost, SshPort: int32(fSshPort), SshUsername: fSshUsername, diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index f13d2b33..b65cd1d2 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -144,6 +144,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 } + // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints var endpoint string switch region { case "cn-hangzhou": diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index df14aea9..f78c85ce 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -121,6 +121,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Cl region = "cn-hangzhou" // SLB 服务默认区域:华东一杭州 } + // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint var endpoint string switch region { case