Anom config

This commit is contained in:
Max Lerebourg
2025-08-04 20:25:06 +02:00
parent 3dc154f415
commit 7adc57941e
4 changed files with 91 additions and 45 deletions

View File

@@ -465,7 +465,19 @@ make run
- Used only in `alone` mode, scenarios for Crowdsec CAPI
- CaptchaProvider
- string
- Provider to validate the captcha, expected values are: `hcaptcha`, `recaptcha`, `turnstile`
- Provider to validate the captcha, expected values are: `hcaptcha`, `recaptcha`, `turnstile` or `custom`
- CaptchaCustomJsURL
- string
- If CaptchaProvider is `custom`, URL used to load the challenge in the HTML (in case of hcaptcha: `https://hcaptcha.com/1/api.js`)
- CaptchaCustomValidateURL
- string
- If CaptchaProvider is `custom`, URL used to validate the challenge (in case of hcaptcha: `https://api.hcaptcha.com/siteverify`)
- CaptchaCustomKey
- string
- If CaptchaProvider is `custom`, used to set classname of the div used by captcha provider (in case of hcaptcha: `h-captcha`)
- CaptchaCustomResponse
- string
- If CaptchaProvider is `custom`, used to set the field in the validate URL body (in case of hcaptcha: `h-captcha-response`)
- CaptchaSiteKey
- string
- Site key for the captcha provider

View File

@@ -234,6 +234,17 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
config.CaptchaSecretKey, _ = configuration.GetVariable(config, "CaptchaSecretKey")
tlsConfig2 := new(tls.Config)
tlsConfig2.InsecureSkipVerify = true
var infoProvider *captcha.InfoProvider
if config.CaptchaProvider == configuration.CustomProvider {
infoProvider = &captcha.InfoProvider{
js: config.CaptchaCustomJsURL,
validate: config.CaptchaCustomValidateURL,
key: config.CaptchaCustomKey,
response: config.CaptchaCustomResponse,
}
}
err = bouncer.captchaClient.New(
log,
bouncer.cacheClient,
@@ -245,6 +256,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
},
Timeout: time.Duration(config.HTTPTimeoutSeconds) * time.Second,
},
infoProvider,
config.CaptchaProvider,
config.CaptchaSiteKey,
config.CaptchaSecretKey,

View File

