mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2025-11-08 15:15:05 +01:00
✨ Anom config
This commit is contained in:
14
README.md
14
README.md
@@ -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
|
||||
|
||||
12
bouncer.go
12
bouncer.go
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user