Fix Azure KeyVault bug

This commit is contained in:
fondoger 2025-04-16 21:53:19 +08:00
parent 88b90986b1
commit 364ceb2399
10 changed files with 155 additions and 14 deletions

View File

@ -297,11 +297,12 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
switch options.Provider { switch options.Provider {
case domain.DeployProviderTypeAzureKeyVault: case domain.DeployProviderTypeAzureKeyVault:
deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{ deployer, err := pAzureKeyVault.NewDeployer(&pAzureKeyVault.DeployerConfig{
TenantId: access.TenantId, TenantId: access.TenantId,
ClientId: access.ClientId, ClientId: access.ClientId,
ClientSecret: access.ClientSecret, ClientSecret: access.ClientSecret,
CloudName: access.CloudName, CloudName: access.CloudName,
KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"), KeyVaultName: maputil.GetString(options.ProviderDeployConfig, "keyvaultName"),
CertificateName: maputil.GetString(options.ProviderDeployConfig, "certificateName"),
}) })
return deployer, err return deployer, err

View File

@ -22,6 +22,8 @@ type DeployerConfig struct {
CloudName string `json:"cloudName,omitempty"` CloudName string `json:"cloudName,omitempty"`
// Key Vault 名称。 // Key Vault 名称。
KeyVaultName string `json:"keyvaultName"` KeyVaultName string `json:"keyvaultName"`
// Certificate 名称。
CertificateName string `json:"certificateName"`
} }
type DeployerProvider struct { type DeployerProvider struct {
@ -38,11 +40,12 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
} }
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
TenantId: config.TenantId, TenantId: config.TenantId,
ClientId: config.ClientId, ClientId: config.ClientId,
ClientSecret: config.ClientSecret, ClientSecret: config.ClientSecret,
CloudName: config.CloudName, CloudName: config.CloudName,
KeyVaultName: config.KeyVaultName, KeyVaultName: config.KeyVaultName,
CertificateName: config.CertificateName,
}) })
if err != nil { if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader") return nil, xerrors.Wrap(err, "failed to create ssl uploader")

View File

@ -3,6 +3,7 @@
import ( import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/base64"
"fmt" "fmt"
"log/slog" "log/slog"
"time" "time"
@ -29,6 +30,8 @@ type UploaderConfig struct {
CloudName string `json:"cloudName,omitempty"` CloudName string `json:"cloudName,omitempty"`
// Key Vault 名称。 // Key Vault 名称。
KeyVaultName string `json:"keyvaultName"` KeyVaultName string `json:"keyvaultName"`
// Certificate 名称。
CertificateName string `json:"certificateName,omitempty"`
} }
type UploaderProvider struct { type UploaderProvider struct {
@ -88,6 +91,11 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
for _, certItem := range page.Value { for _, certItem := range page.Value {
// 如果已经指定了证书名称,则跳过证书名称不匹配的证书
if u.config.CertificateName != "" && certItem.ID.Name() != u.config.CertificateName {
continue
}
// 先对比证书有效期 // 先对比证书有效期
if certItem.Attributes == nil { if certItem.Attributes == nil {
continue continue
@ -138,16 +146,28 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
} }
} }
// 生成新证书名(需符合 Azure 命名规则) certName := u.config.CertificateName
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) if certName == "" {
// 未指定证书名称时生成包含timestamp的新证书名需符合 Azure 命名规则)
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
}
// Azure Key Vault 不支持导入带有Certificiate Chain的PEM证书。
// Issue Link: https://github.com/Azure/azure-cli/issues/19017
// 暂时的解决方法是,将 PEM 证书转换成 PFX 格式,然后再导入。
pfxCert, err := certutil.TransformCertificateFromPEMToPFX(certPem, privkeyPem, "")
if err != nil {
u.logger.Error("failed to transform certificate from PEM to PFX", slog.String("certPem", certPem), slog.String("privkeyPem", privkeyPem))
return nil, xerrors.Wrap(err, "failed to transform certificate from PEM to PFX")
}
// 导入证书 // 导入证书
// REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate // REF: https://learn.microsoft.com/en-us/rest/api/keyvault/certificates/import-certificate/import-certificate
importCertificateParams := azcertificates.ImportCertificateParameters{ importCertificateParams := azcertificates.ImportCertificateParameters{
Base64EncodedCertificate: to.Ptr(certPem), Base64EncodedCertificate: to.Ptr(base64.StdEncoding.EncodeToString(pfxCert)),
CertificatePolicy: &azcertificates.CertificatePolicy{ CertificatePolicy: &azcertificates.CertificatePolicy{
SecretProperties: &azcertificates.SecretProperties{ SecretProperties: &azcertificates.SecretProperties{
ContentType: to.Ptr("application/x-pem-file"), ContentType: to.Ptr("application/x-pkcs12"),
}, },
}, },
Tags: map[string]*string{ Tags: map[string]*string{

View File

@ -0,0 +1,87 @@
package azurekeyvault_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/azure-keyvault"
)
var (
fInputCertPath string
fInputKeyPath string
fTenantId string
fAccessKeyId string
fSecretAccessKey string
fKeyVaultName string
fCertificateName string
)
func init() {
argsPrefix := "CERTIMATE_UPLOADER_AZUREKEYVAULT_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fTenantId, argsPrefix+"TENANTID", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
flag.StringVar(&fKeyVaultName, argsPrefix+"KEYVAULTNAME", "", "")
flag.StringVar(&fCertificateName, argsPrefix+"CERTIFICATENAME", "", "")
}
/*
Shell command to run this test:
go test -v ./azure_keyvault_test.go -args \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_TENANTID="your-tenant-id" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_ACCESSKEYID="your-app-registration-client-id" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_SECRETACCESSKEY="your-app-registration-client-secret" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_KEYVAULTNAME="your-keyvault-name" \
--CERTIMATE_UPLOADER_AZUREKEYVAULT_CERTIFICATENAME="your-certificate-name"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("TENANTID: %v", fTenantId),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
fmt.Sprintf("KEYVAULTNAME: %v", fKeyVaultName),
fmt.Sprintf("CERTIFICATENAME: %v", fCertificateName),
}, "\n"))
uploader, err := provider.NewUploader(&provider.UploaderConfig{
TenantId: fTenantId,
ClientId: fAccessKeyId,
ClientSecret: fSecretAccessKey,
KeyVaultName: fKeyVaultName,
CertificateName: fCertificateName,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
sres, _ := json.Marshal(res)
t.Logf("ok: %s", string(sres))
})
}

View File

@ -2,9 +2,11 @@ import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd"; import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import { validAzureKeyVaultCertificateName } from "@/utils/validators";
type DeployNodeConfigFormAzureKeyVaultConfigFieldValues = Nullish<{ type DeployNodeConfigFormAzureKeyVaultConfigFieldValues = Nullish<{
keyvaultName: string; keyvaultName: string;
certificateName?: string;
}>; }>;
export type DeployNodeConfigFormAzureKeyVaultConfigProps = { export type DeployNodeConfigFormAzureKeyVaultConfigProps = {
@ -33,6 +35,13 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
.string({ message: t("workflow_node.deploy.form.azure_keyvault_name.placeholder") }) .string({ message: t("workflow_node.deploy.form.azure_keyvault_name.placeholder") })
.nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")) .nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder"))
.trim(), .trim(),
certificateName: z
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
.nullish()
.refine((v) =>{
if (!v) return true;
return validAzureKeyVaultCertificateName(v);
}, t("common.errmsg.azure_keyvault_certificate_name_invalid")),
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
@ -57,6 +66,14 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
> >
<Input placeholder={t("workflow_node.deploy.form.azure_keyvault_name.placeholder")} /> <Input placeholder={t("workflow_node.deploy.form.azure_keyvault_name.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item
name="certificateName"
label={t("workflow_node.deploy.form.azure_keyvault_certificate_name.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder")} />
</Form.Item>
</Form> </Form>
); );
}; };

View File

@ -35,6 +35,7 @@
"common.errmsg.port_invalid": "Please enter a valid port", "common.errmsg.port_invalid": "Please enter a valid port",
"common.errmsg.ip_invalid": "Please enter a valid IP address", "common.errmsg.ip_invalid": "Please enter a valid IP address",
"common.errmsg.url_invalid": "Please enter a valid URL", "common.errmsg.url_invalid": "Please enter a valid URL",
"common.errmsg.azure_keyvault_certificate_name_invalid": "Certificate name can only contain letters, numbers, and hyphens (-), with a length limit of 1 to 127 characters",
"common.notifier.bark": "Bark", "common.notifier.bark": "Bark",
"common.notifier.dingtalk": "DingTalk", "common.notifier.dingtalk": "DingTalk",

View File

@ -234,6 +234,9 @@
"workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault name", "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault name",
"workflow_node.deploy.form.azure_keyvault_name.placeholder": "Please enter Azure KeyVault name", "workflow_node.deploy.form.azure_keyvault_name.placeholder": "Please enter Azure KeyVault name",
"workflow_node.deploy.form.azure_keyvault_name.tooltip": "For more information, see <a href=\"https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates\" target=\"_blank\">https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates</a>", "workflow_node.deploy.form.azure_keyvault_name.tooltip": "For more information, see <a href=\"https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates\" target=\"_blank\">https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates</a>",
"workflow_node.deploy.form.azure_keyvault_certificate_name.label": "Azure KeyVault certificate name (Optional)",
"workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder": "Please enter Azure KeyVault certificate name",
"workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip": "If not filled in, a default name with a timestamp will be automatically generated.",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "Resource type", "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "Resource type",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "Please select resource type",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "BLB load balancer", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "BLB load balancer",

View File

@ -35,6 +35,7 @@
"common.errmsg.port_invalid": "请输入正确的端口号", "common.errmsg.port_invalid": "请输入正确的端口号",
"common.errmsg.ip_invalid": "请输入正确的 IP 地址", "common.errmsg.ip_invalid": "请输入正确的 IP 地址",
"common.errmsg.url_invalid": "请输入正确的 URL 地址", "common.errmsg.url_invalid": "请输入正确的 URL 地址",
"common.errmsg.azure_keyvault_certificate_name_invalid": "证书名称只能包含字母、数字和连字符(-),长度限制为 1 到 127 个字符",
"common.notifier.bark": "Bark", "common.notifier.bark": "Bark",
"common.notifier.dingtalk": "钉钉", "common.notifier.dingtalk": "钉钉",

View File

@ -233,6 +233,9 @@
"workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault 名称", "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault 名称",
"workflow_node.deploy.form.azure_keyvault_name.placeholder": "请输入 Azure KeyVault 名称", "workflow_node.deploy.form.azure_keyvault_name.placeholder": "请输入 Azure KeyVault 名称",
"workflow_node.deploy.form.azure_keyvault_name.tooltip": "这是什么?请参阅 <a href=\"https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates\" target=\"_blank\">https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates</a>", "workflow_node.deploy.form.azure_keyvault_name.tooltip": "这是什么?请参阅 <a href=\"https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates\" target=\"_blank\">https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates</a>",
"workflow_node.deploy.form.azure_keyvault_certificate_name.label": "Azure KeyVault 证书名称 (可选)",
"workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder": "请输入 Azure KeyVault 证书名称",
"workflow_node.deploy.form.azure_keyvault_certificate_name.tooltip": "不填写时,会自动生成带时间戳的默认名称。",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.baiducloud_appblb_resource_type.label": "证书替换方式",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder": "请选择证书替换方式",
"workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书", "workflow_node.deploy.form.baiducloud_appblb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/SSL 监听的证书",

View File

@ -9,6 +9,11 @@ export const validDomainName = (value: string, { allowWildcard = false }: { allo
return re.test(value); return re.test(value);
}; };
export const validAzureKeyVaultCertificateName = (value: string) => {
const re = /^[a-zA-Z0-9-]{1,127}$/;
return re.test(value);
}
export const validEmailAddress = (value: string) => { export const validEmailAddress = (value: string) => {
const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return re.test(value); return re.test(value);