@@ -17,7 +17,6 @@ import (
// Client Captcha client.
type Client struct {
Valid bool
provider string
siteKey string
secretKey string
remediationCustomHeader string
@@ -26,54 +25,51 @@ type Client struct {
cacheClient *cache.Client
httpClient *http.Client
log *logger.Log
infoProvider *InfoProvider
}
type infoProvider struct {
// InfoProvider Information for self-hosted provider.
type InfoProvider struct {
js string
key string
response string
validate string
}
var (
//nolint:gochecknoglobals
captcha = map[string]infoProvider{
configuration.HcaptchaProvider: {
js: "https://hcaptcha.com/1/api.js",
key: "h-captcha",
response: "h-captcha-response",
validate: "https://api.hcaptcha.com/siteverify",
},
configuration.RecaptchaProvider: {
js: "https://www.google.com/recaptcha/api.js",
key: "g-recaptcha",
response: "g-recaptcha-response",
validate: "https://www.google.com/recaptcha/api/siteverify",
},
configuration.TurnstileProvider: {
js: "https://challenges.cloudflare.com/turnstile/v0/api.js",
key: "cf-turnstile",
response: "cf-turnstile-response",
validate: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
},
configuration.WicketkeeperProvider: {
js: "https://captcha.max.lan/fast.js",
key: "wicketkeeper",
response: "wicketkeeper_solution",
validate: "https://captcha.max.lan/v0/siteverify",
},
}
)
//nolint:gochecknoglobals
var infoProviders = map[string]*InfoProvider{
configuration.HcaptchaProvider: &InfoProvider{
js: "https://hcaptcha.com/1/api.js",
key: "h-captcha",
response: "h-captcha-response",
validate: "https://api.hcaptcha.com/siteverify",
},
configuration.RecaptchaProvider: &InfoProvider{
js: "https://www.google.com/recaptcha/api.js",
key: "g-recaptcha",
response: "g-recaptcha-response",
validate: "https://www.google.com/recaptcha/api/siteverify",
},
configuration.TurnstileProvider: &InfoProvider{
js: "https://challenges.cloudflare.com/turnstile/v0/api.js",
key: "cf-turnstile",
response: "cf-turnstile-response",
validate: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
},
}
// New Initialize captcha client.
func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *http.Client, provider, siteKey, secretKey, remediationCustomHeader, captchaTemplatePath string, gracePeriodSeconds int64) error {
func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *http.Client, infoProvider *InfoProvider, provider, siteKey, secretKey, remediationCustomHeader, captchaTemplatePath string, gracePeriodSeconds int64) error {
c.Valid = provider != ""
if !c.Valid {
return nil
}
c.infoProvider = infoProvider
if c.infoProvider == nil {
c.infoProvider = infoProviders[provider]
}
c.siteKey = siteKey
c.secretKey = secretKey
c.provider = provider
c.remediationCustomHeader = remediationCustomHeader
html, _ := configuration.GetHTMLTemplate(captchaTemplatePath)
c.captchaTemplate = html
@@ -105,8 +101,8 @@ func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP str
rw.WriteHeader(http.StatusOK)
err = c.captchaTemplate.Execute(rw, map[string]string{
"SiteKey": c.siteKey,
"FrontendJS": captcha[c.provider].js,
"FrontendKey": captcha[c.provider].key,
"FrontendJS": c.infoProvider.js,
"FrontendKey": c.infoProvider.key,
})
if err != nil {
c.log.Info("captcha:ServeHTTP captchaTemplateServe " + err.Error())
@@ -131,7 +127,7 @@ func (c *Client) Validate(r *http.Request) (bool, error) {
c.log.Debug("captcha:Validate invalid method: " + r.Method)
return false, nil
}
var response = r.FormValue(captcha[c.provider].response)
var response = r.FormValue(c.infoProvider.response)
if response == "" {
c.log.Debug("captcha:Validate no captcha response found in request")
return false, nil
@@ -139,7 +135,7 @@ func (c *Client) Validate(r *http.Request) (bool, error) {
var body = url.Values{}
body.Add("secret", c.secretKey)
body.Add("response", response)
res, err := c.httpClient.PostForm(captcha[c.provider].validate, body)
res, err := c.httpClient.PostForm(c.infoProvider.validate, body)
if err != nil {
return false, err
}

View File

@@ -34,7 +34,7 @@ const (
HcaptchaProvider = "hcaptcha"
RecaptchaProvider = "recaptcha"
TurnstileProvider = "turnstile"
WicketkeeperProvider = "wicketkeeper"
CustomProvider = "custom"
)
// Config the plugin configuration.
@@ -85,6 +85,10 @@ type Config struct {
BanHTMLFilePath string `json:"banHtmlFilePath,omitempty"`
CaptchaHTMLFilePath string `json:"captchaHtmlFilePath,omitempty"`
CaptchaProvider string `json:"captchaProvider,omitempty"`
CaptchaCustomJsURL string `json:"captchaCustomJsUrl,omitempty"`
CaptchaCustomValidateURL string `json:"captchaCustomValidateURL,omitempty"`
CaptchaCustomKey string `json:"captchaCustomKey,omitempty"`
CaptchaCustomResponse string `json:"captchaCustomResponse,omitempty"`
CaptchaSiteKey string `json:"captchaSiteKey,omitempty"`
CaptchaSiteKeyFile string `json:"captchaSiteKeyFile,omitempty"`
CaptchaSecretKey string `json:"captchaSecretKey,omitempty"`
@@ -126,6 +130,10 @@ func New() *Config {
RemediationStatusCode: http.StatusForbidden,
HTTPTimeoutSeconds: 10,
CaptchaProvider: "",
CaptchaCustomJsURL: "",
CaptchaCustomValidateURL: "",
CaptchaCustomKey: "",
CaptchaCustomResponse: "",
CaptchaSiteKey: "",
CaptchaSecretKey: "",
CaptchaGracePeriodSeconds: 1800,
@@ -196,6 +204,10 @@ func ValidateParams(config *Config) error {
if err := validateParamsRequired(config); err != nil {
return err
}
if err := validateCaptcha(config); err != nil {
return err
}
if err := validateParamsIPs(config.ForwardedHeadersTrustedIPs, "ForwardedHeadersTrustedIPs"); err != nil {
return err
@@ -332,6 +344,23 @@ func validateParamsIPs(listIP []string, key string) error {
return nil
}
func validateCaptcha(config *Config) error {
if !contains([]string{"", HcaptchaProvider, RecaptchaProvider, TurnstileProvider, CustomProvider}, config.CaptchaProvider) {
return fmt.Errorf("CaptchaProvider: must be one of '%s', '%s', '%s' or '%s'", HcaptchaProvider, RecaptchaProvider, TurnstileProvider, CustomProvider)
}
if config.CaptchaProvider == CustomProvider {
if config.CaptchaCustomKey == "" || config.CaptchaCustomResponse == "" || config.CaptchaCustomValidateURL == "" || config.CaptchaCustomJsURL == "" {
return fmt.Errorf(
"CaptchaProvider: provider is custom, captchaCustom variables must be filled: CaptchaCustomKey:%s, CaptchaCustomResponse:%s, CaptchaCustomValidateURL:%s, CaptchaCustomJsURL:%s",
config.CaptchaCustomKey ,
config.CaptchaCustomResponse,
config.CaptchaCustomValidateURL,
config.CaptchaCustomJsURL,
)
}
}
}
func validateParamsRequired(config *Config) error {
requiredStrings := map[string]string{
"CrowdsecLapiScheme": config.CrowdsecLapiScheme,
@@ -340,7 +369,7 @@ func validateParamsRequired(config *Config) error {
}
for key, val := range requiredStrings {
if len(val) == 0 {
return fmt.Errorf("%v: cannot be empty", key)
return errors.New(key + ": cannot be empty")
}
}
requiredInt0 := map[string]int64{
@@ -349,7 +378,7 @@ func validateParamsRequired(config *Config) error {
}
for key, val := range requiredInt0 {
if val < 0 {
return fmt.Errorf("%v: cannot be less than 0", key)
return errors.New(key + ": cannot be less than 0", )
}
}
requiredInt1 := map[string]int64{
@@ -360,7 +389,7 @@ func validateParamsRequired(config *Config) error {
}
for key, val := range requiredInt1 {
if val < 1 {
return fmt.Errorf("%v: cannot be less than 1", key)
return errors.New(key + ": cannot be less than 1", )
}
}
if config.UpdateMaxFailure < -1 {
@@ -379,9 +408,6 @@ func validateParamsRequired(config *Config) error {
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
return errors.New("CrowdsecLapiScheme: must be one of 'http' or 'https'")
}
if !contains([]string{"", HcaptchaProvider, RecaptchaProvider, TurnstileProvider, WicketkeeperProvider}, config.CaptchaProvider) {
return errors.New("CaptchaProvider: must be one of 'hcaptcha', 'recaptcha', 'turnstile' or 'wicketkeeper'")
}
return nil
}