diff --git a/go.mod b/go.mod
index fc4267b9..0571e260 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
+ github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
github.com/alibabacloud-go/slb-20140515/v4 v4.0.9
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
diff --git a/go.sum b/go.sum
index 3475675b..10911699 100644
--- a/go.sum
+++ b/go.sum
@@ -47,6 +47,7 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.7/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
@@ -64,6 +65,8 @@ github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko=
+github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3/go.mod h1:4a/RcBYeAhYowHzX+LMgnouz7NradnSKPKl14KS3B1U=
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
@@ -94,6 +97,7 @@ github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQ
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
diff --git a/internal/deployer/aliyun_nlb.go b/internal/deployer/aliyun_nlb.go
new file mode 100644
index 00000000..8e4d8d84
--- /dev/null
+++ b/internal/deployer/aliyun_nlb.go
@@ -0,0 +1,230 @@
+package deployer
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+
+ openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ nlb20220430 "github.com/alibabacloud-go/nlb-20220430/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+
+ "github.com/usual2970/certimate/internal/domain"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+)
+
+type AliyunNLBDeployer struct {
+ option *DeployerOption
+ infos []string
+
+ sdkClient *nlb20220430.Client
+ sslUploader uploader.Uploader
+}
+
+func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) {
+ access := &domain.AliyunAccess{}
+ json.Unmarshal([]byte(option.Access), access)
+
+ client, err := (&AliyunNLBDeployer{}).createSdkClient(
+ access.AccessKeyId,
+ access.AccessKeySecret,
+ option.DeployConfig.GetConfigAsString("region"),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ uploader, err := uploader.NewAliyunCASUploader(&uploader.AliyunCASUploaderConfig{
+ AccessKeyId: access.AccessKeyId,
+ AccessKeySecret: access.AccessKeySecret,
+ Region: option.DeployConfig.GetConfigAsString("region"),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ 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) GetInfo() []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) (*nlb20220430.Client, error) {
+ if region == "" {
+ region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州
+ }
+
+ aConfig := &openapi.Config{
+ AccessKeyId: tea.String(accessKeyId),
+ AccessKeySecret: tea.String(accessKeySecret),
+ }
+
+ var endpoint string
+ switch region {
+ case "cn-hangzhou-finance":
+ endpoint = "nlb.cn-hangzhou.aliyuncs.com"
+ default:
+ endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region)
+ }
+ aConfig.Endpoint = tea.String(endpoint)
+
+ client, err := nlb20220430.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")
+ }
+
+ // 查询负载均衡实例的详细信息
+ // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute
+ getLoadBalancerAttributeReq := &nlb20220430.GetLoadBalancerAttributeRequest{
+ LoadBalancerId: tea.String(aliLoadbalancerId),
+ }
+ getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'nlb.GetLoadBalancerAttribute': %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp))
+
+ // 查询监听列表
+ // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners
+ aliListenerIds := make([]string, 0)
+ listListenersPage := 1
+ listListenersLimit := int32(100)
+ var listListenersToken *string = nil
+ for {
+ listListenersReq := &nlb20220430.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 fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err)
+ }
+
+ 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
+ uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
+ if err != nil {
+ return err
+ }
+
+ d.infos = append(d.infos, toStr("已上传证书", uploadResult))
+
+ // 批量更新监听证书
+ var errs []error
+ for _, aliListenerId := range aliListenerIds {
+ if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.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
+ uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
+ if err != nil {
+ return err
+ }
+
+ d.infos = append(d.infos, toStr("已上传证书", uploadResult))
+
+ // 更新监听
+ if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.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 := &nlb20220430.GetListenerAttributeRequest{
+ ListenerId: tea.String(aliListenerId),
+ }
+ getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'nlb.GetListenerAttribute': %w", err)
+ }
+
+ 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 := &nlb20220430.UpdateListenerAttributeRequest{
+ ListenerId: tea.String(aliListenerId),
+ CertificateIds: []*string{tea.String(aliCertId)},
+ }
+ updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'nlb.UpdateListenerAttribute': %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp))
+
+ return nil
+}
diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go
index 8e130b3b..3936da88 100644
--- a/internal/deployer/deployer.go
+++ b/internal/deployer/deployer.go
@@ -19,6 +19,8 @@ const (
targetAliyunCDN = "aliyun-cdn"
targetAliyunESA = "aliyun-dcdn"
targetAliyunCLB = "aliyun-clb"
+ targetAliyunALB = "aliyun-alb"
+ targetAliyunNLB = "aliyun-nlb"
targetTencentCDN = "tencent-cdn"
targetTencentCLB = "tencent-clb"
targetTencentCOS = "tencent-cos"
@@ -109,6 +111,10 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
return NewAliyunESADeployer(option)
case targetAliyunCLB:
return NewAliyunCLBDeployer(option)
+ case targetAliyunALB:
+ return NewAliyunALBDeployer(option)
+ case targetAliyunNLB:
+ return NewAliyunNLBDeployer(option)
case targetTencentCDN:
return NewTencentCDNDeployer(option)
case targetTencentCLB:
@@ -130,7 +136,7 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
case targetK8sSecret:
return NewK8sSecretDeployer(option)
}
- return nil, errors.New("not implemented")
+ return nil, errors.New("unsupported deploy target")
}
func getProduct(t string) string {
diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx
index 395fd9c8..b368a1fb 100644
--- a/ui/src/components/certimate/DeployEditDialog.tsx
+++ b/ui/src/components/certimate/DeployEditDialog.tsx
@@ -13,6 +13,7 @@ import DeployToAliyunOSS from "./DeployToAliyunOSS";
import DeployToAliyunCDN from "./DeployToAliyunCDN";
import DeployToAliyunCLB from "./DeployToAliyunCLB";
import DeployToAliyunALB from "./DeployToAliyunALB";
+import DeployToAliyunNLB from "./DeployToAliyunNLB";
import DeployToTencentCDN from "./DeployToTencentCDN";
import DeployToTencentCLB from "./DeployToTencentCLB";
import DeployToTencentCOS from "./DeployToTencentCOS";
@@ -126,6 +127,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
case "aliyun-alb":
childComponent =