diff --git a/README.md b/README.md
index 4fe79035..40d590e1 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ make local.run
| 服务商 | 支持申请证书 | 支持部署证书 | 备注 |
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
-| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、CLB(SLB) |
+| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN、SLB |
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 COS、CDN、CLB |
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB |
| 七牛云 | | √ | 可部署到七牛云 CDN |
diff --git a/README_EN.md b/README_EN.md
index 74cb3098..8194ac5b 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -70,22 +70,22 @@ password:1234567890
## List of Supported Providers
-| Provider | Registration | Deployment | Remarks |
-| :-----------: | :----------: | :--------: | ----------------------------------------------------------------------------------------------------- |
-| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN, CLB(SLB) |
-| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, CLB |
-| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
-| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
-| AWS | √ | | Supports domains managed on AWS Route53 |
-| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
-| GoDaddy | √ | | Supports domains registered on GoDaddy |
-| Namesilo | √ | | Supports domains registered on Namesilo |
-| PowerDNS | √ | | Supports domains managed on PowerDNS |
-| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
-| Local Deploy | | √ | Supports deployment to local servers |
-| SSH | | √ | Supports deployment to SSH servers |
-| Webhook | | √ | Supports callback to Webhook |
-| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
+| Provider | Registration | Deployment | Remarks |
+| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------------ |
+| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN, SLB |
+| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, CLB |
+| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN, ELB |
+| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
+| AWS | √ | | Supports domains managed on AWS Route53 |
+| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
+| GoDaddy | √ | | Supports domains registered on GoDaddy |
+| Namesilo | √ | | Supports domains registered on Namesilo |
+| PowerDNS | √ | | Supports domains managed on PowerDNS |
+| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
+| Local Deploy | | √ | Supports deployment to local servers |
+| SSH | | √ | Supports deployment to SSH servers |
+| Webhook | | √ | Supports callback to Webhook |
+| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
## Screenshots
diff --git a/go.mod b/go.mod
index f01708ee..fc4267b9 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.22.0
toolchain go1.23.2
require (
+ github.com/alibabacloud-go/alb-20200616/v2 v2.2.1
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
diff --git a/go.sum b/go.sum
index 64b89b32..3475675b 100644
--- a/go.sum
+++ b/go.sum
@@ -29,6 +29,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
+github.com/alibabacloud-go/alb-20200616/v2 v2.2.1 h1:b8ixnrkFhWrmJQd+iEE1UWPD5vdyC3d9l7G0uvkfi2s=
+github.com/alibabacloud-go/alb-20200616/v2 v2.2.1/go.mod h1:cPdZwovbqpv+5nM/HnMwZpG5q0/gBuX31hu2H1VoyrM=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
diff --git a/internal/deployer/aliyun_alb.go b/internal/deployer/aliyun_alb.go
new file mode 100644
index 00000000..979f25f8
--- /dev/null
+++ b/internal/deployer/aliyun_alb.go
@@ -0,0 +1,232 @@
+package deployer
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+
+ alb20200616 "github.com/alibabacloud-go/alb-20200616/v2/client"
+ openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+ "github.com/alibabacloud-go/tea/tea"
+
+ "github.com/usual2970/certimate/internal/domain"
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+)
+
+type AliyunALBDeployer struct {
+ option *DeployerOption
+ infos []string
+
+ sdkClient *alb20200616.Client
+ sslUploader uploader.Uploader
+}
+
+func NewAliyunALBDeployer(option *DeployerOption) (Deployer, error) {
+ access := &domain.AliyunAccess{}
+ json.Unmarshal([]byte(option.Access), access)
+
+ client, err := (&AliyunALBDeployer{}).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 &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) GetInfo() []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) (*alb20200616.Client, error) {
+ if region == "" {
+ region = "cn-hangzhou" // ALB 服务默认区域:华东一杭州
+ }
+
+ aConfig := &openapi.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 := alb20200616.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")
+ }
+
+ // 查询负载均衡实例的详细信息
+ // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-getloadbalancerattribute
+ getLoadBalancerAttributeReq := &alb20200616.GetLoadBalancerAttributeRequest{
+ LoadBalancerId: tea.String(aliLoadbalancerId),
+ }
+ getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'alb.GetLoadBalancerAttribute': %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("已查询到 ALB 负载均衡实例", getLoadBalancerAttributeResp))
+
+ // 查询监听列表
+ // REF: https://help.aliyun.com/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-listlisteners
+ aliListenerIds := make([]string, 0)
+ listListenersPage := 1
+ listListenersLimit := int32(100)
+ var listListenersToken *string = nil
+ for {
+ listListenersReq := &alb20200616.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 fmt.Errorf("failed to execute sdk request 'alb.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("已查询到 ALB 负载均衡实例下的全部 HTTPS 监听", 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 *AliyunALBDeployer) 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 *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 := &alb20200616.GetListenerAttributeRequest{
+ ListenerId: tea.String(aliListenerId),
+ }
+ getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'alb.GetListenerAttribute': %w", err)
+ }
+
+ 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 := &alb20200616.UpdateListenerAttributeRequest{
+ ListenerId: tea.String(aliListenerId),
+ Certificates: []*alb20200616.UpdateListenerAttributeRequestCertificates{{
+ CertificateId: tea.String(aliCertId),
+ }},
+ }
+ updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq)
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request 'alb.UpdateListenerAttribute': %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("已更新 ALB 监听配置", updateListenerAttributeResp))
+
+ return nil
+}
diff --git a/internal/deployer/aliyun_clb.go b/internal/deployer/aliyun_clb.go
index 9a0f8789..0c6466a7 100644
--- a/internal/deployer/aliyun_clb.go
+++ b/internal/deployer/aliyun_clb.go
@@ -113,7 +113,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
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 := &slb20140515.DescribeLoadBalancerAttributeRequest{
RegionId: tea.String(d.option.DeployConfig.GetConfigAsString("region")),
@@ -124,7 +124,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
return fmt.Errorf("failed to execute sdk request 'slb.DescribeLoadBalancerAttribute': %w", err)
}
- d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡器实例", describeLoadBalancerAttributeResp))
+ d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例", describeLoadBalancerAttributeResp))
// 查询监听列表
// REF: https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-describeloadbalancerlisteners
@@ -159,7 +159,7 @@ func (d *AliyunCLBDeployer) deployToLoadbalancer(ctx context.Context) error {
}
}
- d.infos = append(d.infos, toStr("已查询到 ELB 负载均衡器下的监听器", aliListenerPorts))
+ d.infos = append(d.infos, toStr("已查询到 CLB 负载均衡实例下的全部 HTTPS 监听", aliListenerPorts))
// 上传证书到 SLB
uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx
index 5fdc4ab1..395fd9c8 100644
--- a/ui/src/components/certimate/DeployEditDialog.tsx
+++ b/ui/src/components/certimate/DeployEditDialog.tsx
@@ -12,6 +12,7 @@ import { Context as DeployEditContext } from "./DeployEdit";
import DeployToAliyunOSS from "./DeployToAliyunOSS";
import DeployToAliyunCDN from "./DeployToAliyunCDN";
import DeployToAliyunCLB from "./DeployToAliyunCLB";
+import DeployToAliyunALB from "./DeployToAliyunALB";
import DeployToTencentCDN from "./DeployToTencentCDN";
import DeployToTencentCLB from "./DeployToTencentCLB";
import DeployToTencentCOS from "./DeployToTencentCOS";
@@ -122,6 +123,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
case "aliyun-clb":
childComponent =