diff --git a/internal/certmgmt/deployers/sp_samwaf_console.go b/internal/certmgmt/deployers/sp_samwaf_console.go new file mode 100644 index 00000000..a68f6b02 --- /dev/null +++ b/internal/certmgmt/deployers/sp_samwaf_console.go @@ -0,0 +1,27 @@ +package deployers + +import ( + "fmt" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core" + dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/samwaf-console" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + Registries.MustRegister(domain.DeploymentProviderTypeSamWAFConsole, func(options *ProviderFactoryOptions) (core.Deployer, 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 := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{ + ServerUrl: credentials.ServerUrl, + ApiKey: credentials.ApiKey, + AllowInsecureConnections: credentials.AllowInsecureConnections, + AutoRestart: xmaps.GetBool(options.ProviderExtendedConfig, "autoRestart"), + }) + return provider, err + }) +} diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 840d7d5a..226bc690 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -420,6 +420,7 @@ const ( DeploymentProviderTypeS3 = DeploymentProviderType(AccessProviderTypeS3) DeploymentProviderTypeSafeLine = DeploymentProviderType(AccessProviderTypeSafeLine) DeploymentProviderTypeSamWAF = DeploymentProviderType(AccessProviderTypeSamWAF) + DeploymentProviderTypeSamWAFConsole = DeploymentProviderType(AccessProviderTypeSamWAF + "-console") DeploymentProviderTypeSSH = DeploymentProviderType(AccessProviderTypeSSH) DeploymentProviderTypeSynologyDSM = DeploymentProviderType(AccessProviderTypeSynologyDSM) DeploymentProviderTypeTencentCloudCDN = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn") diff --git a/pkg/core/deployer/providers/safeline/safeline.go b/pkg/core/deployer/providers/safeline/safeline.go index e6d6cd9e..619ff090 100644 --- a/pkg/core/deployer/providers/safeline/safeline.go +++ b/pkg/core/deployer/providers/safeline/safeline.go @@ -63,7 +63,7 @@ func (d *Deployer) SetLogger(logger *slog.Logger) { } func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) { - // 根据部署目标决定业务流程`` + // 根据部署目标决定业务流程 switch d.config.DeployTarget { case DEPLOY_TARGET_CERTIFICATE: if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { diff --git a/pkg/core/deployer/providers/samwaf-console/samwaf_console.go b/pkg/core/deployer/providers/samwaf-console/samwaf_console.go new file mode 100644 index 00000000..e45b5af8 --- /dev/null +++ b/pkg/core/deployer/providers/samwaf-console/samwaf_console.go @@ -0,0 +1,111 @@ +package samwafconsole + +import ( + "context" + "crypto/tls" + "fmt" + "log/slog" + + "github.com/certimate-go/certimate/pkg/core" + samwafsdk "github.com/certimate-go/certimate/pkg/sdk3rd/samwaf" +) + +type ( + Provider = core.Deployer + DeployResult = core.DeployerDeployResult +) + +type DeployerConfig struct { + // SamWAF 服务地址。 + ServerUrl string `json:"serverUrl"` + // SamWAF API Key。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 是否自动重启。 + AutoRestart bool `json:"autoRestart"` +} + +type Deployer struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *samwafsdk.Client +} + +var _ Provider = (*Deployer)(nil) + +func NewDeployer(config *DeployerConfig) (*Deployer, error) { + if config == nil { + return nil, fmt.Errorf("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) (*DeployResult, error) { + // 上传管理端 SSL 证书 + // REF: https://doc.samwaf.com/api/ + vipConfigUploadSslCertReq := &samwafsdk.VipConfigUploadSslCertRequest{ + CertContent: certPEM, + KeyContent: privkeyPEM, + } + vipConfigUploadSslCertResp, err := d.sdkClient.VipConfigUploadSslCertWithContext(ctx, vipConfigUploadSslCertReq) + d.logger.Debug("sdk request 'vipconfig.UploadSslCert'", slog.Any("request", vipConfigUploadSslCertReq), slog.Any("response", vipConfigUploadSslCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vipconfig.UploadSslCert': %w", err) + } + + // 更新管理端 SSL 启用状态 + // REF: https://doc.samwaf.com/api/ + vipConfigUpdateSslEnableReq := &samwafsdk.VipConfigUpdateSslEnableRequest{ + SslEnable: true, + } + vipConfigUpdateSslEnableResp, err := d.sdkClient.VipConfigUpdateSslEnableWithContext(ctx, vipConfigUpdateSslEnableReq) + d.logger.Debug("sdk request 'vipconfig.UpdateSslEnable'", slog.Any("request", vipConfigUpdateSslEnableReq), slog.Any("response", vipConfigUpdateSslEnableResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vipconfig.UpdateSslEnable': %w", err) + } + + if d.config.AutoRestart { + // 重启管理端 + vipConfigRestartManagerResp, err := d.sdkClient.VipConfigRestartManagerWithContext(ctx) + d.logger.Debug("sdk request 'vipconfig.RestartManager'", slog.Any("response", vipConfigRestartManagerResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'vipconfig.RestartManager': %w", err) + } + } + + return &DeployResult{}, nil +} + +func createSDKClient(serverUrl, apiKey string, skipTlsVerify bool) (*samwafsdk.Client, error) { + client, err := samwafsdk.NewClient(serverUrl, + samwafsdk.WithApiKey(apiKey), + ) + if err != nil { + return nil, err + } + + if skipTlsVerify { + client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/pkg/core/deployer/providers/samwaf-console/samwaf_console_test.go b/pkg/core/deployer/providers/samwaf-console/samwaf_console_test.go new file mode 100644 index 00000000..291a0cab --- /dev/null +++ b/pkg/core/deployer/providers/samwaf-console/samwaf_console_test.go @@ -0,0 +1,52 @@ +package samwafconsole_test + +import ( + "testing" + + "github.com/certimate-go/certimate/pkg/core/deployer/internal/tester" + impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/samwaf-console" +) + +var ( + fp = tester.Args("SAMWAFCONSOLE_") + fTestCertPath string + fTestKeyPath string + fServerUrl string + fApiKey string +) + +func init() { + fp.DefineString(&fTestCertPath, "TESTCERTPATH") + fp.DefineString(&fTestKeyPath, "TESTKEYPATH") + fp.DefineString(&fServerUrl, "SERVERURL") + fp.DefineString(&fApiKey, "APIKEY") +} + +/* +Shell command to run this test: + + go test -v ./samwaf_console_test.go -args \ + --SAMWAFCONSOLE_TESTCERTPATH="/path/to/your-test-cert.pem" \ + --SAMWAFCONSOLE_TESTKEYPATH="/path/to/your-test-key.pem" \ + --SAMWAFCONSOLE_SERVERURL="http://127.0.0.1:26666" \ + --SAMWAFCONSOLE_APIKEY="your-api-key" \ + --SAMWAFCONSOLE_CERTIFICATEID="your-certificate-id" +*/ +func TestProvider(t *testing.T) { + fp.Parse() + + t.Run("Deploy", func(t *testing.T) { + provider, err := impl.NewDeployer(&impl.DeployerConfig{ + ServerUrl: fServerUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + AutoRestart: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + tester.TestDeploy(t, provider, tester.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath}) + }) +} diff --git a/pkg/core/deployer/providers/samwaf/samwaf.go b/pkg/core/deployer/providers/samwaf/samwaf.go index 89e217c4..96f56988 100644 --- a/pkg/core/deployer/providers/samwaf/samwaf.go +++ b/pkg/core/deployer/providers/samwaf/samwaf.go @@ -63,7 +63,7 @@ func (d *Deployer) SetLogger(logger *slog.Logger) { } func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) { - // 根据部署目标决定业务流程`` + // 根据部署目标决定业务流程 switch d.config.DeployTarget { case DEPLOY_TARGET_CERTIFICATE: if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { diff --git a/pkg/sdk3rd/samwaf/api_vipconfig_restartmanager.go b/pkg/sdk3rd/samwaf/api_vipconfig_restartmanager.go new file mode 100644 index 00000000..8ce5b233 --- /dev/null +++ b/pkg/sdk3rd/samwaf/api_vipconfig_restartmanager.go @@ -0,0 +1,30 @@ +package samwaf + +import ( + "context" + "net/http" +) + +type VipConfigRestartManagerResponse struct { + sdkResponseBase +} + +func (c *Client) VipConfigRestartManager() (*VipConfigRestartManagerResponse, error) { + return c.VipConfigRestartManagerWithContext(context.Background()) +} + +func (c *Client) VipConfigRestartManagerWithContext(ctx context.Context) (*VipConfigRestartManagerResponse, error) { + httpreq, err := c.newRequest(http.MethodPost, "/vipconfig/restartManager") + if err != nil { + return nil, err + } else { + httpreq.SetContext(ctx) + } + + result := &VipConfigRestartManagerResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/samwaf/api_vipconfig_updatesslenable.go b/pkg/sdk3rd/samwaf/api_vipconfig_updatesslenable.go new file mode 100644 index 00000000..f9867ce4 --- /dev/null +++ b/pkg/sdk3rd/samwaf/api_vipconfig_updatesslenable.go @@ -0,0 +1,35 @@ +package samwaf + +import ( + "context" + "net/http" +) + +type VipConfigUpdateSslEnableRequest struct { + SslEnable bool `json:"ssl_enable"` +} + +type VipConfigUpdateSslEnableResponse struct { + sdkResponseBase +} + +func (c *Client) VipConfigUpdateSslEnable(req *VipConfigUpdateSslEnableRequest) (*VipConfigUpdateSslEnableResponse, error) { + return c.VipConfigUpdateSslEnableWithContext(context.Background(), req) +} + +func (c *Client) VipConfigUpdateSslEnableWithContext(ctx context.Context, req *VipConfigUpdateSslEnableRequest) (*VipConfigUpdateSslEnableResponse, error) { + httpreq, err := c.newRequest(http.MethodPost, "/vipconfig/updateSslEnable") + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &VipConfigUpdateSslEnableResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/samwaf/api_vipconfig_uploadsslcert.go b/pkg/sdk3rd/samwaf/api_vipconfig_uploadsslcert.go new file mode 100644 index 00000000..5eba38a0 --- /dev/null +++ b/pkg/sdk3rd/samwaf/api_vipconfig_uploadsslcert.go @@ -0,0 +1,36 @@ +package samwaf + +import ( + "context" + "net/http" +) + +type VipConfigUploadSslCertRequest struct { + CertContent string `json:"cert_content"` + KeyContent string `json:"key_content"` +} + +type VipConfigUploadSslCertResponse struct { + sdkResponseBase +} + +func (c *Client) VipConfigUploadSslCert(req *VipConfigUploadSslCertRequest) (*VipConfigUploadSslCertResponse, error) { + return c.VipConfigUploadSslCertWithContext(context.Background(), req) +} + +func (c *Client) VipConfigUploadSslCertWithContext(ctx context.Context, req *VipConfigUploadSslCertRequest) (*VipConfigUploadSslCertResponse, error) { + httpreq, err := c.newRequest(http.MethodPost, "/vipconfig/uploadSslCert") + if err != nil { + return nil, err + } else { + httpreq.SetBody(req) + httpreq.SetContext(ctx) + } + + result := &VipConfigUploadSslCertResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx index cd8e8635..8965de4c 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx @@ -101,6 +101,7 @@ import BizDeployNodeConfigFieldsProviderRatPanel from "./BizDeployNodeConfigFiel import BizDeployNodeConfigFieldsProviderS3 from "./BizDeployNodeConfigFieldsProviderS3"; import BizDeployNodeConfigFieldsProviderSafeLine from "./BizDeployNodeConfigFieldsProviderSafeLine"; import BizDeployNodeConfigFieldsProviderSamWAF from "./BizDeployNodeConfigFieldsProviderSamWAF"; +import BizDeployNodeConfigFieldsProviderSamWAFConsole from "./BizDeployNodeConfigFieldsProviderSamWAFConsole"; import BizDeployNodeConfigFieldsProviderSSH from "./BizDeployNodeConfigFieldsProviderSSH"; import BizDeployNodeConfigFieldsProviderSynologyDSM from "./BizDeployNodeConfigFieldsProviderSynologyDSM"; import BizDeployNodeConfigFieldsProviderTencentCloudCDN from "./BizDeployNodeConfigFieldsProviderTencentCloudCDN"; @@ -249,6 +250,7 @@ const providerComponentMap: Partial { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + autoRestart: true, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + autoRestart: z.boolean().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFieldsProviderSamWAFConsole, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 0513965a..0804a3d1 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -710,6 +710,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ S3: `${ACCESS_PROVIDERS.S3}`, SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`, SAMWAF: `${ACCESS_PROVIDERS.SAMWAF}`, + SAMWAF_CONSOLE: `${ACCESS_PROVIDERS.SAMWAF}-console`, SSH: `${ACCESS_PROVIDERS.SSH}`, SYNOLOGYDSM: `${ACCESS_PROVIDERS.SYNOLOGYDSM}`, TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`, @@ -932,6 +933,7 @@ export const deploymentProvidersMap: Map