feat(provider): new deployment provider: samwaf

This commit is contained in:
Fu Diwei
2026-04-17 11:46:05 +08:00
committed by RHQYZ
parent 2c1cf8e31e
commit 784a2f3fd0
23 changed files with 679 additions and 17 deletions
+28
View File
@@ -0,0 +1,28 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core/deployer"
"github.com/certimate-go/certimate/pkg/core/deployer/providers/samwaf"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeSamWAF, func(options *ProviderFactoryOptions) (deployer.Provider, error) {
credentials := domain.AccessConfigForSamWAF{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := samwaf.NewDeployer(&samwaf.DeployerConfig{
ServerUrl: credentials.ServerUrl,
ApiKey: credentials.ApiKey,
AllowInsecureConnections: credentials.AllowInsecureConnections,
ResourceType: xmaps.GetString(options.ProviderExtendedConfig, "resourceType"),
CertificateId: xmaps.GetString(options.ProviderExtendedConfig, "certificateId"),
})
return provider, err
})
}
+6
View File
@@ -487,6 +487,12 @@ type AccessConfigForSafeLine struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSamWAF struct {
ServerUrl string `json:"serverUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSlackBot struct {
BotToken string `json:"botToken"`
ChannelId string `json:"channelId,omitempty"`
+2
View File
@@ -100,6 +100,7 @@ const (
AccessProviderTypeRFC2136 = AccessProviderType("rfc2136")
AccessProviderTypeS3 = AccessProviderType("s3")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSamWAF = AccessProviderType("samwaf")
AccessProviderTypeSectigo = AccessProviderType("sectigo")
AccessProviderTypeSlackBot = AccessProviderType("slackbot")
AccessProviderTypeSpaceship = AccessProviderType("spaceship")
@@ -345,6 +346,7 @@ const (
DeploymentProviderTypeRatPanelConsole = DeploymentProviderType(AccessProviderTypeRatPanel + "-console")
DeploymentProviderTypeS3 = DeploymentProviderType(AccessProviderTypeS3)
DeploymentProviderTypeSafeLine = DeploymentProviderType(AccessProviderTypeSafeLine)
DeploymentProviderTypeSamWAF = DeploymentProviderType(AccessProviderTypeSamWAF)
DeploymentProviderTypeSSH = DeploymentProviderType(AccessProviderTypeSSH)
DeploymentProviderTypeSynologyDSM = DeploymentProviderType(AccessProviderTypeSynologyDSM)
DeploymentProviderTypeTencentCloudCDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn")
@@ -42,7 +42,7 @@ Shell command to run this test:
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Run("Deploy_ToCertificate", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
@@ -0,0 +1,6 @@
package samwaf
const (
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = "certificate"
)
@@ -0,0 +1,118 @@
package samwaf
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/pkg/core/deployer"
samwafsdk "github.com/certimate-go/certimate/pkg/sdk3rd/samwaf"
)
type DeployerConfig struct {
// SamWAF 服务地址。
ServerUrl string `json:"serverUrl"`
// SamWAF API Key。
ApiKey string `json:"apiKey"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 部署资源类型。
ResourceType string `json:"resourceType"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
CertificateId string `json:"certificateId,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *samwafsdk.Client
}
var _ deployer.Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, errors.New("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*deployer.DeployResult, error) {
// 根据部署资源类型决定部署方式``
switch d.config.ResourceType {
case RESOURCE_TYPE_CERTIFICATE:
if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType)
}
return &deployer.DeployResult{}, nil
}
func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM string) error {
if d.config.CertificateId == "" {
return errors.New("config `certificateId` is required")
}
// 获取 SSL 证书 详情
// REF: https://doc.samwaf.com/api/
sslConfigDetailResp, err := d.sdkClient.SslConfigDetailWithContext(ctx, d.config.CertificateId)
d.logger.Debug("sdk request 'samwaf.SslConfigDetail'", slog.Any("request.sslId", d.config.CertificateId), slog.Any("response", sslConfigDetailResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'samwaf.SslConfigDetail': %w", err)
} else if sslConfigDetailResp.Data == nil || sslConfigDetailResp.Data.Id == "" {
return fmt.Errorf("could not find ssl config: '%s'", d.config.CertificateId)
}
// 编辑 SSL 证书
// REF: https://doc.samwaf.com/api/
sslConfigEditReq := &samwafsdk.SslConfigEditRequest{
Id: d.config.CertificateId,
CertContent: certPEM,
KeyContent: privkeyPEM,
}
sslConfigEditResp, err := d.sdkClient.SslConfigEditWithContext(ctx, sslConfigEditReq)
d.logger.Debug("sdk request 'samwaf.SslConfigEdit'", slog.Any("request", sslConfigEditReq), slog.Any("response", sslConfigEditResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'samwaf.SslConfigEdit': %w", err)
}
return nil
}
func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*samwafsdk.Client, error) {
client, err := samwafsdk.NewClient(serverUrl, apiKey)
if err != nil {
return nil, err
}
if skipTlsVerify {
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}
@@ -0,0 +1,77 @@
package samwaf_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/certimate-go/certimate/pkg/core/deployer/providers/samwaf"
)
var (
fInputCertPath string
fInputKeyPath string
fServerUrl string
fApiKey string
fCertificateId string
)
func init() {
argsPrefix := "SAMWAF_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "")
}
/*
Shell command to run this test:
go test -v ./samwaf_test.go -args \
--SAMWAF_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--SAMWAF_INPUTKEYPATH="/path/to/your-input-key.pem" \
--SAMWAF_SERVERURL="http://127.0.0.1:26666" \
--SAMWAF_APIKEY="your-api-key" \
--SAMWAF_CERTIFICATEID="your-certificate-id"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy_ToCertificate", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("SERVERURL: %v", fServerUrl),
fmt.Sprintf("APIKEY: %v", fApiKey),
fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
}, "\n"))
provider, err := provider.NewDeployer(&provider.DeployerConfig{
ServerUrl: fServerUrl,
ApiKey: fApiKey,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_CERTIFICATE,
CertificateId: fCertificateId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := provider.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}
+37
View File
@@ -0,0 +1,37 @@
package samwaf
import (
"context"
"fmt"
"net/http"
)
type SslConfigDetailResponse struct {
sdkResponseBase
Data *SslConfig `json:"data,omitempty"`
}
func (c *Client) SslConfigDetail(sslId string) (*SslConfigDetailResponse, error) {
return c.SslConfigDetailWithContext(context.Background(), sslId)
}
func (c *Client) SslConfigDetailWithContext(ctx context.Context, sslId string) (*SslConfigDetailResponse, error) {
if sslId == "" {
return nil, fmt.Errorf("sdkerr: unset sslId")
}
httpreq, err := c.newRequest(http.MethodGet, "/sslconfig/detail")
if err != nil {
return nil, err
} else {
httpreq.SetQueryParam("id", sslId)
httpreq.SetContext(ctx)
}
result := &SslConfigDetailResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
+39
View File
@@ -0,0 +1,39 @@
package samwaf
import (
"context"
"net/http"
)
type SslConfigEditRequest struct {
Id string `json:"id"`
CertContent string `json:"cert_content"`
CertPath string `json:"cert_path"`
KeyContent string `json:"key_content"`
KeyPath string `json:"key_path"`
}
type SslConfigEditResponse struct {
sdkResponseBase
}
func (c *Client) SslConfigEdit(req *SslConfigEditRequest) (*SslConfigEditResponse, error) {
return c.SslConfigEditWithContext(context.Background(), req)
}
func (c *Client) SslConfigEditWithContext(ctx context.Context, req *SslConfigEditRequest) (*SslConfigEditResponse, error) {
httpreq, err := c.newRequest(http.MethodPost, "/sslconfig/edit")
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &SslConfigEditResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
+110
View File
@@ -0,0 +1,110 @@
package samwaf
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/internal/app"
)
type Client struct {
client *resty.Client
}
func NewClient(serverUrl, apiKey string) (*Client, error) {
if serverUrl == "" {
return nil, fmt.Errorf("sdkerr: unset serverUrl")
}
if _, err := url.Parse(serverUrl); err != nil {
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
}
if apiKey == "" {
return nil, fmt.Errorf("sdkerr: unset apiKey")
}
client := resty.New().
SetBaseURL(strings.TrimRight(serverUrl, "/")+"/api/v1").
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetHeader("X-API-Key", apiKey)
return &Client{client}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
c.client.SetTLSClientConfig(config)
return c
}
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
req := c.client.R()
req.Method = method
req.URL = path
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
// WARN:
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := c.doRequest(req)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &res)
}
return resp, err
}
if len(resp.Body()) != 0 {
var errRes *sdkResponseBase
if err := json.Unmarshal(resp.Body(), &errRes); err == nil {
if tcode := errRes.GetCode(); tcode != 0 {
return resp, fmt.Errorf("sdkerr: code='%d', msg='%s'", tcode, errRes.GetMsg())
}
}
if err := json.Unmarshal(resp.Body(), &res); err != nil {
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
}
}
return resp, nil
}
+37
View File
@@ -0,0 +1,37 @@
package samwaf
type sdkResponse interface {
GetCode() int
GetMsg() string
}
type sdkResponseBase struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
}
func (r *sdkResponseBase) GetCode() int {
return r.Code
}
func (r *sdkResponseBase) GetMsg() string {
return r.Msg
}
var _ sdkResponse = (*sdkResponseBase)(nil)
type SslConfig struct {
Id string `json:"id"`
CertContent string `json:"cert_content"`
KeyContent string `json:"key_content"`
SerialNo string `json:"serial_no"`
Subject string `json:"subject"`
Issuer string `json:"issuer"`
ValidFrom string `json:"valid_from"`
ValidTo string `json:"valid_to"`
Domains string `json:"domains"`
KeyPath string `json:"key_path"`
CertPath string `json:"cert_path"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -88,6 +88,7 @@ import AccessConfigFieldsProviderRatPanel from "./AccessConfigFieldsProviderRatP
import AccessConfigFieldsProviderRFC2136 from "./AccessConfigFieldsProviderRFC2136";
import AccessConfigFieldsProviderS3 from "./AccessConfigFieldsProviderS3";
import AccessConfigFieldsProviderSafeLine from "./AccessConfigFieldsProviderSafeLine";
import AccessConfigFieldsProviderSamWAF from "./AccessConfigFieldsProviderSamWAF";
import AccessConfigFieldsProviderSectigo from "./AccessConfigFieldsProviderSectigo";
import AccessConfigFieldsProviderSlackBot from "./AccessConfigFieldsProviderSlackBot";
import AccessConfigFieldsProviderSpaceship from "./AccessConfigFieldsProviderSpaceship";
@@ -202,6 +203,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.RFC2136]: AccessConfigFieldsProviderRFC2136,
[ACCESS_PROVIDERS.S3]: AccessConfigFieldsProviderS3,
[ACCESS_PROVIDERS.SAFELINE]: AccessConfigFieldsProviderSafeLine,
[ACCESS_PROVIDERS.SAMWAF]: AccessConfigFieldsProviderSamWAF,
[ACCESS_PROVIDERS.SECTIGO]: AccessConfigFieldsProviderSectigo,
[ACCESS_PROVIDERS.SLACKBOT]: AccessConfigFieldsProviderSlackBot,
[ACCESS_PROVIDERS.SPACESHIP]: AccessConfigFieldsProviderSpaceship,
@@ -0,0 +1,73 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProviderSamWAF = () => {
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, "serverUrl"]}
initialValue={initialValues.serverUrl}
label={t("access.form.samwaf_server_url.label")}
rules={[formRule]}
>
<Input type="url" placeholder={t("access.form.samwaf_server_url.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiKey"]}
initialValue={initialValues.apiKey}
label={t("access.form.samwaf_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.samwaf_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.samwaf_api_key.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "allowInsecureConnections"]}
initialValue={initialValues.allowInsecureConnections}
label={t("access.form.shared_allow_insecure_conns.label")}
rules={[formRule]}
>
<Switch />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
serverUrl: "http://<your-host-addr>:26666/",
apiKey: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
serverUrl: z.url(t("common.errmsg.url_invalid")),
apiKey: z.string().nonempty(t("access.form.samwaf_api_key.placeholder")),
allowInsecureConnections: z.boolean().nullish(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderSamWAF, {
getInitialValues,
getSchema,
});
export default _default;
@@ -2,7 +2,7 @@
import { DEPLOYMENT_PROVIDERS, type DeploymentProviderType } from "@/domain/provider";
import BizDeployNodeConfigFieldsProvider1Panel from "./BizDeployNodeConfigFieldsProvider1Panel.tsx";
import BizDeployNodeConfigFieldsProvider1Panel from "./BizDeployNodeConfigFieldsProvider1Panel";
import BizDeployNodeConfigFieldsProvider1PanelConsole from "./BizDeployNodeConfigFieldsProvider1PanelConsole";
import BizDeployNodeConfigFieldsProviderAliyunALB from "./BizDeployNodeConfigFieldsProviderAliyunALB";
import BizDeployNodeConfigFieldsProviderAliyunAPIGW from "./BizDeployNodeConfigFieldsProviderAliyunAPIGW";
@@ -30,16 +30,16 @@ import BizDeployNodeConfigFieldsProviderBaiduCloudAppBLB from "./BizDeployNodeCo
import BizDeployNodeConfigFieldsProviderBaiduCloudBLB from "./BizDeployNodeConfigFieldsProviderBaiduCloudBLB";
import BizDeployNodeConfigFieldsProviderBaiduCloudCDN from "./BizDeployNodeConfigFieldsProviderBaiduCloudCDN";
import BizDeployNodeConfigFieldsProviderBaishanCDN from "./BizDeployNodeConfigFieldsProviderBaishanCDN";
import BizDeployNodeConfigFieldsProviderBaotaPanel from "./BizDeployNodeConfigFieldsProviderBaotaPanel.tsx";
import BizDeployNodeConfigFieldsProviderBaotaPanel from "./BizDeployNodeConfigFieldsProviderBaotaPanel";
import BizDeployNodeConfigFieldsProviderBaotaPanelConsole from "./BizDeployNodeConfigFieldsProviderBaotaPanelConsole";
import BizDeployNodeConfigFieldsProviderBaotaPanelGo from "./BizDeployNodeConfigFieldsProviderBaotaPanelGo.tsx";
import BizDeployNodeConfigFieldsProviderBaotaPanelGo from "./BizDeployNodeConfigFieldsProviderBaotaPanelGo";
import BizDeployNodeConfigFieldsProviderBaotaPanelGoConsole from "./BizDeployNodeConfigFieldsProviderBaotaPanelGoConsole";
import BizDeployNodeConfigFieldsProviderBaotaWAF from "./BizDeployNodeConfigFieldsProviderBaotaWAF.tsx";
import BizDeployNodeConfigFieldsProviderBaotaWAFConsole from "./BizDeployNodeConfigFieldsProviderBaotaWAFConsole.tsx";
import BizDeployNodeConfigFieldsProviderBaotaWAF from "./BizDeployNodeConfigFieldsProviderBaotaWAF";
import BizDeployNodeConfigFieldsProviderBaotaWAFConsole from "./BizDeployNodeConfigFieldsProviderBaotaWAFConsole";
import BizDeployNodeConfigFieldsProviderBunnyCDN from "./BizDeployNodeConfigFieldsProviderBunnyCDN";
import BizDeployNodeConfigFieldsProviderBytePlusCDN from "./BizDeployNodeConfigFieldsProviderBytePlusCDN";
import BizDeployNodeConfigFieldsProviderCdnfly from "./BizDeployNodeConfigFieldsProviderCdnfly";
import BizDeployNodeConfigFieldsProviderCPanel from "./BizDeployNodeConfigFieldsProviderCPanel.tsx";
import BizDeployNodeConfigFieldsProviderCPanel from "./BizDeployNodeConfigFieldsProviderCPanel";
import BizDeployNodeConfigFieldsProviderCTCCCloudAO from "./BizDeployNodeConfigFieldsProviderCTCCCloudAO";
import BizDeployNodeConfigFieldsProviderCTCCCloudCDN from "./BizDeployNodeConfigFieldsProviderCTCCCloudCDN";
import BizDeployNodeConfigFieldsProviderCTCCCloudELB from "./BizDeployNodeConfigFieldsProviderCTCCCloudELB";
@@ -70,7 +70,7 @@ import BizDeployNodeConfigFieldsProviderKubernetesSecret from "./BizDeployNodeCo
import BizDeployNodeConfigFieldsProviderLeCDN from "./BizDeployNodeConfigFieldsProviderLeCDN";
import BizDeployNodeConfigFieldsProviderLocal from "./BizDeployNodeConfigFieldsProviderLocal";
import BizDeployNodeConfigFieldsProviderMohuaMVH from "./BizDeployNodeConfigFieldsProviderMohuaMVH";
import BizDeployNodeConfigFieldsProviderNetlify from "./BizDeployNodeConfigFieldsProviderNetlify.tsx";
import BizDeployNodeConfigFieldsProviderNetlify from "./BizDeployNodeConfigFieldsProviderNetlify";
import BizDeployNodeConfigFieldsProviderNginxProxyManager from "./BizDeployNodeConfigFieldsProviderNginxProxyManager";
import BizDeployNodeConfigFieldsProviderProxmoxVE from "./BizDeployNodeConfigFieldsProviderProxmoxVE";
import BizDeployNodeConfigFieldsProviderQiniuCDN from "./BizDeployNodeConfigFieldsProviderQiniuCDN";
@@ -78,9 +78,10 @@ import BizDeployNodeConfigFieldsProviderQiniuKodo from "./BizDeployNodeConfigFie
import BizDeployNodeConfigFieldsProviderQiniuPili from "./BizDeployNodeConfigFieldsProviderQiniuPili";
import BizDeployNodeConfigFieldsProviderRainYunRCDN from "./BizDeployNodeConfigFieldsProviderRainYunRCDN";
import BizDeployNodeConfigFieldsProviderRainYunSSLCenter from "./BizDeployNodeConfigFieldsProviderRainYunSSLCenter";
import BizDeployNodeConfigFieldsProviderRatPanel from "./BizDeployNodeConfigFieldsProviderRatPanel.tsx";
import BizDeployNodeConfigFieldsProviderRatPanel from "./BizDeployNodeConfigFieldsProviderRatPanel";
import BizDeployNodeConfigFieldsProviderS3 from "./BizDeployNodeConfigFieldsProviderS3";
import BizDeployNodeConfigFieldsProviderSafeLine from "./BizDeployNodeConfigFieldsProviderSafeLine.tsx";
import BizDeployNodeConfigFieldsProviderSafeLine from "./BizDeployNodeConfigFieldsProviderSafeLine";
import BizDeployNodeConfigFieldsProviderSamWAF from "./BizDeployNodeConfigFieldsProviderSamWAF";
import BizDeployNodeConfigFieldsProviderSSH from "./BizDeployNodeConfigFieldsProviderSSH";
import BizDeployNodeConfigFieldsProviderSynologyDSM from "./BizDeployNodeConfigFieldsProviderSynologyDSM";
import BizDeployNodeConfigFieldsProviderTencentCloudCDN from "./BizDeployNodeConfigFieldsProviderTencentCloudCDN";
@@ -114,8 +115,8 @@ import BizDeployNodeConfigFieldsProviderVolcEngineDCDN from "./BizDeployNodeConf
import BizDeployNodeConfigFieldsProviderVolcEngineImageX from "./BizDeployNodeConfigFieldsProviderVolcEngineImageX";
import BizDeployNodeConfigFieldsProviderVolcEngineLive from "./BizDeployNodeConfigFieldsProviderVolcEngineLive";
import BizDeployNodeConfigFieldsProviderVolcEngineTOS from "./BizDeployNodeConfigFieldsProviderVolcEngineTOS";
import BizDeployNodeConfigFieldsProviderVolcEngineVOD from "./BizDeployNodeConfigFieldsProviderVolcEngineVOD.tsx";
import BizDeployNodeConfigFieldsProviderVolcEngineWAF from "./BizDeployNodeConfigFieldsProviderVolcEngineWAF.tsx";
import BizDeployNodeConfigFieldsProviderVolcEngineVOD from "./BizDeployNodeConfigFieldsProviderVolcEngineVOD";
import BizDeployNodeConfigFieldsProviderVolcEngineWAF from "./BizDeployNodeConfigFieldsProviderVolcEngineWAF";
import BizDeployNodeConfigFieldsProviderWangsuCDN from "./BizDeployNodeConfigFieldsProviderWangsuCDN";
import BizDeployNodeConfigFieldsProviderWangsuCDNPro from "./BizDeployNodeConfigFieldsProviderWangsuCDNPro";
import BizDeployNodeConfigFieldsProviderWangsuCertificate from "./BizDeployNodeConfigFieldsProviderWangsuCertificate";
@@ -205,6 +206,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.RATPANEL]: BizDeployNodeConfigFieldsProviderRatPanel,
[DEPLOYMENT_PROVIDERS.S3]: BizDeployNodeConfigFieldsProviderS3,
[DEPLOYMENT_PROVIDERS.SAFELINE]: BizDeployNodeConfigFieldsProviderSafeLine,
[DEPLOYMENT_PROVIDERS.SAMWAF]: BizDeployNodeConfigFieldsProviderSamWAF,
[DEPLOYMENT_PROVIDERS.SSH]: BizDeployNodeConfigFieldsProviderSSH,
[DEPLOYMENT_PROVIDERS.SYNOLOGYDSM]: BizDeployNodeConfigFieldsProviderSynologyDSM,
[DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CDN]: BizDeployNodeConfigFieldsProviderTencentCloudCDN,
@@ -0,0 +1,99 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import Show from "@/components/Show";
import Tips from "@/components/Tips";
import { useFormNestedFieldsContext } from "./_context";
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
const BizDeployNodeConfigFieldsProviderSamWAF = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const formInst = Form.useFormInstance();
const initialValues = getInitialValues();
const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst);
return (
<>
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.samwaf.guide") }}></span>} />
</Form.Item>
<Form.Item
name={[parentNamePath, "resourceType"]}
initialValue={initialValues.resourceType}
label={t("workflow_node.deploy.form.shared_resource_type.label")}
rules={[formRule]}
>
<Select
options={[RESOURCE_TYPE_CERTIFICATE].map((s) => ({
value: s,
label: t(`workflow_node.deploy.form.samwaf_resource_type.option.${s}.label`),
}))}
placeholder={t("workflow_node.deploy.form.shared_resource_type.placeholder")}
/>
</Form.Item>
<Show when={fieldResourceType === RESOURCE_TYPE_CERTIFICATE}>
<Form.Item
name={[parentNamePath, "certificateId"]}
initialValue={initialValues.certificateId}
label={t("workflow_node.deploy.form.samwaf_certificate_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.samwaf_certificate_id.tooltip") }}></span>}
>
<Input type="number" placeholder={t("workflow_node.deploy.form.samwaf_certificate_id.placeholder")} />
</Form.Item>
</Show>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
resourceType: RESOURCE_TYPE_CERTIFICATE,
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z
.object({
resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.shared_resource_type.placeholder")),
certificateId: z.union([z.string(), z.number().int()]).nullish(),
})
.superRefine((values, ctx) => {
switch (values.resourceType) {
case RESOURCE_TYPE_CERTIFICATE:
{
const scCertificateId = z.coerce.number().int().positive();
if (!scCertificateId.safeParse(values.certificateId).success) {
ctx.addIssue({
code: "custom",
message: t("workflow_node.deploy.form.samwaf_certificate_id.placeholder"),
path: ["certificateId"],
});
}
}
break;
}
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderSamWAF, {
getInitialValues,
getSchema,
});
export default _default;
+4
View File
@@ -104,6 +104,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
RFC2136: "rfc2136",
S3: "s3",
SAFELINE: "safeline",
SAMWAF: "samwaf",
SECTIGO: "sectigo",
SLACKBOT: "slackbot",
SPACESHIP: "spaceship",
@@ -199,6 +200,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.PROXMOXVE, "provider.proxmoxve", "/imgs/providers/proxmoxve.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.RATPANEL, "provider.ratpanel", "/imgs/providers/ratpanel.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SAMWAF, "provider.samwaf", "/imgs/providers/samwaf.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SYNOLOGYDSM, "provider.synologydsm", "/imgs/providers/synologydsm.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.AKAMAI, "provider.akamai", "/imgs/providers/akamai.svg", [ACCESS_USAGES.DNS]],
@@ -639,6 +641,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
RATPANEL_CONSOLE: `${ACCESS_PROVIDERS.RATPANEL}-console`,
S3: `${ACCESS_PROVIDERS.S3}`,
SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
SAMWAF: `${ACCESS_PROVIDERS.SAMWAF}`,
SSH: `${ACCESS_PROVIDERS.SSH}`,
SYNOLOGYDSM: `${ACCESS_PROVIDERS.SYNOLOGYDSM}`,
TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
@@ -830,6 +833,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.BAOTAWAF, "provider.baotawaf_common", DEPLOYMENT_CATEGORIES.FIREWALL],
[DEPLOYMENT_PROVIDERS.BAOTAWAF_CONSOLE, "provider.baotawaf_console", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.SAFELINE, "provider.safeline", DEPLOYMENT_CATEGORIES.FIREWALL],
[DEPLOYMENT_PROVIDERS.SAMWAF, "provider.samwaf", DEPLOYMENT_CATEGORIES.FIREWALL],
[DEPLOYMENT_PROVIDERS.APISIX, "provider.apisix", DEPLOYMENT_CATEGORIES.APIGATEWAY],
[DEPLOYMENT_PROVIDERS.KONG, "provider.kong", DEPLOYMENT_CATEGORIES.APIGATEWAY],
[DEPLOYMENT_PROVIDERS.CPANEL, "provider.cpanel", DEPLOYMENT_CATEGORIES.WEBSITE],
+5
View File
@@ -571,6 +571,11 @@
"access.form.s3_signature_version.placeholder": "Please select signature version",
"access.form.s3_use_path_style.label": "Use path style addressing",
"access.form.s3_use_path_style.tooltip": "<ol style=\"list-style: disc;\"><li>Virtual-hosted style (default): https://&lt;BUCKET&gt;.&lt;ENDPOINT&gt;/&lt;KEY&gt; </li><li>Path style: https://&lt;ENDPOINT&gt;/&lt;BUCKET&gt;/&lt;KEY&gt; </li></ol>",
"access.form.samwaf_server_url.label": "SamWAF server URL",
"access.form.samwaf_server_url.placeholder": "Please enter SamWAF server URL",
"access.form.samwaf_api_key.label": "SamWAF API Key",
"access.form.samwaf_api_key.placeholder": "Please enter SamWAF API Key",
"access.form.samwaf_api_key.tooltip": "For more information, see <a href=\"https://doc.samwaf.com/api/\" target=\"_blank\">https://doc.samwaf.com/api/</a>",
"access.form.safeline_server_url.label": "SafeLine server URL",
"access.form.safeline_server_url.placeholder": "Please enter SafeLine server URL",
"access.form.safeline_api_token.label": "SafeLine API token",
+1
View File
@@ -171,6 +171,7 @@
"provider.s3": "Object storage (S3-compatible)",
"provider.s3_upload": "Upload to S3-compatible object storage",
"provider.safeline": "SafeLine",
"provider.samwaf": "SamWAF",
"provider.sectigo": "Sectigo",
"provider.slackbot": "Slack Bot",
"provider.spaceship": "Spaceship",
@@ -574,7 +574,7 @@
"workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "Certificate",
"workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN certificate ID",
"workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "Please enter FlexCDN certificate ID",
"workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "You can find it on FlexCDN dashboard.",
"workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "You can find it on FlexCDN dashboard. <br><br>(Please open the browser devtools, and visit the \"网站列表 -> 证书管理 -> 证书\" page to capture network requests, then you can get the certificate ID.)",
"workflow_node.deploy.form.flyio_app_name.label": "Fly.io App name",
"workflow_node.deploy.form.flyio_app_name.placeholder": "Please enter Fly.io App name",
"workflow_node.deploy.form.flyio_domain.label": "Fly.io custom domain",
@@ -590,7 +590,7 @@
"workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "Certificate",
"workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge certificate ID",
"workflow_node.deploy.form.goedge_certificate_id.placeholder": "Please enter GoEdge certificate ID",
"workflow_node.deploy.form.goedge_certificate_id.tooltip": "You can find it on GoEdge dashboard.",
"workflow_node.deploy.form.goedge_certificate_id.tooltip": "You can find it on GoEdge dashboard. <br><br>(Please open the browser devtools, and visit the \"网站列表 -> 证书管理 -> 证书\" page to capture network requests, then you can get the certificate ID.)",
"workflow_node.deploy.form.huaweicloud_aad_domain.label": "Huawei Cloud AAD domain",
"workflow_node.deploy.form.huaweicloud_aad_domain.placeholder": "Please enter Huawei Cloud AAD domain name",
"workflow_node.deploy.form.huaweicloud_aad_instance_id.label": "Huawei Cloud AAD instance ID",
@@ -875,6 +875,11 @@
"workflow_node.deploy.form.safeline_certificate_id.label": "SafeLine certificate ID",
"workflow_node.deploy.form.safeline_certificate_id.placeholder": "Please enter SafeLine certificate ID",
"workflow_node.deploy.form.safeline_certificate_id.tooltip": "You can find it on SafeLine dashboard.",
"workflow_node.deploy.form.samwaf.guide": "Requires SamWAF v1.3.20 or higher.",
"workflow_node.deploy.form.samwaf_resource_type.option.certificate.label": "Certificate",
"workflow_node.deploy.form.samwaf_certificate_id.label": "SamWAF certificate ID",
"workflow_node.deploy.form.samwaf_certificate_id.placeholder": "Please enter SamWAF certificate ID",
"workflow_node.deploy.form.samwaf_certificate_id.tooltip": "You can find it on SamWAF dashboard.",
"workflow_node.deploy.form.ssh_format.label": "File format",
"workflow_node.deploy.form.ssh_format.placeholder": "Please select file format",
"workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)",
+6 -1
View File
@@ -46,7 +46,7 @@
"access.form.shared_acme_eab_kid.placeholder": "请输入 ACME EAB KID",
"access.form.shared_acme_eab_hmac_key.label": "ACME EAB HMAC Key",
"access.form.shared_acme_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
"access.form.shared_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
"access.form.shared_allow_insecure_conns.label": "忽略 SSL/TLS 证书验证",
"access.form.1panel_server_url.label": "1Panel 服务地址",
"access.form.1panel_server_url.placeholder": "请输入 1Panel 服务地址",
"access.form.1panel_server_url.help": "提示:请勿包含安全入口后缀。",
@@ -571,6 +571,11 @@
"access.form.s3_signature_version.placeholder": "请选择签名版本",
"access.form.s3_use_path_style.label": "使用路径风格地址",
"access.form.s3_use_path_style.tooltip": "<ol style=\"list-style: disc;\"><li>虚拟托管风格(默认):<br><em>https://&lt;BUCKET&gt;.&lt;ENDPOINT&gt;/&lt;KEY&gt;</em> </li><li>路径风格:<br><em>https://&lt;ENDPOINT&gt;/&lt;BUCKET&gt;/&lt;KEY&gt;</em> </li></ol>",
"access.form.samwaf_server_url.label": "SamWAF 服务地址",
"access.form.samwaf_server_url.placeholder": "请输入 SamWAF 服务地址",
"access.form.samwaf_api_key.label": "SamWAF API Key",
"access.form.samwaf_api_key.placeholder": "请输入 SamWAF API Key",
"access.form.samwaf_api_key.tooltip": "这是什么?请参阅 <a href=\"https://doc.samwaf.com/api/\" target=\"_blank\">https://doc.samwaf.com/api/</a>",
"access.form.safeline_server_url.label": "雷池服务地址",
"access.form.safeline_server_url.placeholder": "请输入雷池服务地址",
"access.form.safeline_api_token.label": "雷池 API Token",
+1
View File
@@ -171,6 +171,7 @@
"provider.s3": "对象存储(S3 兼容)",
"provider.s3_upload": "上传到 S3 兼容的对象存储",
"provider.safeline": "雷池",
"provider.samwaf": "SamWAF",
"provider.sectigo": "Sectigo",
"provider.slackbot": "Slack 机器人",
"provider.spaceship": "Spaceship",
@@ -572,7 +572,7 @@
"workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "替换指定证书",
"workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN 证书 ID",
"workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "请输入 FlexCDN 证书 ID",
"workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 控制台查看",
"workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 控制台查看<br><br>(目前需要在「网站列表 -> 证书管理 -> 证书」页面,并打开浏览器开发者工具抓取网络请求后得到证书 ID)",
"workflow_node.deploy.form.flyio_app_name.label": "Fly.io 应用名称",
"workflow_node.deploy.form.flyio_app_name.placeholder": "请输入 Fly.io 应用名称",
"workflow_node.deploy.form.flyio_domain.label": "Fly.io 自定义域名",
@@ -588,7 +588,7 @@
"workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书",
"workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge 证书 ID",
"workflow_node.deploy.form.goedge_certificate_id.placeholder": "请输入 GoEdge 证书 ID",
"workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 控制台查看",
"workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 控制台查看<br><br>(目前需要在「网站列表 -> 证书管理 -> 证书」页面,并打开浏览器开发者工具抓取网络请求后得到证书 ID)",
"workflow_node.deploy.form.huaweicloud_aad_instance_id.label": "华为云 DDoS 高防实例 ID",
"workflow_node.deploy.form.huaweicloud_aad_instance_id.placeholder": "请输入华为云 DDoS 高防实例 ID",
"workflow_node.deploy.form.huaweicloud_aad_instance_id.tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/cnad/#/cnad/cad/list\" target=\"_blank\">https://console.huaweicloud.com/cnad/#/cnad/cad/list</a>",
@@ -873,6 +873,11 @@
"workflow_node.deploy.form.safeline_certificate_id.label": "雷池证书 ID",
"workflow_node.deploy.form.safeline_certificate_id.placeholder": "请输入雷池证书 ID",
"workflow_node.deploy.form.safeline_certificate_id.tooltip": "请登录雷池控制台查看",
"workflow_node.deploy.form.samwaf.guide": "需要 SamWAF v1.3.20 或更高版本。",
"workflow_node.deploy.form.samwaf_resource_type.option.certificate.label": "替换指定证书",
"workflow_node.deploy.form.samwaf_certificate_id.label": "SamWAF 证书 ID",
"workflow_node.deploy.form.samwaf_certificate_id.placeholder": "请输入 SamWAF 证书 ID",
"workflow_node.deploy.form.samwaf_certificate_id.tooltip": "请登录 SamWAF 控制台查看",
"workflow_node.deploy.form.ssh_format.label": "文件格式",
"workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式",
"workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key",