diff --git a/go.mod b/go.mod
index 0140ddc8..861bfceb 100644
--- a/go.mod
+++ b/go.mod
@@ -140,6 +140,7 @@ require (
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/vultr/govultr/v3 v3.21.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
diff --git a/go.sum b/go.sum
index 74227562..959dfd1a 100644
--- a/go.sum
+++ b/go.sum
@@ -887,6 +887,8 @@ github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0
github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
github.com/volcengine/volcengine-go-sdk v1.1.30 h1:D85LL8euXgxwsZzwQ0azT/hannEsiKxKY4h/7/HU764=
github.com/volcengine/volcengine-go-sdk v1.1.30/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
+github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM=
+github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
diff --git a/internal/certapply/applicators/sp_vultr.go b/internal/certapply/applicators/sp_vultr.go
new file mode 100644
index 00000000..6101a023
--- /dev/null
+++ b/internal/certapply/applicators/sp_vultr.go
@@ -0,0 +1,29 @@
+package applicators
+
+import (
+ "fmt"
+
+ "github.com/go-acme/lego/v4/challenge"
+
+ "github.com/certimate-go/certimate/internal/domain"
+ "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/vultr"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeVultr, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
+ credentials := domain.AccessConfigForVultr{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := vultr.NewChallengeProvider(&vultr.ChallengeProviderConfig{
+ ApiKey: credentials.ApiKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return provider, err
+ }); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/certapply/config.go b/internal/certapply/config.go
index 8b36d778..d47753fb 100644
--- a/internal/certapply/config.go
+++ b/internal/certapply/config.go
@@ -15,6 +15,7 @@ import (
var acmeDirUrls = map[string]string{
string(domain.CAProviderTypeLetsEncrypt): "https://acme-v02.api.letsencrypt.org/directory",
string(domain.CAProviderTypeLetsEncryptStaging): "https://acme-staging-v02.api.letsencrypt.org/directory",
+ string(domain.CAProviderTypeActalisSSL): "https://acme-api.actalis.com/acme/directory",
string(domain.CAProviderTypeBuypass): "https://api.buypass.com/acme/directory",
string(domain.CAProviderTypeGoogleTrustServices): "https://dv.acme-v02.api.pki.goog/directory",
string(domain.CAProviderTypeSSLCom): "https://acme.ssl.com/sslcom-dv-rsa",
diff --git a/internal/certdeploy/deployers/sp_aliyun_cdn.go b/internal/certdeploy/deployers/sp_aliyun_cdn.go
index cd446f50..3301adaf 100644
--- a/internal/certdeploy/deployers/sp_aliyun_cdn.go
+++ b/internal/certdeploy/deployers/sp_aliyun_cdn.go
@@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
+ Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err
diff --git a/internal/certdeploy/deployers/sp_aliyund_cdn.go b/internal/certdeploy/deployers/sp_aliyun_dcdn.go
similarity index 92%
rename from internal/certdeploy/deployers/sp_aliyund_cdn.go
rename to internal/certdeploy/deployers/sp_aliyun_dcdn.go
index 2a8279b7..8725d0ef 100644
--- a/internal/certdeploy/deployers/sp_aliyund_cdn.go
+++ b/internal/certdeploy/deployers/sp_aliyun_dcdn.go
@@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
+ Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err
diff --git a/internal/certdeploy/deployers/sp_kubernetes_secret.go b/internal/certdeploy/deployers/sp_kubernetes_secret.go
index 85d6fc05..8fe56574 100644
--- a/internal/certdeploy/deployers/sp_kubernetes_secret.go
+++ b/internal/certdeploy/deployers/sp_kubernetes_secret.go
@@ -2,6 +2,7 @@ package deployers
import (
"fmt"
+ "strings"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
@@ -16,6 +17,50 @@ func init() {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
+ parseKeyValueMap := func(s string) (map[string]string, error) {
+ result := make(map[string]string)
+
+ lines := strings.Split(s, "\n")
+ for i, line := range lines {
+ if strings.TrimSpace(line) == "" {
+ continue
+ }
+
+ pos := strings.Index(line, ":")
+ if pos == -1 {
+ return nil, fmt.Errorf("invalid line format at line %d", i+1)
+ }
+
+ key := strings.TrimSpace(line[:pos])
+ value := strings.TrimSpace(line[pos+1:])
+ if key == "" {
+ return nil, fmt.Errorf("invalid key at line %d", i+1)
+ }
+
+ result[key] = value
+ }
+
+ return result, nil
+ }
+
+ secretAnnotations := make(map[string]string)
+ if secretAnnotationsString := xmaps.GetString(options.ProviderExtendedConfig, "secretAnnotations"); secretAnnotationsString != "" {
+ temp, err := parseKeyValueMap(secretAnnotationsString)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse kubernetes secret annotations: %w", err)
+ }
+ secretAnnotations = temp
+ }
+
+ secretLabels := make(map[string]string)
+ if secretLabelsString := xmaps.GetString(options.ProviderExtendedConfig, "secretLabels"); secretLabelsString != "" {
+ temp, err := parseKeyValueMap(secretLabelsString)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse kubernetes secret labels: %w", err)
+ }
+ secretLabels = temp
+ }
+
provider, err := k8ssecret.NewSSLDeployerProvider(&k8ssecret.SSLDeployerProviderConfig{
KubeConfig: credentials.KubeConfig,
Namespace: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"),
@@ -23,6 +68,8 @@ func init() {
SecretType: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"),
SecretDataKeyForCrt: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"),
SecretDataKeyForKey: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"),
+ SecretAnnotations: secretAnnotations,
+ SecretLabels: secretLabels,
})
return provider, err
}); err != nil {
diff --git a/internal/certdeploy/deployers/sp_webhook.go b/internal/certdeploy/deployers/sp_webhook.go
index 271fc744..c53ff730 100644
--- a/internal/certdeploy/deployers/sp_webhook.go
+++ b/internal/certdeploy/deployers/sp_webhook.go
@@ -43,6 +43,7 @@ func init() {
WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString),
Method: credentials.Method,
Headers: mergedHeaders,
+ Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"),
AllowInsecureConnections: credentials.AllowInsecureConnections,
})
return provider, err
diff --git a/internal/domain/access.go b/internal/domain/access.go
index d43462ed..ceb569b8 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -44,6 +44,10 @@ type AccessConfigForACMEHttpReq struct {
Password string `json:"password,omitempty"`
}
+type AccessConfigForActalisSSL struct {
+ AccessConfigForACMEExternalAccountBinding
+}
+
type AccessConfigForAliyun struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
@@ -402,6 +406,10 @@ type AccessConfigForVolcEngine struct {
SecretAccessKey string `json:"secretAccessKey"`
}
+type AccessConfigForVultr struct {
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForWangsu struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index e35919a7..fec6d3bf 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -13,6 +13,7 @@ const (
AccessProviderTypeACMECA = AccessProviderType("acmeca")
AccessProviderTypeACMEDNS = AccessProviderType("acmedns")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
+ AccessProviderTypeActalisSSL = AccessProviderType("actalisssl")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
AccessProviderTypeAliyun = AccessProviderType("aliyun")
AccessProviderTypeAPISIX = AccessProviderType("apisix")
@@ -86,6 +87,7 @@ const (
AccessProviderTypeUpyun = AccessProviderType("upyun")
AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
+ AccessProviderTypeVultr = AccessProviderType("vultr")
AccessProviderTypeWangsu = AccessProviderType("wangsu")
AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWeComBot = AccessProviderType("wecombot")
@@ -104,6 +106,7 @@ NOTICE: If you add new constant, please keep ASCII order.
*/
const (
CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA)
+ CAProviderTypeActalisSSL = CAProviderType(AccessProviderTypeActalisSSL)
CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass)
CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices)
CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt)
@@ -171,6 +174,7 @@ const (
ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel)
ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS]
ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns")
+ ACMEDns01ProviderTypeVultr = ACMEDns01ProviderType(AccessProviderTypeVultr)
ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn)
)
diff --git a/internal/notify/notifiers/sp_webhook.go b/internal/notify/notifiers/sp_webhook.go
index db0c4412..7cacd0a6 100644
--- a/internal/notify/notifiers/sp_webhook.go
+++ b/internal/notify/notifiers/sp_webhook.go
@@ -43,6 +43,7 @@ func init() {
WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString),
Method: credentials.Method,
Headers: mergedHeaders,
+ Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"),
AllowInsecureConnections: credentials.AllowInsecureConnections,
})
return provider, err
diff --git a/internal/workflow/service.go b/internal/workflow/service.go
index adf2a39e..d869a1c0 100644
--- a/internal/workflow/service.go
+++ b/internal/workflow/service.go
@@ -111,7 +111,9 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR
workflowRun = resp
}
- s.dispatcher.Start(ctx, workflowRun.Id)
+ if err := s.dispatcher.Start(ctx, workflowRun.Id); err != nil {
+ return nil, err
+ }
return &dtos.WorkflowStartRunResp{RunId: workflowRun.Id}, nil
}
@@ -131,7 +133,9 @@ func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCance
return nil, errors.New("workflow run is not pending or processing")
}
- s.dispatcher.Cancel(ctx, workflowRun.Id)
+ if err := s.dispatcher.Cancel(ctx, workflowRun.Id); err != nil {
+ return nil, err
+ }
return &dtos.WorkflowCancelRunResp{}, nil
}
diff --git a/pkg/core/notifier/providers/webhook/webhook.go b/pkg/core/notifier/providers/webhook/webhook.go
index 09ae91e3..3f57da7d 100644
--- a/pkg/core/notifier/providers/webhook/webhook.go
+++ b/pkg/core/notifier/providers/webhook/webhook.go
@@ -27,6 +27,9 @@ type NotifierProviderConfig struct {
Method string `json:"method,omitempty"`
// 请求标头。
Headers map[string]string `json:"headers,omitempty"`
+ // 请求超时(单位:秒)。
+ // 零值时默认值 30。
+ Timeout int32 `json:"timeout,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
@@ -48,6 +51,9 @@ func NewNotifierProvider(config *NotifierProviderConfig) (*NotifierProvider, err
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second)
+ if config.Timeout > 0 {
+ client.SetTimeout(time.Duration(config.Timeout) * time.Second)
+ }
if config.AllowInsecureConnections {
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go
new file mode 100644
index 00000000..c3badc34
--- /dev/null
+++ b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go
@@ -0,0 +1,38 @@
+package vultr
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/providers/dns/vultr"
+
+ "github.com/certimate-go/certimate/pkg/core"
+)
+
+type ChallengeProviderConfig struct {
+ ApiKey string `json:"apiKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
+ if config == nil {
+ return nil, errors.New("the configuration of the acme challenge provider is nil")
+ }
+
+ providerConfig := vultr.NewDefaultConfig()
+ providerConfig.APIKey = config.ApiKey
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := vultr.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go
index a59924d7..00a541d7 100644
--- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go
+++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go
@@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
+ "strconv"
"strings"
- "time"
alicdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
+ "github.com/samber/lo"
+
"github.com/certimate-go/certimate/pkg/core"
+ sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
+ // 阿里云地域。
+ Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
- config *SSLDeployerProviderConfig
- logger *slog.Logger
- sdkClient *alicdn.Client
+ config *SSLDeployerProviderConfig
+ logger *slog.Logger
+ sdkClient *alicdn.Client
+ sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
+ sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
+ AccessKeyId: config.AccessKeyId,
+ AccessKeySecret: config.AccessKeySecret,
+ ResourceGroupId: config.ResourceGroupId,
+ Region: lo.
+ If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
+ Else("ap-southeast-1"),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not create ssl manager: %w", err)
+ }
+
return &SSLDeployerProvider{
- config: config,
- logger: slog.Default(),
- sdkClient: client,
+ config: config,
+ logger: slog.Default(),
+ sdkClient: client,
+ sslManager: sslmgr,
}, nil
}
@@ -59,18 +78,32 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
}
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
+ if d.config.Domain == "" {
+ return nil, errors.New("config `domain` is required")
+ }
+
+ // 上传证书
+ upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload certificate file: %w", err)
+ } else {
+ d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
+ }
+
// "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式
domain := strings.TrimPrefix(d.config.Domain, "*")
// 设置 CDN 域名域名证书
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{
- DomainName: tea.String(domain),
- CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
- CertType: tea.String("upload"),
+ DomainName: tea.String(domain),
+ CertType: tea.String("cas"),
+ CertId: tea.Int64(int64(certId)),
+ CertRegion: lo.
+ If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
+ Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
- SSLPub: tea.String(certPEM),
- SSLPri: tea.String(privkeyPEM),
}
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp))
diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go
index 6118828f..a599e397 100644
--- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go
+++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go
@@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
+ "strconv"
"strings"
- "time"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
alidcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
"github.com/alibabacloud-go/tea/tea"
+ "github.com/samber/lo"
+
"github.com/certimate-go/certimate/pkg/core"
+ sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
+ // 阿里云地域。
+ Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
- config *SSLDeployerProviderConfig
- logger *slog.Logger
- sdkClient *alidcdn.Client
+ config *SSLDeployerProviderConfig
+ logger *slog.Logger
+ sdkClient *alidcdn.Client
+ sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
+ sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
+ AccessKeyId: config.AccessKeyId,
+ AccessKeySecret: config.AccessKeySecret,
+ ResourceGroupId: config.ResourceGroupId,
+ Region: lo.
+ If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
+ Else("ap-southeast-1"),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not create ssl manager: %w", err)
+ }
+
return &SSLDeployerProvider{
- config: config,
- logger: slog.Default(),
- sdkClient: client,
+ config: config,
+ logger: slog.Default(),
+ sdkClient: client,
+ sslManager: sslmgr,
}, nil
}
@@ -63,18 +82,28 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
+ // 上传证书
+ upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload certificate file: %w", err)
+ } else {
+ d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
+ }
+
// "*.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
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{
- DomainName: tea.String(domain),
- CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
- CertType: tea.String("upload"),
+ DomainName: tea.String(domain),
+ CertType: tea.String("cas"),
+ CertId: tea.Int64(int64(certId)),
+ CertRegion: lo.
+ If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
+ Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
- SSLPub: tea.String(certPEM),
- SSLPri: tea.String(privkeyPEM),
}
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp))
diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go
index 12f8ceec..dec81ee7 100644
--- a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go
+++ b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go
@@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// 为网站业务转发规则关联 SSL 证书
// REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert
- certId, _ := strconv.Atoi(upres.CertId)
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
associateWebCertReq := &aliddos.AssociateWebCertRequest{
Domain: tea.String(d.config.Domain),
CertId: tea.Int32(int32(certId)),
diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go
index 08173368..07ea9c10 100644
--- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go
+++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go
@@ -5,12 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
- "time"
+ "strconv"
+ "strings"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
alivod "github.com/alibabacloud-go/vod-20170321/v4/client"
+ "github.com/samber/lo"
+
"github.com/certimate-go/certimate/pkg/core"
+ sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -27,9 +31,10 @@ type SSLDeployerProviderConfig struct {
}
type SSLDeployerProvider struct {
- config *SSLDeployerProviderConfig
- logger *slog.Logger
- sdkClient *alivod.Client
+ config *SSLDeployerProviderConfig
+ logger *slog.Logger
+ sdkClient *alivod.Client
+ sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -44,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
+ sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
+ AccessKeyId: config.AccessKeyId,
+ AccessKeySecret: config.AccessKeySecret,
+ ResourceGroupId: config.ResourceGroupId,
+ Region: lo.
+ If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
+ Else("ap-southeast-1"),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not create ssl manager: %w", err)
+ }
+
return &SSLDeployerProvider{
- config: config,
- logger: slog.Default(),
- sdkClient: client,
+ config: config,
+ logger: slog.Default(),
+ sdkClient: client,
+ sslManager: sslmgr,
}, nil
}
@@ -64,15 +82,25 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
+ // 上传证书
+ upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
+ if err != nil {
+ return nil, fmt.Errorf("failed to upload certificate file: %w", err)
+ } else {
+ d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
+ }
+
// 设置域名证书
// REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{
- DomainName: tea.String(d.config.Domain),
- CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
- CertType: tea.String("upload"),
+ DomainName: tea.String(d.config.Domain),
+ CertType: tea.String("cas"),
+ CertId: tea.Int64(int64(certId)),
+ CertRegion: lo.
+ If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
+ Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
- SSLPub: tea.String(certPEM),
- SSLPri: tea.String(privkeyPEM),
}
setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificate(setVodDomainSSLCertificateReq)
d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp))
diff --git a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go
index 6659c62d..d1408d9e 100644
--- a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go
+++ b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go
@@ -114,8 +114,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
modifySiteReq := &btwafsdk.ModifySiteRequest{
SiteId: lo.ToPtr(siteId),
Type: lo.ToPtr("openCert"),
- Server: &btwafsdk.SiteServerInfo{
- ListenSSLPorts: lo.ToPtr([]int32{d.config.SitePort}),
+ Server: &btwafsdk.SiteServerInfoMod{
+ ListenSSLPorts: lo.ToPtr([]string{fmt.Sprintf("%d", d.config.SitePort)}),
SSL: &btwafsdk.SiteServerSSLInfo{
IsSSL: lo.ToPtr(int32(1)),
FullChain: lo.ToPtr(certPEM),
diff --git a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go
index e9a16683..b90db949 100644
--- a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go
+++ b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go
@@ -30,6 +30,10 @@ type SSLDeployerProviderConfig struct {
SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"`
// Kubernetes Secret 中用于存放私钥的 Key。
SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"`
+ // Kubernetes Secret 注解。
+ SecretAnnotations map[string]string `json:"secretAnnotations,omitempty"`
+ // Kubernetes Secret 标签。
+ SecretLabels map[string]string `json:"secretLabels,omitempty"`
}
type SSLDeployerProvider struct {
@@ -94,6 +98,17 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
"certimate/issuer-sn": certX509.Issuer.SerialNumber,
"certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","),
}
+ secretLabels := map[string]string{}
+ if d.config.SecretAnnotations != nil {
+ for k, v := range d.config.SecretAnnotations {
+ secretAnnotations[k] = v
+ }
+ }
+ if d.config.SecretLabels != nil {
+ for k, v := range d.config.SecretLabels {
+ secretLabels[k] = v
+ }
+ }
// 获取 Secret 实例,如果不存在则创建
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{})
@@ -106,6 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
ObjectMeta: k8smeta.ObjectMeta{
Name: d.config.SecretName,
Annotations: secretAnnotations,
+ Labels: secretLabels,
},
Type: k8score.SecretType(d.config.SecretType),
}
@@ -131,6 +147,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
secretPayload.ObjectMeta.Annotations[k] = v
}
}
+ if secretPayload.ObjectMeta.Labels == nil {
+ secretPayload.ObjectMeta.Labels = secretLabels
+ } else {
+ for k, v := range secretLabels {
+ secretPayload.ObjectMeta.Labels[k] = v
+ }
+ }
if secretPayload.Data == nil {
secretPayload.Data = make(map[string][]byte)
}
diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go
index 8af0fa06..1accc617 100644
--- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go
+++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go
@@ -80,7 +80,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// RCDN SSL 绑定域名
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
- certId, _ := strconv.Atoi(upres.CertId)
+ certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
CertId: int32(certId),
Domains: []string{d.config.Domain},
diff --git a/pkg/core/ssl-deployer/providers/webhook/webhook.go b/pkg/core/ssl-deployer/providers/webhook/webhook.go
index 2bf1aa14..9de2d8fc 100644
--- a/pkg/core/ssl-deployer/providers/webhook/webhook.go
+++ b/pkg/core/ssl-deployer/providers/webhook/webhook.go
@@ -28,6 +28,9 @@ type SSLDeployerProviderConfig struct {
Method string `json:"method,omitempty"`
// 请求标头。
Headers map[string]string `json:"headers,omitempty"`
+ // 请求超时(单位:秒)。
+ // 零值时默认值 30。
+ Timeout int32 `json:"timeout,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
@@ -49,6 +52,9 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second)
+ if config.Timeout > 0 {
+ client.SetTimeout(time.Duration(config.Timeout) * time.Second)
+ }
if config.AllowInsecureConnections {
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
diff --git a/pkg/sdk3rd/btwaf/api_modify_site.go b/pkg/sdk3rd/btwaf/api_modify_site.go
index 516277ec..a93f8ba8 100644
--- a/pkg/sdk3rd/btwaf/api_modify_site.go
+++ b/pkg/sdk3rd/btwaf/api_modify_site.go
@@ -6,9 +6,9 @@ import (
)
type ModifySiteRequest struct {
- SiteId *string `json:"site_id,omitempty"`
- Type *string `json:"types,omitempty"`
- Server *SiteServerInfo `json:"server,omitempty"`
+ SiteId *string `json:"site_id,omitempty"`
+ Type *string `json:"types,omitempty"`
+ Server *SiteServerInfoMod `json:"server,omitempty"`
}
type ModifySiteResponse struct {
diff --git a/pkg/sdk3rd/btwaf/types.go b/pkg/sdk3rd/btwaf/types.go
index 8c75a44b..b1c26de3 100644
--- a/pkg/sdk3rd/btwaf/types.go
+++ b/pkg/sdk3rd/btwaf/types.go
@@ -32,6 +32,11 @@ type SiteServerInfo struct {
SSL *SiteServerSSLInfo `json:"ssl,omitempty"`
}
+type SiteServerInfoMod struct {
+ ListenSSLPorts *[]string `json:"listen_ssl_port,omitempty"`
+ SSL *SiteServerSSLInfo `json:"ssl,omitempty"`
+}
+
type SiteServerSSLInfo struct {
IsSSL *int32 `json:"is_ssl,omitempty"`
FullChain *string `json:"full_chain,omitempty"`
diff --git a/ui/public/imgs/providers/actalisssl.png b/ui/public/imgs/providers/actalisssl.png
new file mode 100644
index 00000000..3897c6b4
Binary files /dev/null and b/ui/public/imgs/providers/actalisssl.png differ
diff --git a/ui/public/imgs/providers/vultr.svg b/ui/public/imgs/providers/vultr.svg
new file mode 100644
index 00000000..cf49d15d
--- /dev/null
+++ b/ui/public/imgs/providers/vultr.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 976434ae..69112aa5 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -15,6 +15,7 @@ import AccessConfigFieldsProvider1Panel from "./forms/AccessConfigFieldsProvider
import AccessConfigFieldsProviderACMECA from "./forms/AccessConfigFieldsProviderACMECA";
import AccessConfigFieldsProviderACMEDNS from "./forms/AccessConfigFieldsProviderACMEDNS";
import AccessConfigFieldsProviderACMEHttpReq from "./forms/AccessConfigFieldsProviderACMEHttpReq";
+import AccessConfigFieldsProviderActalisSSL from "./forms/AccessConfigFieldsProviderActalisSSL";
import AccessConfigFieldsProviderAliyun from "./forms/AccessConfigFieldsProviderAliyun";
import AccessConfigFieldsProviderAPISIX from "./forms/AccessConfigFieldsProviderAPISIX";
import AccessConfigFieldsProviderAWS from "./forms/AccessConfigFieldsProviderAWS";
@@ -80,6 +81,7 @@ import AccessConfigFieldsProviderUniCloud from "./forms/AccessConfigFieldsProvid
import AccessConfigFieldsProviderUpyun from "./forms/AccessConfigFieldsProviderUpyun";
import AccessConfigFieldsProviderVercel from "./forms/AccessConfigFieldsProviderVercel";
import AccessConfigFieldsProviderVolcEngine from "./forms/AccessConfigFieldsProviderVolcEngine";
+import AccessConfigFieldsProviderVultr from "./forms/AccessConfigFieldsProviderVultr";
import AccessConfigFieldsProviderWangsu from "./forms/AccessConfigFieldsProviderWangsu";
import AccessConfigFieldsProviderWebhook from "./forms/AccessConfigFieldsProviderWebhook";
import AccessConfigFieldsProviderWeComBot from "./forms/AccessConfigFieldsProviderWeComBot";
@@ -140,6 +142,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, ..
case ACCESS_PROVIDERS.ACMEHTTPREQ: {
return