Add parameter to configure the ban page Content-Type response header (#325)

* Add parameter to configure Ban Response Content-Type

* Add testing for new BanResponseContentType parameter

* Ensure there is a fallback to default Content-Type is user provided empty value

* Set Content-Type even if banTemplate is nil

* Add more edge cases for testing ban response Content-Type

* Add CR/LF validation for BanResponseContentType

* Add CaptchaResponseContentType to allow separate Content-Type configuration for captcha responses

* Add testing for new CaptchaResponseContentType

* Update README

* Split nil and CR/LF response Content-Type value validation into separate function

* Throw error instead of setting the default in case of empty parameter declaration

* Update testing accordingly

*  remove HTML from var name, add tests and infer content type from filePath

* 🍱 fix lint ?

* 🍱 fix lint

* 🍱 fix lint

* 🍱 fix lint + naming

* 🍱 fix lint

* 🍱 fuck lint

---------

Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
This commit is contained in:
omer
2026-06-28 22:30:48 +02:00
committed by GitHub
co-authored by maxlerebourg
parent f4dcd933c8
commit 7c73cb38dd
14 changed files with 168 additions and 63 deletions
+19 -14
View File
@@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
htmltemplate "html/template"
"io"
"log/slog"
"net/http"
@@ -110,7 +109,8 @@ type Bouncer struct {
crowdsecStreamRoute string
crowdsecHeader string
redisUnreachableBlock bool
banTemplate *htmltemplate.Template
banTemplate *template.Template
banTemplateContentType string
traceCustomHeader string
clientPoolStrategy *ip.PoolStrategy
serverPoolStrategy *ip.PoolStrategy
@@ -123,10 +123,18 @@ type Bouncer struct {
// New creates the crowdsec bouncer plugin.
//
//nolint:nestif,gocyclo,gocognit
//nolint:nestif,gocyclo,gocognit,funlen,maintidx
func New(_ context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
config.LogLevel = strings.ToUpper(config.LogLevel)
log := logger.NewWithFormat(config.LogLevel, config.LogFilePath, config.LogFormat)
if config.BanFilePath == "" && config.BanHTMLFilePath != "" {
config.BanFilePath = config.BanHTMLFilePath
}
if config.CaptchaHTMLFilePath != "" {
config.CaptchaFilePath = config.CaptchaHTMLFilePath
}
err := configuration.ValidateParams(config, log)
if err != nil {
log.Error("New:validateParams " + err.Error())
@@ -184,9 +192,10 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
}
}
var banTemplate *htmltemplate.Template
if config.BanHTMLFilePath != "" {
banTemplate, _ = configuration.GetHTMLTemplate(config.BanHTMLFilePath)
var banTemplate *template.Template
var banTemplateContentType string
if config.BanFilePath != "" {
banTemplate, banTemplateContentType, _ = configuration.GetTemplate(config.BanFilePath)
}
bouncer := &Bouncer{
@@ -219,6 +228,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
remediationStatusCode: config.RemediationStatusCode,
redisUnreachableBlock: config.RedisCacheUnreachableBlock,
banTemplate: banTemplate,
banTemplateContentType: banTemplateContentType,
traceCustomHeader: config.TraceHeadersCustomName,
crowdsecStreamRoute: crowdsecStreamRoute,
crowdsecHeader: crowdsecHeader,
@@ -276,7 +286,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
config.CaptchaSiteKey,
config.CaptchaSecretKey,
config.RemediationHeadersCustomName,
config.CaptchaHTMLFilePath,
config.CaptchaFilePath,
config.CaptchaGracePeriodSeconds,
)
if err != nil {
@@ -433,14 +443,9 @@ func (bouncer *Bouncer) handleBanServeHTTP(rw http.ResponseWriter, req *http.Req
if bouncer.remediationCustomHeader != "" {
rw.Header().Set(bouncer.remediationCustomHeader, "ban")
}
if bouncer.banTemplate == nil {
rw.WriteHeader(bouncer.remediationStatusCode)
return
}
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.Header().Set("Content-Type", bouncer.banTemplateContentType)
rw.WriteHeader(bouncer.remediationStatusCode)
if req.Method == http.MethodHead {
if bouncer.banTemplate == nil || req.Method == http.MethodHead {
return
}
templateData := map[string]string{