feat(provider): new purge provider: huaweicloud waf

This commit is contained in:
Fu Diwei
2026-08-27 17:14:31 +08:00
parent 4ded078aaf
commit c6e48816d0
11 changed files with 349 additions and 0 deletions
@@ -0,0 +1,27 @@
package purgers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
pgrimpl "github.com/certimate-go/certimate/pkg/core/purger/providers/huaweicloud-waf"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.PurgeProviderTypeHuaweiCloudWAF, func(options *ProviderFactoryOptions) (core.Purger, error) {
credentials := domain.AccessConfigForHuaweiCloud{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := pgrimpl.NewPurger(&pgrimpl.PurgerConfig{
AccessKeyId: credentials.AccessKeyId,
SecretAccessKey: credentials.SecretAccessKey,
EnterpriseProjectId: credentials.EnterpriseProjectId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
})
return provider, err
})
}
+1
View File
@@ -497,6 +497,7 @@ const (
PurgeProviderTypeAliyunESA = PurgeProviderType(AccessProviderTypeAliyun + "-esa")
PurgeProviderTypeHuaweiCloudELB = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-elb")
PurgeProviderTypeHuaweiCloudSCM = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-scm")
PurgeProviderTypeHuaweiCloudWAF = PurgeProviderType(AccessProviderTypeHuaweiCloud + "-waf")
PurgeProviderTypeTencentCloudGAAP = PurgeProviderType(AccessProviderTypeTencentCloud + "-gaap")
PurgeProviderTypeTencentCloudSSL = PurgeProviderType(AccessProviderTypeTencentCloud + "-ssl")
PurgeProviderTypeTencentCloudTSE = PurgeProviderType(AccessProviderTypeTencentCloud + "-tse")
@@ -0,0 +1,48 @@
//go:build tester
package huaweicloudwaf_test
import (
"testing"
"github.com/stretchr/testify/require"
tester "github.com/certimate-go/certimate/pkg/core/purger/providers-tester"
impl "github.com/certimate-go/certimate/pkg/core/purger/providers/huaweicloud-waf"
)
var (
fp = tester.InitArgs("HUAWEICLOUDWAF_")
fAccessKeyId string
fSecretAccessKey string
fRegion string
)
func init() {
fp.DefineString(&fAccessKeyId, "ACCESSKEYID")
fp.DefineString(&fSecretAccessKey, "SECRETACCESSKEY")
fp.DefineString(&fRegion, "REGION")
}
/*
Shell command to run this test:
go test -tags=tester -v ./huaweicloud_waf_test.go -args \
--HUAWEICLOUDWAF_ACCESSKEYID="your-access-key-id" \
--HUAWEICLOUDWAF_SECRETACCESSKEY="your-access-key-secret" \
--HUAWEICLOUDWAF_REGION="cn-north-4"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Purge", func(t *testing.T) {
provider, err := impl.NewPurger(&impl.PurgerConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Region: fRegion,
})
require.NoError(t, err)
tester.Purge(t, provider, tester.PurgeInput{})
})
}
@@ -0,0 +1,193 @@
package huaweicloudwaf
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
hwwafmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/model"
hwwafregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1/region"
"github.com/samber/lo"
hwwaf "github.com/certimate-go/certimate/pkg/sdk3rd-trimmed/github.com/huaweicloud/huaweicloud-sdk-go-v3/services/waf/v1"
"github.com/certimate-go/certimate/pkg/core"
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
xhuaweicloud "github.com/certimate-go/certimate/pkg/utils/third-party/huaweicloud"
xwait "github.com/certimate-go/certimate/pkg/utils/wait"
)
type (
Provider = core.Purger
PurgeResult = core.PurgerPurgeResult
)
type PurgerConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云企业项目 ID。
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
// 华为云区域。
Region string `json:"region"`
}
type Purger struct {
config *PurgerConfig
logger *slog.Logger
sdkClient *hwwaf.WafClient
}
var _ Provider = (*Purger)(nil)
func NewPurger(config *PurgerConfig) (*Purger, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the purger provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Purger{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (p *Purger) SetLogger(logger *slog.Logger) {
if logger == nil {
p.logger = slog.New(slog.DiscardHandler)
} else {
p.logger = logger
}
}
func (p *Purger) Purge(ctx context.Context, expiry time.Duration) (*PurgeResult, error) {
purgingCertIds := make([]string, 0)
// 查询证书列表
// REF: https://support.huaweicloud.com/api-waf/ListCertificates.html
listCertificatesPage := 1
listCertificatesPageSize := 100
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
listCertificatesReq := &hwwafmodel.ListCertificatesRequest{
EnterpriseProjectId: lo.EmptyableToPtr(p.config.EnterpriseProjectId),
ExpStatus: lo.ToPtr(hwwafmodel.GetListCertificatesRequestExpStatusEnum().E_1),
Page: lo.ToPtr(int32(listCertificatesPage)),
Pagesize: lo.ToPtr(int32(listCertificatesPageSize)),
}
listCertificatesResp, err := p.sdkClient.ListCertificates(listCertificatesReq)
p.logger.Debug("sdk request 'waf.ListCertificates'", slog.Any("request", listCertificatesReq), slog.Any("response", listCertificatesResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'waf.ListCertificates': %w", err)
}
for _, certItem := range *listCertificatesResp.Items {
certNotAfter := time.UnixMilli(lo.FromPtr(certItem.ExpireTime))
if time.Since(certNotAfter) >= expiry {
purgingCertIds = append(purgingCertIds, certItem.Id)
}
}
if len(*listCertificatesResp.Items) < listCertificatesPageSize {
break
}
listCertificatesPage++
}
// 删除证书
// REF: https://support.huaweicloud.com/api-waf/DeleteCertificate.html
purgedCount := 0
if len(purgingCertIds) > 0 {
p.logger.Info("found certificates to purge", slog.Any("certIds", purgingCertIds))
if err := xloop.ForRangeAllWithContext(ctx, purgingCertIds, func(ctx context.Context, certId string, i int) error {
if i > 0 {
if err := xwait.DelayWithContext(ctx, 1*time.Second); err != nil {
return err
}
}
deleteCertificateReq := &hwwafmodel.DeleteCertificateRequest{
EnterpriseProjectId: lo.EmptyableToPtr(p.config.EnterpriseProjectId),
CertificateId: certId,
}
deleteCertificateResp, err := p.sdkClient.DeleteCertificate(deleteCertificateReq)
p.logger.Debug("sdk request 'waf.DeleteCertificate'", slog.Any("request", deleteCertificateReq), slog.Any("response", deleteCertificateResp))
if err != nil {
if deleteCertificateResp != nil && deleteCertificateResp.HttpStatusCode == 404 {
return nil
}
return fmt.Errorf("failed to execute sdk request 'waf.DeleteCertificate': %w", err)
}
purgedCount++
return nil
}); err != nil {
return nil, err
}
}
return &PurgeResult{
Count: purgedCount,
}, nil
}
func createSDKClient(accessKeyId, secretAccessKey, region string) (*hwwaf.WafClient, error) {
projectId, err := getSDKProjectId(accessKeyId, secretAccessKey, region)
if err != nil {
return nil, err
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
WithProjectId(projectId).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hwwafregion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hwwaf.WafClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hwwaf.NewWafClient(hcClient)
return client, nil
}
func getSDKProjectId(accessKeyId, secretAccessKey, region string) (string, error) {
auth, err := global.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return "", err
}
return xhuaweicloud.GetKeystoneProjectIDWithRegion(auth, region)
}
@@ -28,6 +28,16 @@ func (c *WafClient) CreateCertificate(request *model.CreateCertificateRequest) (
}
}
func (c *WafClient) DeleteCertificate(request *model.DeleteCertificateRequest) (*model.DeleteCertificateResponse, error) {
requestDef := GenReqDefForDeleteCertificate()
if resp, err := c.HcClient.Sync(request, requestDef); err != nil {
return nil, err
} else {
return resp.(*model.DeleteCertificateResponse), nil
}
}
func (c *WafClient) ListCertificates(request *model.ListCertificatesRequest) (*model.ListCertificatesResponse, error) {
requestDef := GenReqDefForListCertificates()
@@ -9,6 +9,10 @@ func GenReqDefForCreateCertificate() *def.HttpRequestDef {
return waf.GenReqDefForCreateCertificate()
}
func GenReqDefForDeleteCertificate() *def.HttpRequestDef {
return waf.GenReqDefForDeleteCertificate()
}
func GenReqDefForListCertificates() *def.HttpRequestDef {
return waf.GenReqDefForListCertificates()
}
@@ -6,6 +6,7 @@ import BizPurgeNodeConfigFieldsProviderAliyunCAS from "./BizPurgeNodeConfigField
import BizPurgeNodeConfigFieldsProviderAliyunCLB from "./BizPurgeNodeConfigFieldsProviderAliyunCLB";
import BizPurgeNodeConfigFieldsProviderAliyunESA from "./BizPurgeNodeConfigFieldsProviderAliyunESA";
import BizPurgeNodeConfigFieldsProviderHuaweiCloudELB from "./BizPurgeNodeConfigFieldsProviderHuaweiCloudELB";
import BizPurgeNodeConfigFieldsProviderHuaweiCloudWAF from "./BizPurgeNodeConfigFieldsProviderHuaweiCloudWAF";
import BizPurgeNodeConfigFieldsProviderTencentCloudGAAP from "./BizPurgeNodeConfigFieldsProviderTencentCloudGAAP";
import BizPurgeNodeConfigFieldsProviderTencentCloudSSL from "./BizPurgeNodeConfigFieldsProviderTencentCloudSSL";
import BizPurgeNodeConfigFieldsProviderTencentCloudTSE from "./BizPurgeNodeConfigFieldsProviderTencentCloudTSE";
@@ -20,6 +21,7 @@ const providerComponentMap: Partial<Record<PurgeProviderType, React.ComponentTyp
[PURGE_PROVIDERS.ALIYUN_CLB]: BizPurgeNodeConfigFieldsProviderAliyunCLB,
[PURGE_PROVIDERS.ALIYUN_ESA]: BizPurgeNodeConfigFieldsProviderAliyunESA,
[PURGE_PROVIDERS.HUAWEICLOUD_ELB]: BizPurgeNodeConfigFieldsProviderHuaweiCloudELB,
[PURGE_PROVIDERS.HUAWEICLOUD_WAF]: BizPurgeNodeConfigFieldsProviderHuaweiCloudWAF,
[PURGE_PROVIDERS.TENCENTCLOUD_GAAP]: BizPurgeNodeConfigFieldsProviderTencentCloudGAAP,
[PURGE_PROVIDERS.TENCENTCLOUD_SSL]: BizPurgeNodeConfigFieldsProviderTencentCloudSSL,
[PURGE_PROVIDERS.TENCENTCLOUD_TSE]: BizPurgeNodeConfigFieldsProviderTencentCloudTSE,
@@ -0,0 +1,52 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const BizPurgeNodeConfigFieldsProviderHuaweiCloudWAF = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.purge.form.huaweicloud_waf_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.purge.form.huaweicloud_waf_region.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.purge.form.huaweicloud_waf_region.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
region: z.string().nonempty(),
});
};
const _default = Object.assign(BizPurgeNodeConfigFieldsProviderHuaweiCloudWAF, {
getInitialValues,
getSchema,
});
export default _default;
+2
View File
@@ -991,6 +991,7 @@ export const PURGE_PROVIDERS = Object.freeze(
ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`,
HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`,
HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`,
HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`,
TENCENTCLOUD_GAAP: `${ACCESS_PROVIDERS.TENCENTCLOUD}-gaap`,
TENCENTCLOUD_SSL: `${ACCESS_PROVIDERS.TENCENTCLOUD}-ssl`,
TENCENTCLOUD_TSE: `${ACCESS_PROVIDERS.TENCENTCLOUD}-tse`,
@@ -1017,6 +1018,7 @@ export const purgeProvidersMap: Map<PurgeProvider["type"] | string, PurgeProvide
[PURGE_PROVIDERS.TENCENTCLOUD_TSE, "provider.tencentcloud_tse"],
[PURGE_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud_scm"],
[PURGE_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud_elb"],
[PURGE_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud_waf"],
[PURGE_PROVIDERS.UCLOUD_USSL, "provider.ucloud_ussl"],
] satisfies Array<[PurgeProviderType, string, "builtin"] | [PurgeProviderType, string]>
).map(([type, name]) => [
@@ -3057,6 +3057,11 @@
"placeholder": "Please enter Huawei Cloud ELB region (e.g. cn-north-1)",
"tooltip": "For more information, see <a href=\"https://console-intl.huaweicloud.com/apiexplorer/#/endpoint/elb?locale=en-us\" target=\"_blank\">https://console-intl.huaweicloud.com/apiexplorer/#/endpoint/elb</a>"
},
"huaweicloud_waf_region": {
"label": "Huawei Cloud region",
"placeholder": "Please enter Huawei Cloud WAF region (e.g. cn-north-1)",
"tooltip": "For more information, see <a href=\"https://console-intl.huaweicloud.com/apiexplorer/#/endpoint/waf?locale=en-us\" target=\"_blank\">https://console-intl.huaweicloud.com/apiexplorer/#/endpoint/waf</a>"
},
"tencentcloud_gaap_endpoint": {
"label": "Tencent Cloud API endpoint (Optional)",
"placeholder": "Please enter Tencent Cloud GAAP API endpoint (e.g. gaap.intl.tencentcloudapi.com)",
@@ -3056,6 +3056,11 @@
"placeholder": "请输入华为云 ELB 服务区域(例如:cn-north-1)",
"tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/apiexplorer/#/endpoint/elb\" target=\"_blank\">https://console.huaweicloud.com/apiexplorer/#/endpoint/elb</a>"
},
"huaweicloud_waf_region": {
"label": "华为云服务区域",
"placeholder": "请输入华为云 WAF 服务区域(例如:cn-north-1)",
"tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/apiexplorer/#/endpoint/waf\" target=\"_blank\">https://console.huaweicloud.com/apiexplorer/#/endpoint/waf</a>"
},
"tencentcloud_gaap_endpoint": {
"label": "腾讯云接口端点(可选)",
"placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)",