diff --git a/README_EN.md b/README_EN.md
index b92fc82a..2c3a2a8a 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -138,7 +138,7 @@ The following hosting providers are supported:
| [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN |
| [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN |
| [SafeLine](https://waf.chaitin.com/) | Supports deployment to SafeLine WAF |
-| [BaoTa Panel](https://www.bt.cn/) | Supports deployment to BaoTa Panel sites |
+| [aaPanel](https://www.aapanel.com/) | Supports deployment to aaPanel (aka BaoTaPanel) sites |
| [AWS](https://aws.amazon.com/) | Supports deployment to AWS CloudFront |
| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN |
| [CacheFly](https://www.cachefly.com/) | Supports deployment to CacheFly CDN |
diff --git a/go.mod b/go.mod
index 40efa5a4..ecf902f8 100644
--- a/go.mod
+++ b/go.mod
@@ -51,12 +51,16 @@ require (
)
require (
- github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
- github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
+ github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/G-Core/gcorelabscdn-go v1.0.26 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index d15c396f..a9d2d038 100644
--- a/go.sum
+++ b/go.sum
@@ -54,10 +54,17 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WW
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
+github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0 h1:btEsytNrA4TG3edZnnUnzOz8W2MjOd6Bu3/7xyOXSOY=
+github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0/go.mod h1:5SlTxxL1U4LLipEr7pAbnu6Ck5y3aIEu4L/tVbGmpsY=
+github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
+github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
@@ -68,10 +75,16 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1 h1:HUJQzFYTv7t3V1dxPms52eEgl0l9xCNqutDrY45Lvmw=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.3.1/go.mod h1:ig/8nSkzmfxm5QGeIy5JYIEj8JEFy5JxvY3OB1YNRC4=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns/azure-dns.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns/azure-dns.go
index bf36f3fb..eaf46bce 100644
--- a/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns/azure-dns.go
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns/azure-dns.go
@@ -1,13 +1,12 @@
package azuredns
import (
- "fmt"
- "strings"
"time"
- "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/azuredns"
+
+ azcommon "github.com/usual2970/certimate/internal/pkg/vendors/azure-sdk/common"
)
type ChallengeProviderConfig struct {
@@ -29,16 +28,11 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider,
providerConfig.ClientID = config.ClientId
providerConfig.ClientSecret = config.ClientSecret
if config.CloudName != "" {
- switch strings.ToLower(config.CloudName) {
- case "default", "public", "cloud", "azurecloud":
- providerConfig.Environment = cloud.AzurePublic
- case "usgovernment", "azureusgovernment":
- providerConfig.Environment = cloud.AzureGovernment
- case "china", "chinacloud", "azurechina", "azurechinacloud":
- providerConfig.Environment = cloud.AzureChina
- default:
- return nil, fmt.Errorf("azuredns: unknown environment %s", config.CloudName)
+ env, err := azcommon.GetCloudEnvironmentConfiguration(config.CloudName)
+ if err != nil {
+ return nil, err
}
+ providerConfig.Environment = env
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
diff --git a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
index 325a5319..4743b7cd 100644
--- a/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
+++ b/internal/pkg/core/deployer/providers/dogecloud-cdn/dogecloud_cdn.go
@@ -47,6 +47,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
}
return &DeployerProvider{
+ config: config,
logger: logger.NewNilLogger(),
sdkClient: client,
sslUploader: uploader,
diff --git a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
index 73372781..2b248adc 100644
--- a/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
+++ b/internal/pkg/core/deployer/providers/volcengine-clb/volcengine_clb.go
@@ -60,6 +60,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
return &DeployerProvider{
config: config,
+ logger: logger.NewNilLogger(),
sdkClient: client,
sslUploader: uploader,
}, nil
diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go
index 18ffd3aa..2f5db477 100644
--- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go
+++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go
@@ -2,14 +2,13 @@
import (
"context"
- "fmt"
- "time"
aws "github.com/aws/aws-sdk-go-v2/aws"
awsCfg "github.com/aws/aws-sdk-go-v2/config"
awsCred "github.com/aws/aws-sdk-go-v2/credentials"
awsAcm "github.com/aws/aws-sdk-go-v2/service/acm"
xerrors "github.com/pkg/errors"
+ "golang.org/x/exp/slices"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
@@ -54,13 +53,74 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
return nil, err
}
- // 生成 AWS 所需的服务端证书和证书链参数
+ // 生成 AWS 业务参数
scertPem, _ := certs.ConvertCertificateToPEM(certX509)
bcertPem := certPem
- // 生成新证书名(需符合 AWS 命名规则)
- var certId, certName string
- certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
+ // 获取证书列表,避免重复上传
+ // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
+ listCertificatesNextToken := new(string)
+ listCertificatesMaxItems := int32(1000)
+ for {
+ listCertificatesReq := &awsAcm.ListCertificatesInput{
+ NextToken: listCertificatesNextToken,
+ MaxItems: aws.Int32(listCertificatesMaxItems),
+ }
+ listCertificatesResp, err := u.sdkClient.ListCertificates(context.TODO(), listCertificatesReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'acm.ListCertificates'")
+ }
+
+ for _, certSummary := range listCertificatesResp.CertificateSummaryList {
+ // 先对比证书有效期
+ if certSummary.NotBefore == nil || !certSummary.NotBefore.Equal(certX509.NotBefore) {
+ continue
+ }
+ if certSummary.NotAfter == nil || !certSummary.NotAfter.Equal(certX509.NotAfter) {
+ continue
+ }
+
+ // 再对比证书多域名
+ if !slices.Equal(certX509.DNSNames, certSummary.SubjectAlternativeNameSummaries) {
+ continue
+ }
+
+ // 最后对比证书内容
+ // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListTagsForCertificate.html
+ getCertificateReq := &awsAcm.GetCertificateInput{
+ CertificateArn: certSummary.CertificateArn,
+ }
+ getCertificateResp, err := u.sdkClient.GetCertificate(context.TODO(), getCertificateReq)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'acm.GetCertificate'")
+ } else {
+ oldCertPem := aws.ToString(getCertificateResp.CertificateChain)
+ if oldCertPem == "" {
+ oldCertPem = aws.ToString(getCertificateResp.Certificate)
+ }
+
+ oldCertX509, err := certs.ParseCertificateFromPEM(oldCertPem)
+ if err != nil {
+ continue
+ }
+
+ if !certs.EqualCertificate(certX509, oldCertX509) {
+ continue
+ }
+ }
+
+ // 如果以上信息都一致,则视为已存在相同证书,直接返回
+ return &uploader.UploadResult{
+ CertId: *certSummary.CertificateArn,
+ }, nil
+ }
+
+ if listCertificatesResp.NextToken == nil || len(listCertificatesResp.CertificateSummaryList) < int(listCertificatesMaxItems) {
+ break
+ } else {
+ listCertificatesNextToken = listCertificatesResp.NextToken
+ }
+ }
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
@@ -74,10 +134,8 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
return nil, xerrors.Wrap(err, "failed to execute sdk request 'acm.ImportCertificate'")
}
- certId = *importCertificateResp.CertificateArn
return &uploader.UploadResult{
- CertId: certId,
- CertName: certName,
+ CertId: *importCertificateResp.CertificateArn,
}, nil
}
diff --git a/internal/pkg/core/uploader/providers/azure-keyvault/azure_keyvault.go b/internal/pkg/core/uploader/providers/azure-keyvault/azure_keyvault.go
new file mode 100644
index 00000000..16109171
--- /dev/null
+++ b/internal/pkg/core/uploader/providers/azure-keyvault/azure_keyvault.go
@@ -0,0 +1,181 @@
+package azurekeyvault
+
+import (
+ "context"
+ "crypto/x509"
+ "fmt"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/sdk/azcore"
+ "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+ "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+ "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates"
+ xerrors "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/uploader"
+ "github.com/usual2970/certimate/internal/pkg/utils/certs"
+ azcommon "github.com/usual2970/certimate/internal/pkg/vendors/azure-sdk/common"
+)
+
+type UploaderConfig struct {
+ // Azure TenantId。
+ TenantId string `json:"tenantId"`
+ // Azure ClientId。
+ ClientId string `json:"clientId"`
+ // Azure ClientSecret。
+ ClientSecret string `json:"clientSecret"`
+ // Azure 主权云环境。
+ CloudName string `json:"cloudName,omitempty"`
+ // Key Vault 名称。
+ KeyVaultName string `json:"keyvaultName"`
+}
+
+type UploaderProvider struct {
+ config *UploaderConfig
+ sdkClient *azcertificates.Client
+}
+
+var _ uploader.Uploader = (*UploaderProvider)(nil)
+
+func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ client, err := createSdkClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to create sdk client")
+ }
+
+ return &UploaderProvider{
+ config: config,
+ sdkClient: client,
+ }, nil
+}
+
+func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
+ // 解析证书内容
+ certX509, err := certs.ParseCertificateFromPEM(certPem)
+ if err != nil {
+ return nil, err
+ }
+
+ // 生成 Azure 业务参数
+ const TAG_CERTCN = "certimate/cert-cn"
+ const TAG_CERTSN = "certimate/cert-sn"
+ certCN := certX509.Subject.CommonName
+ certSN := certX509.SerialNumber.Text(16)
+
+ // 获取证书列表,避免重复上传
+ // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificates/get-certificates
+ listCertificatesPager := u.sdkClient.NewListCertificatesPager(nil)
+ for listCertificatesPager.More() {
+ page, err := listCertificatesPager.NextPage(context.TODO())
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.GetCertificates'")
+ }
+
+ for _, certItem := range page.Value {
+ // 先对比证书有效期
+ if certItem.Attributes == nil {
+ continue
+ }
+ if certItem.Attributes.NotBefore == nil || !certItem.Attributes.NotBefore.Equal(certX509.NotBefore) {
+ continue
+ }
+ if certItem.Attributes.Expires == nil || !certItem.Attributes.Expires.Equal(certX509.NotAfter) {
+ continue
+ }
+
+ // 再对比 Tag 中的通用名称
+ if v, ok := certItem.Tags[TAG_CERTCN]; !ok || v == nil {
+ continue
+ } else if *v != certCN {
+ continue
+ }
+
+ // 再对比 Tag 中的序列号
+ if v, ok := certItem.Tags[TAG_CERTSN]; !ok || v == nil {
+ continue
+ } else if *v != certSN {
+ continue
+ }
+
+ // 最后对比证书内容
+ getCertificateResp, err := u.sdkClient.GetCertificate(context.TODO(), certItem.ID.Name(), certItem.ID.Version(), nil)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.GetCertificate'")
+ } else {
+ oldCertX509, err := x509.ParseCertificate(getCertificateResp.CER)
+ if err != nil {
+ continue
+ }
+
+ if !certs.EqualCertificate(certX509, oldCertX509) {
+ continue
+ }
+ }
+
+ // 如果以上信息都一致,则视为已存在相同证书,直接返回
+ return &uploader.UploadResult{
+ CertId: string(*certItem.ID),
+ CertName: certItem.ID.Name(),
+ }, nil
+ }
+ }
+
+ // 生成新证书名(需符合 Azure 命名规则)
+ certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
+
+ // 导入证书
+ // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
+ importCertificateParams := azcertificates.ImportCertificateParameters{
+ Base64EncodedCertificate: to.Ptr(certPem),
+ CertificatePolicy: &azcertificates.CertificatePolicy{
+ SecretProperties: &azcertificates.SecretProperties{
+ ContentType: to.Ptr("application/x-pem-file"),
+ },
+ },
+ Tags: map[string]*string{
+ TAG_CERTCN: to.Ptr(certCN),
+ TAG_CERTSN: to.Ptr(certSN),
+ },
+ }
+ importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), certName, importCertificateParams, nil)
+ if err != nil {
+ return nil, xerrors.Wrap(err, "failed to execute sdk request 'keyvault.ImportCertificate'")
+ }
+
+ return &uploader.UploadResult{
+ CertId: string(*importCertificateResp.ID),
+ CertName: certName,
+ }, nil
+}
+
+func createSdkClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) {
+ env, err := azcommon.GetCloudEnvironmentConfiguration(cloudName)
+ if err != nil {
+ return nil, err
+ }
+ clientOptions := azcore.ClientOptions{Cloud: env}
+
+ credential, err := azidentity.NewClientSecretCredential(tenantId, clientId, clientSecret,
+ &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
+ if err != nil {
+ return nil, err
+ }
+
+ endpoint := fmt.Sprintf("https://%s.vault.azure.net", keyvaultName)
+ if azcommon.IsEnvironmentGovernment(cloudName) {
+ endpoint = fmt.Sprintf("https://%s.vault.usgovcloudapi.net", keyvaultName)
+ } else if azcommon.IsEnvironmentChina(cloudName) {
+ endpoint = fmt.Sprintf("https://%s.vault.azure.cn", keyvaultName)
+ }
+
+ client, err := azcertificates.NewClient(endpoint, credential, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return client, nil
+}
diff --git a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go
index afbbf3e1..6518bf41 100644
--- a/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go
+++ b/internal/pkg/core/uploader/providers/jdcloud-ssl/jdcloud_ssl.go
@@ -76,31 +76,31 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
}
for _, certDetail := range describeCertsResp.Result.CertListDetails {
- // 先尝试匹配 CN
+ // 先对比证书通用名称
if !strings.EqualFold(certX509.Subject.CommonName, certDetail.CommonName) {
continue
}
- // 再尝试匹配 SAN
+ // 再对比证书多域名
if !slices.Equal(certX509.DNSNames, certDetail.DnsNames) {
continue
}
- // 再尝试匹配证书有效期
+ // 再对比证书有效期
oldCertNotBefore, _ := time.Parse(time.RFC3339, certDetail.StartTime)
oldCertNotAfter, _ := time.Parse(time.RFC3339, certDetail.EndTime)
if !certX509.NotBefore.Equal(oldCertNotBefore) || !certX509.NotAfter.Equal(oldCertNotAfter) {
continue
}
- // 最后尝试匹配私钥摘要
+ // 最后对比私钥摘要
newKeyDigest := sha256.Sum256([]byte(privkeyPem))
newKeyDigestHex := hex.EncodeToString(newKeyDigest[:])
if !strings.EqualFold(newKeyDigestHex, certDetail.Digest) {
continue
}
- // 如果以上都匹配,则视为已存在相同证书,直接返回已有的证书信息
+ // 如果以上信息都一致,则视为已存在相同证书,直接返回
return &uploader.UploadResult{
CertId: certDetail.CertId,
CertName: certDetail.CertName,
diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
index aaa03999..e5d2fc1c 100644
--- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
+++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
@@ -121,8 +121,8 @@ func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (re
if getCertificateListResp.CertificateList != nil {
for _, certInfo := range getCertificateListResp.CertificateList {
- // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试匹配来判断是否为同一证书
- // 先分别匹配证书的域名、品牌、有效期,再匹配签名算法
+ // 优刻得未提供可唯一标识证书的字段,只能通过多个字段尝试对比来判断是否为同一证书
+ // 先分别对比证书的多域名、品牌、有效期,再对比签名算法
if len(certX509.DNSNames) == 0 || certInfo.Domains != strings.Join(certX509.DNSNames, ",") {
continue
diff --git a/internal/pkg/utils/types/types.go b/internal/pkg/utils/types/types.go
index cd2b1602..f23d8e33 100644
--- a/internal/pkg/utils/types/types.go
+++ b/internal/pkg/utils/types/types.go
@@ -24,3 +24,29 @@ func IsNil(obj any) bool {
return false
}
+
+// 将对象转换为指针。
+//
+// 入参:
+// - 待转换的对象。
+//
+// 出参:
+// - 返回对象的指针。
+func ToPtr[T any](v T) (p *T) {
+ return &v
+}
+
+// 将指针转换为对象。
+//
+// 入参:
+// - 待转换的指针。
+//
+// 出参:
+// - 返回指针指向的对象。如果指针为空,则返回对象的零值。
+func ToObj[T any](p *T) (v T) {
+ if p == nil {
+ return v
+ }
+
+ return *p
+}
diff --git a/internal/pkg/vendors/azure-sdk/common/config.go b/internal/pkg/vendors/azure-sdk/common/config.go
new file mode 100644
index 00000000..eca082f9
--- /dev/null
+++ b/internal/pkg/vendors/azure-sdk/common/config.go
@@ -0,0 +1,47 @@
+package common
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
+)
+
+func IsEnvironmentPublic(env string) bool {
+ switch strings.ToLower(env) {
+ case "", "default", "public", "azurecloud":
+ return true
+ default:
+ return false
+ }
+}
+
+func IsEnvironmentGovernment(env string) bool {
+ switch strings.ToLower(env) {
+ case "usgovernment", "government", "azureusgovernment", "azuregovernment":
+ return true
+ default:
+ return false
+ }
+}
+
+func IsEnvironmentChina(env string) bool {
+ switch strings.ToLower(env) {
+ case "china", "chinacloud", "azurechina", "azurechinacloud":
+ return true
+ default:
+ return false
+ }
+}
+
+func GetCloudEnvironmentConfiguration(env string) (cloud.Configuration, error) {
+ if IsEnvironmentPublic(env) {
+ return cloud.AzurePublic, nil
+ } else if IsEnvironmentGovernment(env) {
+ return cloud.AzureGovernment, nil
+ } else if IsEnvironmentChina(env) {
+ return cloud.AzureChina, nil
+ }
+
+ return cloud.Configuration{}, fmt.Errorf("unknown azure cloud environment %s", env)
+}
diff --git a/internal/pkg/vendors/btpanel-sdk/client.go b/internal/pkg/vendors/btpanel-sdk/client.go
index 67e9fdb1..54564f0e 100644
--- a/internal/pkg/vendors/btpanel-sdk/client.go
+++ b/internal/pkg/vendors/btpanel-sdk/client.go
@@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
+ "reflect"
"strings"
"time"
@@ -45,24 +46,36 @@ func (c *Client) generateSignature(timestamp string) string {
func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) {
timestamp := time.Now().Unix()
- data := make(map[string]any)
+ data := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
json.Unmarshal(jsonb, &temp)
for k, v := range temp {
if v != nil {
- data[k] = v
+ switch reflect.Indirect(reflect.ValueOf(v)).Kind() {
+ case reflect.String:
+ data[k] = v.(string)
+ case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
+ data[k] = fmt.Sprintf("%v", v)
+ default:
+ if t, ok := v.(time.Time); ok {
+ data[k] = t.Format(time.RFC3339)
+ } else {
+ jbytes, _ := json.Marshal(v)
+ data[k] = string(jbytes)
+ }
+ }
}
}
}
- data["request_time"] = timestamp
+ data["request_time"] = fmt.Sprintf("%d", timestamp)
data["request_token"] = c.generateSignature(fmt.Sprintf("%d", timestamp))
url := c.apiHost + path
req := c.client.R().
- SetHeader("Content-Type", "application/json").
- SetBody(data)
+ SetHeader("Content-Type", "application/x-www-form-urlencoded").
+ SetFormData(data)
resp, err := req.Post(url)
if err != nil {
return nil, fmt.Errorf("baota api error: failed to send request: %w", err)
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index dc468b92..ff6115a9 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -67,12 +67,12 @@
"access.form.baiducloud_secret_access_key.tooltip": "For more information, see https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en",
"access.form.baishan_api_token.label": "Baishan Cloud API token",
"access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token",
- "access.form.baotapanel_api_url.label": "BaoTa Panel URL",
- "access.form.baotapanel_api_url.placeholder": "Please enter BaoTa Panel URL",
+ "access.form.baotapanel_api_url.label": "aaPanel URL",
+ "access.form.baotapanel_api_url.placeholder": "Please enter aaPanel URL",
"access.form.baotapanel_api_url.tooltip": "For more information, see https://www.bt.cn/bbs/thread-20376-1-1.html",
- "access.form.baotapanel_api_key.label": "BaoTa Panel API key",
- "access.form.baotapanel_api_key.placeholder": "Please enter BaoTa Panel API key",
- "access.form.baotapanel_api_key.tooltip": "For more information, see https://www.bt.cn/bbs/thread-113890-1-1.html",
+ "access.form.baotapanel_api_key.label": "aaPanel API key",
+ "access.form.baotapanel_api_key.placeholder": "Please enter aaPanel API key",
+ "access.form.baotapanel_api_key.tooltip": "For more information, see https://www.bt.cn/bbs/thread-20376-1-1.html",
"access.form.byteplus_access_key.label": "BytePlus AccessKey",
"access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey",
"access.form.byteplus_access_key.tooltip": "For more information, see https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index e924b43a..9ce0bc5c 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -26,9 +26,9 @@
"provider.baiducloud.dns": "Baidu Cloud - DNS (Domain Name Service)",
"provider.baishan": "Baishan",
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
- "provider.baotapanel": "BaoTa Panel",
- "provider.baotapanel.console": "BaoTa Panel - Console",
- "provider.baotapanel.site": "BaoTa Panel - Site",
+ "provider.baotapanel": "aaPanel (aka BaoTaPanel)",
+ "provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console",
+ "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Site",
"provider.byteplus": "BytePlus",
"provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)",
"provider.cachefly": "CacheFly",
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index ebf7cf3c..6a377deb 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -203,19 +203,19 @@
"workflow_node.deploy.form.baishan_cdn_domain.placeholder": "Please enter Baishan CDN domain name",
"workflow_node.deploy.form.baishan_cdn_domain.tooltip": "For more information, see https://cdnx.console.baishan.com",
"workflow_node.deploy.form.baotapanel_console_auto_restart.label": "Auto restart after deployment",
- "workflow_node.deploy.form.baotapanel_site_type.label": "BaoTa Panel site type",
- "workflow_node.deploy.form.baotapanel_site_type.placeholder": "Please select BaoTa Panel site type",
+ "workflow_node.deploy.form.baotapanel_site_type.label": "aaPanel site type",
+ "workflow_node.deploy.form.baotapanel_site_type.placeholder": "Please select aaPanel site type",
"workflow_node.deploy.form.baotapanel_site_type.option.php.label": "PHP sites",
"workflow_node.deploy.form.baotapanel_site_type.option.other.label": "Other sites",
- "workflow_node.deploy.form.baotapanel_site_name.label": "BaoTa Panel site name",
- "workflow_node.deploy.form.baotapanel_site_name.placeholder": "Please enter BaoTa Panel site name",
+ "workflow_node.deploy.form.baotapanel_site_name.label": "aaPanel site name",
+ "workflow_node.deploy.form.baotapanel_site_name.placeholder": "Please enter aaPanel site name",
"workflow_node.deploy.form.baotapanel_site_name.tooltip": "Usually equal to the website domain name.",
- "workflow_node.deploy.form.baotapanel_site_names.label": "BaoTa Panel site names",
- "workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter BaoTa Panel site names (separated by semicolons)",
- "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid BaoTa Panel site name",
+ "workflow_node.deploy.form.baotapanel_site_names.label": "aaPanel site names",
+ "workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter aaPanel site names (separated by semicolons)",
+ "workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid aaPanel site name",
"workflow_node.deploy.form.baotapanel_site_names.tooltip": "Usually equal to the websites domain name.",
- "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "Change BaoTa Panel site names",
- "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "Please enter BaoTa Panel site name",
+ "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "Change aaPanel site names",
+ "workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "Please enter aaPanel site name",
"workflow_node.deploy.form.byteplus_cdn_domain.label": "BytePlus CDN domain",
"workflow_node.deploy.form.byteplus_cdn_domain.placeholder": "Please enter BytePlus CDN domain name",
"workflow_node.deploy.form.byteplus_cdn_domain.tooltip": "For more information, see https://console.byteplus.com/cdn",