167 feature update to go 122 (#168)

* ⬆️ Upgrade golang version

* 🚨 Optimize Lint for strings

* 🔒️ Add allow list of packages

* 🚨 Fix final lint

* 👷 Update ci

* 🍱 upgrade dependencies

* 🍱 fix comment

---------

Co-authored-by: Max Lerebourg <maxlerebourg@gmail.com>
This commit is contained in:
mathieuHa
2024-05-18 13:20:14 +02:00
committed by GitHub
parent 70ad0365f0
commit 6187a722ca
13 changed files with 76 additions and 100 deletions

View File

@@ -1,47 +0,0 @@
name: Go Matrix
on: [push, pull_request]
jobs:
cross:
name: Go
runs-on: ${{ matrix.os }}
env:
CGO_ENABLED: 0
strategy:
matrix:
go-version: [ 1.19, 1.x ]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v2
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
# * Build cache (Mac)
# * Build cache (Windows)
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-
- name: Test
run: go test -v -cover ./...

View File

@@ -12,9 +12,9 @@ jobs:
name: Main Process
runs-on: ubuntu-latest
env:
GO_VERSION: 1.19
GOLANGCI_LINT_VERSION: v1.50.0
YAEGI_VERSION: v0.14.2
GO_VERSION: 1.22
GOLANGCI_LINT_VERSION: v1.57.2
YAEGI_VERSION: v0.16.1
CGO_ENABLED: 0
defaults:
run:

View File

@@ -1,15 +1,11 @@
run:
timeout: 3m
skip-files: []
skip-dirs: []
linters-settings:
govet:
enable-all: true
disable:
- fieldalignment
golint:
min-confidence: 0
gocyclo:
min-complexity: 15
goconst:
@@ -25,6 +21,31 @@ linters-settings:
- FIXME
gofumpt:
extra-rules: true
depguard:
rules:
Main:
files:
- $all
- "!$test"
allow:
- $gostd
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/captcha
- github.com/leprosus/golang-ttl-map
- github.com/maxlerebourg/simpleredis
Test:
files:
- $test
allow:
- $gostd
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/captcha
linters:
enable-all: true
@@ -67,7 +88,6 @@ linters:
issues:
exclude-use-default: false
max-per-linter: 0
max-same-issues: 0
exclude:
- "G402: TLS InsecureSkipVerify may be true."

View File

@@ -7,6 +7,7 @@ import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -85,11 +86,11 @@ type Bouncer struct {
}
// New creates the crowdsec bouncer plugin.
func New(ctx context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
func New(_ context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
log := logger.New(config.LogLevel)
err := configuration.ValidateParams(config)
if err != nil {
log.Error(fmt.Sprintf("New:validateParams %s", err.Error()))
log.Error("New:validateParams " + err.Error())
return nil, err
}
@@ -112,12 +113,12 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
crowdsecHeader = crowdsecLapiHeader
tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log)
if err != nil {
log.Error(fmt.Sprintf("New:getTLSConfigCrowdsec fail to get tlsConfig %s", err.Error()))
log.Error("New:getTLSConfigCrowdsec fail to get tlsConfig " + err.Error())
return nil, err
}
apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey")
if errAPIKey != nil && len(tlsConfig.Certificates) == 0 {
log.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", errAPIKey.Error()))
log.Error("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup " + errAPIKey.Error())
return nil, err
}
config.CrowdsecLapiKey = apiKey
@@ -129,7 +130,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
banTemplate, _ := configuration.GetHTMLTemplate(config.BanHTMLFilePath)
err = banTemplate.Execute(&buf, nil)
if err != nil {
log.Error(fmt.Sprintf("New:banTemplate is bad formatted %s", err.Error()))
log.Error("New:banTemplate is bad formatted " + err.Error())
return nil, err
}
banTemplateString = buf.String()
@@ -209,7 +210,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
if (config.CrowdsecMode == configuration.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
if config.CrowdsecMode == configuration.AloneMode {
if err := getToken(bouncer); err != nil {
bouncer.log.Error(fmt.Sprintf("New:getToken %s", err.Error()))
bouncer.log.Error("New:getToken " + err.Error())
return nil, err
}
}
@@ -219,7 +220,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
handleStreamTicker(bouncer)
})
}
bouncer.log.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode))
bouncer.log.Debug("New initialized mode:" + config.CrowdsecMode)
return bouncer, nil
}
@@ -443,7 +444,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
case "captcha":
value = cache.CaptchaValue
default:
bouncer.log.Debug(fmt.Sprintf("handleStreamCache:unknownType %s", decision.Type))
bouncer.log.Debug("handleStreamCache:unknownType " + decision.Type)
}
if isLiveMode {
durationSecond := int64(duration.Seconds())
@@ -452,7 +453,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
}
bouncer.cacheClient.Set(remoteIP, value, durationSecond)
}
return value, fmt.Errorf("handleNoStreamCache:banned")
return value, errors.New("handleNoStreamCache:banned")
}
func getToken(bouncer *Bouncer) error {
@@ -517,7 +518,7 @@ func handleStreamCache(bouncer *Bouncer) error {
case "captcha":
value = cache.CaptchaValue
default:
bouncer.log.Debug(fmt.Sprintf("handleStreamCache:unknownType %s", decision.Type))
bouncer.log.Debug("handleStreamCache:unknownType " + decision.Type)
}
bouncer.cacheClient.Set(decision.Value, value, int64(duration.Seconds()))
}
@@ -549,7 +550,7 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
}
defer func() {
if err = res.Body.Close(); err != nil {
bouncer.log.Error(fmt.Sprintf("crowdsecQuery:closeBody %s", err.Error()))
bouncer.log.Error("crowdsecQuery:closeBody " + err.Error())
}
}()
if res.StatusCode == http.StatusUnauthorized && bouncer.crowdsecMode == configuration.AloneMode {
@@ -605,7 +606,7 @@ func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error {
}
defer func() {
if err = res.Body.Close(); err != nil {
bouncer.log.Error(fmt.Sprintf("appsecQuery:closeBody %s", err.Error()))
bouncer.log.Error("appsecQuery:closeBody " + err.Error())
}
}()
if res.StatusCode == http.StatusInternalServerError {

View File

@@ -18,7 +18,7 @@ func TestServeHTTP(t *testing.T) {
cfg.CrowdsecLapiKey = "test"
ctx := context.Background()
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})
handler, err := New(ctx, next, cfg, "demo-plugin")
if err != nil {
@@ -93,7 +93,7 @@ func TestBouncer_ServeHTTP(t *testing.T) {
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(_ *testing.T) {
bouncer := &Bouncer{
next: tt.fields.next,
name: tt.fields.name,

4
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
go 1.19
go 1.22
require (
github.com/leprosus/golang-ttl-map v1.1.7
github.com/maxlerebourg/simpleredis v1.0.9
github.com/maxlerebourg/simpleredis v1.0.11
)

4
go.sum
View File

@@ -1,4 +1,4 @@
github.com/leprosus/golang-ttl-map v1.1.7 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM=
github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0=
github.com/maxlerebourg/simpleredis v1.0.9 h1:aj1hKaYPeOVE4Ksu3TV/zsreUDDWOpKXBAvoFysiqII=
github.com/maxlerebourg/simpleredis v1.0.9/go.mod h1:/DH8zOK6kDskSqoX/m5CJJdNGfkIQZd/ERBJgytDDSk=
github.com/maxlerebourg/simpleredis v1.0.11 h1:B33TUeIrHtJH2/Qj2bRdU+UZ1BvZwFyP55JWMxHirWg=
github.com/maxlerebourg/simpleredis v1.0.11/go.mod h1:lT4LX02SOsE9PxUcSrz1QW5ZnO86gPbaiYBxmtcXEls=

9
pkg/cache/cache.go vendored
View File

@@ -3,6 +3,7 @@
package cache
import (
"errors"
"fmt"
ttl_map "github.com/leprosus/golang-ttl-map"
@@ -38,7 +39,7 @@ func (localCache) get(key string) (string, error) {
if isCached && isValid && len(valueString) > 0 {
return valueString, nil
}
return "", fmt.Errorf(CacheMiss)
return "", errors.New(CacheMiss)
}
func (localCache) set(key, value string, duration int64) {
@@ -60,20 +61,20 @@ func (redisCache) get(key string) (string, error) {
return valueString, nil
}
if err.Error() == simpleredis.RedisMiss {
return "", fmt.Errorf(CacheMiss)
return "", errors.New(CacheMiss)
}
return "", err
}
func (rc redisCache) set(key, value string, duration int64) {
if err := redis.Set(key, []byte(value), duration); err != nil {
rc.log.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error()))
rc.log.Error("cache:setDecisionRedisCache" + err.Error())
}
}
func (rc redisCache) delete(key string) {
if err := redis.Del(key); err != nil {
rc.log.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error()))
rc.log.Error("cache:deleteDecisionRedisCache " + err.Error())
}
}

View File

@@ -76,13 +76,13 @@ func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *htt
func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP string) {
valid, err := c.Validate(r)
if err != nil {
c.log.Debug(fmt.Sprintf("captcha:ServeHTTP:validate %s", err.Error()))
c.log.Info("captcha:ServeHTTP:validate " + err.Error())
rw.WriteHeader(http.StatusBadRequest)
return
}
if valid {
c.log.Debug("captcha:ServeHTTP captcha:valid")
c.cacheClient.Set(fmt.Sprintf("%s_captcha", remoteIP), cache.CaptchaDoneValue, c.gracePeriodSeconds)
c.cacheClient.Set(remoteIP+"_captcha", cache.CaptchaDoneValue, c.gracePeriodSeconds)
http.Redirect(rw, r, r.URL.String(), http.StatusFound)
return
}
@@ -94,13 +94,13 @@ func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP str
"FrontendKey": captcha[c.provider].key,
})
if err != nil {
c.log.Info(fmt.Sprintf("captcha:ServeHTTP captchaTemplateServe %s", err.Error()))
c.log.Info("captcha:ServeHTTP captchaTemplateServe " + err.Error())
}
}
// Check Verify if the captcha is already done.
func (c *Client) Check(remoteIP string) bool {
value, _ := c.cacheClient.Get(fmt.Sprintf("%s_captcha", remoteIP))
value, _ := c.cacheClient.Get(remoteIP + "_captcha")
passed := value == cache.CaptchaDoneValue
c.log.Debug(fmt.Sprintf("captcha:Check ip:%s pass:%v", remoteIP, passed))
return passed
@@ -113,10 +113,10 @@ type responseProvider struct {
// Validate Verify the captcha from provider API.
func (c *Client) Validate(r *http.Request) (bool, error) {
if r.Method != http.MethodPost {
c.log.Debug(fmt.Sprintf("captcha:Validate invalid method: %s", r.Method))
c.log.Debug("captcha:Validate invalid method: " + r.Method)
return false, nil
}
var response = r.FormValue(fmt.Sprintf("%s-response", captcha[c.provider].key))
var response = r.FormValue(captcha[c.provider].key + "-response")
if response == "" {
c.log.Debug("captcha:Validate no captcha response found in request")
return false, nil
@@ -130,7 +130,7 @@ func (c *Client) Validate(r *http.Request) (bool, error) {
}
defer func() {
if err = res.Body.Close(); err != nil {
c.log.Error(fmt.Sprintf("captcha:Validate %s", err.Error()))
c.log.Error("captcha:Validate " + err.Error())
}
}()
if !strings.Contains(res.Header.Get("content-type"), "application/json") {

View File

@@ -4,6 +4,7 @@ package configuration
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"html/template"
"net/http"
@@ -124,7 +125,7 @@ func New() *Config {
func GetVariable(config *Config, key string) (string, error) {
value := ""
object := reflect.Indirect(reflect.ValueOf(config))
field := object.FieldByName(fmt.Sprintf("%sFile", key))
field := object.FieldByName(key + "File")
// Here linter say you should simplify this code, but lets not, performance is important not clarity and complexity
fp := field.String()
if fp != "" {
@@ -151,7 +152,7 @@ func GetVariable(config *Config, key string) (string, error) {
func GetHTMLTemplate(path string) (*template.Template, error) {
var err error
if path == "" {
return nil, fmt.Errorf("no html template provided")
return nil, errors.New("no html template provided")
}
//nolint:gosec
b, err := os.ReadFile(path)
@@ -234,7 +235,7 @@ func ValidateParams(config *Config) error {
}
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be all empty")
return errors.New("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be all empty")
} else if lapiKey != "" && (certBouncer == "" || certBouncerKey == "") {
lapiKey = strings.TrimSpace(lapiKey)
if err = validateParamsAPIKey(lapiKey); err != nil {
@@ -267,7 +268,7 @@ func validateURL(variable, scheme, host string) error {
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
func validateParamsAPIKey(lapiKey string) error {
reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~=/]*$")
if !reg.Match([]byte(lapiKey)) {
if !reg.MatchString(lapiKey) {
return fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String())
}
return nil
@@ -279,12 +280,12 @@ func validateParamsTLS(config *Config) error {
return err
}
if certAuth == "" {
return fmt.Errorf("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
return errors.New("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
}
tlsConfig := new(tls.Config)
tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) {
return fmt.Errorf("failed parsing pem file")
return errors.New("failed parsing pem file")
}
return nil
}
@@ -321,17 +322,17 @@ func validateParamsRequired(config *Config) error {
}
}
if config.UpdateMaxFailure < -1 {
return fmt.Errorf("UpdateMaxFailure: cannot be less than -1")
return errors.New("UpdateMaxFailure: cannot be less than -1")
}
if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode, AppsecMode}, config.CrowdsecMode) {
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
return errors.New("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
}
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
return errors.New("CrowdsecLapiScheme: must be one of 'http' or 'https'")
}
if !contains([]string{"", HcaptchaProvider, RecaptchaProvider, TurnstileProvider}, config.CaptchaProvider) {
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'hcaptcha', 'recaptcha' or 'turnstile'")
return errors.New("CrowdsecLapiScheme: must be one of 'hcaptcha', 'recaptcha' or 'turnstile'")
}
return nil
}
@@ -360,7 +361,7 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error)
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuthority)) {
// here we return because if CrowdsecLapiTLSInsecureVerify is false
// and CA not load, we can't communicate with https
return nil, fmt.Errorf("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled")
return nil, errors.New("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled")
}
log.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority CA added successfully")
}

View File

@@ -3,6 +3,7 @@
package ip
import (
"errors"
"fmt"
"net"
"net/http"
@@ -45,7 +46,7 @@ func NewChecker(log *logger.Log, trustedIPs []string) (*Checker, error) {
// Contains checks if provided address is in the trusted IPs.
func (ip *Checker) Contains(addr string) (bool, error) {
if len(addr) == 0 {
return false, fmt.Errorf("Contains:noAddress")
return false, errors.New("Contains:noAddress")
}
ipAddr, err := parseIP(addr)

View File

@@ -18,7 +18,7 @@ const (
RedisMiss = "redis:miss"
RedisTimeout = "redis:timeout"
RedisNoAuth = "redis:noauth"
RedisIssue = "redis:issue?"
RedisIssue = "redis:issue?"
)
// A redisCmd is used to communicate with redis at low level using commands.
@@ -122,7 +122,6 @@ func (sr *SimpleRedis) askRedis(cmd redisCmd, channel chan redisCmd) redisCmd {
}
read, _ = reader.ReadLineBytes()
return redisCmd{Data: read}
}
}
}

4
vendor/modules.txt vendored
View File

@@ -1,6 +1,6 @@
# github.com/leprosus/golang-ttl-map v1.1.7
## explicit; go 1.15
github.com/leprosus/golang-ttl-map
# github.com/maxlerebourg/simpleredis v1.0.9
## explicit; go 1.19
# github.com/maxlerebourg/simpleredis v1.0.11
## explicit; go 1.22
github.com/maxlerebourg/simpleredis