From 58b3180043124caea06f9da144e3f4f9e83e1d8d Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 22 Dec 2025 13:42:11 +0800 Subject: [PATCH] feat: support configuring 'subscriptionId' and 'resourceGroupName' for azure credentials --- internal/certacme/certifiers/sp_azure_dns.go | 2 + internal/domain/access.go | 10 +++-- .../challengers/dns01/azure-dns/azure-dns.go | 4 ++ .../azure-keyvault/azure_keyvault.go | 27 +++++++------ .../azure-keyvault/azure_keyvault.go | 40 ------------------- .../forms/AccessConfigFieldsProviderAzure.tsx | 22 ++++++++++ ui/src/i18n/locales/en/nls.access.json | 6 +++ ui/src/i18n/locales/zh/nls.access.json | 6 +++ 8 files changed, 61 insertions(+), 56 deletions(-) diff --git a/internal/certacme/certifiers/sp_azure_dns.go b/internal/certacme/certifiers/sp_azure_dns.go index 4e291897c..3df51bd5b 100644 --- a/internal/certacme/certifiers/sp_azure_dns.go +++ b/internal/certacme/certifiers/sp_azure_dns.go @@ -21,6 +21,8 @@ func init() { TenantId: credentials.TenantId, ClientId: credentials.ClientId, ClientSecret: credentials.ClientSecret, + SubscriptionId: credentials.SubscriptionId, + ResourceGroupName: credentials.ResourceGroupName, CloudName: credentials.CloudName, DnsPropagationTimeout: options.DnsPropagationTimeout, DnsTTL: options.DnsTTL, diff --git a/internal/domain/access.go b/internal/domain/access.go index 7cc5290f8..e7fb438e6 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -87,10 +87,12 @@ type AccessConfigForAWS struct { } type AccessConfigForAzure struct { - TenantId string `json:"tenantId"` - ClientId string `json:"clientId"` - ClientSecret string `json:"clientSecret"` - CloudName string `json:"cloudName,omitempty"` + TenantId string `json:"tenantId"` + ClientId string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + SubscriptionId string `json:"subscriptionId,omitempty"` + ResourceGroupName string `json:"resourceGroupName,omitempty"` + CloudName string `json:"cloudName,omitempty"` } type AccessConfigForBaiduCloud struct { diff --git a/pkg/core/certifier/challengers/dns01/azure-dns/azure-dns.go b/pkg/core/certifier/challengers/dns01/azure-dns/azure-dns.go index e71a4ba7d..b3ccb2def 100644 --- a/pkg/core/certifier/challengers/dns01/azure-dns/azure-dns.go +++ b/pkg/core/certifier/challengers/dns01/azure-dns/azure-dns.go @@ -14,6 +14,8 @@ type ChallengerConfig struct { TenantId string `json:"tenantId"` ClientId string `json:"clientId"` ClientSecret string `json:"clientSecret"` + SubscriptionId string `json:"subscriptionId,omitempty"` + ResourceGroupName string `json:"resourceGroupName,omitempty"` CloudName string `json:"cloudName,omitempty"` DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"` DnsTTL int `json:"dnsTTL,omitempty"` @@ -29,6 +31,8 @@ func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) { providerConfig.TenantID = config.TenantId providerConfig.ClientID = config.ClientId providerConfig.ClientSecret = config.ClientSecret + providerConfig.SubscriptionID = config.SubscriptionId + providerConfig.ResourceGroup = config.ResourceGroupName if config.CloudName != "" { env, err := azenv.GetCloudEnvConfiguration(config.CloudName) if err != nil { diff --git a/pkg/core/certmgr/providers/azure-keyvault/azure_keyvault.go b/pkg/core/certmgr/providers/azure-keyvault/azure_keyvault.go index 66632d291..12b2f494b 100644 --- a/pkg/core/certmgr/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/certmgr/providers/azure-keyvault/azure_keyvault.go @@ -44,7 +44,7 @@ func NewCertmgr(config *CertmgrConfig) (*Certmgr, error) { return nil, errors.New("the configuration of the certmgr provider is nil") } - client, err := createSDKClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName) + client, err := createSDKClient(config.CloudName, config.TenantId, config.ClientId, config.ClientSecret, config.KeyVaultName) if err != nil { return nil, fmt.Errorf("could not create client: %w", err) } @@ -72,14 +72,12 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*cert } // 生成 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 := c.sdkClient.NewListCertificatePropertiesPager(nil) + listCertificatesPager := c.sdkClient.NewListCertificatePropertiesPager(&azcertificates.ListCertificatePropertiesOptions{}) for listCertificatesPager.More() { page, err := listCertificatesPager.NextPage(ctx) if err != nil { @@ -99,14 +97,14 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*cert } // 对比 Tag 中的通用名称 - if v, ok := certItem.Tags[TAG_CERTCN]; !ok || v == nil { + if v, ok := certItem.Tags[kvTagCertCN]; !ok || v == nil { continue } else if *v != certCN { continue } // 对比 Tag 中的序列号 - if v, ok := certItem.Tags[TAG_CERTSN]; !ok || v == nil { + if v, ok := certItem.Tags[kvTagCertSN]; !ok || v == nil { continue } else if *v != certSN { continue @@ -153,8 +151,8 @@ func (c *Certmgr) Upload(ctx context.Context, certPEM, privkeyPEM string) (*cert }, }, Tags: map[string]*string{ - TAG_CERTCN: to.Ptr(certCN), - TAG_CERTSN: to.Ptr(certSN), + kvTagCertCN: to.Ptr(certCN), + kvTagCertSN: to.Ptr(certSN), }, } importCertificateResp, err := c.sdkClient.ImportCertificate(ctx, certName, importCertificateParams, nil) @@ -208,8 +206,8 @@ func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, pri }, }, Tags: map[string]*string{ - "certimate/cert-cn": to.Ptr(certX509.Subject.CommonName), - "certimate/cert-sn": to.Ptr(certX509.SerialNumber.Text(16)), + kvTagCertCN: to.Ptr(certX509.Subject.CommonName), + kvTagCertSN: to.Ptr(certX509.SerialNumber.Text(16)), }, } importCertificateResp, err := c.sdkClient.ImportCertificate(ctx, certIdOrName, importCertificateParams, nil) @@ -221,13 +219,18 @@ func (c *Certmgr) Replace(ctx context.Context, certIdOrName string, certPEM, pri return &certmgr.OperateResult{}, nil } -func createSDKClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) { +const ( + kvTagCertCN = "certimate/cert-cn" + kvTagCertSN = "certimate/cert-sn" +) + +func createSDKClient(cloudName, tenantId, clientId, clientSecret, keyvaultName string) (*azcertificates.Client, error) { env, err := azenv.GetCloudEnvConfiguration(cloudName) if err != nil { return nil, err } - clientOptions := azcore.ClientOptions{Cloud: env} + clientOptions := azcore.ClientOptions{Cloud: env} credential, err := azidentity.NewClientSecretCredential(tenantId, clientId, clientSecret, &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions}) if err != nil { diff --git a/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go b/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go index 5c641a422..6e14c3efd 100644 --- a/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go +++ b/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go @@ -6,14 +6,9 @@ import ( "fmt" "log/slog" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" - "github.com/certimate-go/certimate/pkg/core/certmgr" mcertmgr "github.com/certimate-go/certimate/pkg/core/certmgr/providers/azure-keyvault" "github.com/certimate-go/certimate/pkg/core/deployer" - azenv "github.com/certimate-go/certimate/pkg/sdk3rd/azure/env" ) type DeployerConfig struct { @@ -35,7 +30,6 @@ type DeployerConfig struct { type Deployer struct { config *DeployerConfig logger *slog.Logger - sdkClient *azcertificates.Client sdkCertmgr certmgr.Provider } @@ -46,11 +40,6 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) { return nil, errors.New("the configuration of the deployer provider is nil") } - client, err := createSDKClient(config.TenantId, config.ClientId, config.ClientSecret, config.CloudName, config.KeyVaultName) - if err != nil { - return nil, fmt.Errorf("could not create client: %w", err) - } - pcertmgr, err := mcertmgr.NewCertmgr(&mcertmgr.CertmgrConfig{ TenantId: config.TenantId, ClientId: config.ClientId, @@ -65,7 +54,6 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) { return &Deployer{ config: config, logger: slog.Default(), - sdkClient: client, sdkCertmgr: pcertmgr, }, nil } @@ -101,31 +89,3 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep return &deployer.DeployResult{}, nil } - -func createSDKClient(tenantId, clientId, clientSecret, cloudName, keyvaultName string) (*azcertificates.Client, error) { - env, err := azenv.GetCloudEnvConfiguration(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 azenv.IsUSGovernmentEnv(cloudName) { - endpoint = fmt.Sprintf("https://%s.vault.usgovcloudapi.net", keyvaultName) - } else if azenv.IsChinaEnv(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/ui/src/components/access/forms/AccessConfigFieldsProviderAzure.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderAzure.tsx index 6ff35f8c3..e248bc534 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderAzure.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderAzure.tsx @@ -47,6 +47,26 @@ const AccessConfigFormFieldsProviderAzure = () => { + } + > + + + + } + > + + + }) = tenantId: z.string().nonempty(t("access.form.azure_tenant_id.placeholder")), clientId: z.string().nonempty(t("access.form.azure_client_id.placeholder")), clientSecret: z.string().nonempty(t("access.form.azure_client_secret.placeholder")), + subscriptionId: z.string().nullish(), + resourceGroupName: z.string().nullish(), cloudName: z.string().nullish(), }); }; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 89ff558dc..8ce6d2cdd 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -136,6 +136,12 @@ "access.form.azure_client_secret.label": "Azure client secret", "access.form.azure_client_secret.placeholder": "Please enter Azure client secret", "access.form.azure_client_secret.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/azure-monitor/logs/api/register-app-for-token", + "access.form.azure_subscription_id.label": "Azure subscription ID (Optional)", + "access.form.azure_subscription_id.placeholder": "Please enter Azure subscription ID", + "access.form.azure_subscription_id.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id", + "access.form.azure_resource_group_name.label": "Azure resource group name (Optional)", + "access.form.azure_resource_group_name.placeholder": "Please enter Azure resource group name", + "access.form.azure_resource_group_name.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal", "access.form.azure_cloud_name.label": "Azure sovereign cloud name (Optional)", "access.form.azure_cloud_name.placeholder": "Please enter Azure sovereign cloud name (e.g. public)", "access.form.azure_cloud_name.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/sovereign-clouds", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index a67b0f1c3..5626e749d 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -136,6 +136,12 @@ "access.form.azure_client_secret.label": "Azure 客户端密码", "access.form.azure_client_secret.placeholder": "请输入 Azure 客户端密码", "access.form.azure_client_secret.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-monitor/logs/api/register-app-for-token", + "access.form.azure_subscription_id.label": "Azure 订阅 ID(可选)", + "access.form.azure_subscription_id.placeholder": "请输入 Azure 订阅 ID", + "access.form.azure_subscription_id.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/azure-portal/get-subscription-tenant-id", + "access.form.azure_resource_group_name.label": "Azure 资源组名称(可选)", + "access.form.azure_resource_group_name.placeholder": "请输入 Azure 资源组名称", + "access.form.azure_resource_group_name.tooltip": "这是什么?请参阅 https://docs.azure.cn/zh-cn/azure-resource-manager/management/manage-resource-groups-portal", "access.form.azure_cloud_name.label": "Azure 主权云环境(可选)", "access.form.azure_cloud_name.placeholder": "请输入 Azure 主权云环境(例如:public)", "access.form.azure_cloud_name.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/developer/azure-developer-cli/sovereign-clouds",