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",