feat: add golangci linter rules

This commit is contained in:
drev74
2025-10-11 22:36:20 +03:00
parent 8d5af6be5f
commit 27abae69ea
16 changed files with 171 additions and 44 deletions

122
.golangci.yml Normal file
View File

@@ -0,0 +1,122 @@
version: "2"
run:
issues-exit-code: 1
tests: false
build-tags:
- nobadger
- nomysql
- nopgx
output:
formats:
text:
path: stdout
print-linter-name: true
print-issued-lines: true
linters:
default: none
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errcheck
- errname
- exhaustive
- gosec
- govet
- importas
- ineffassign
- misspell
- prealloc
- promlinter
- sloglint
- sqlclosecheck
- staticcheck
- testableexamples
- testifylint
- tparallel
- unconvert
- unused
- wastedassign
- whitespace
- zerologlint
settings:
staticcheck:
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
errcheck:
exclude-functions:
- fmt.*
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
exhaustive:
ignore-enum-types: reflect.Kind|svc.Cmd
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- gosec
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
- linters:
- gosec
text: G107 # we aren't calling unknown URL
- linters:
- gosec
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
- linters:
- gosec
text: G204 # we're shelling out to known commands, not relying on user-defined input.
- linters:
- gosec
# the choice of weakrand is deliberate, hence the named import "weakrand"
path: modules/caddyhttp/reverseproxy/selectionpolicies.go
text: G404
- linters:
- gosec
path: modules/caddyhttp/reverseproxy/streaming.go
text: G404
- linters:
- dupl
path: modules/logging/filters.go
- linters:
- dupl
path: modules/caddyhttp/matchers.go
- linters:
- dupl
path: modules/caddyhttp/vars.go
- linters:
- errcheck
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@@ -25,16 +25,16 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/caddyserver/caddy/v2" "github.com/fsnotify/fsnotify"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
trie "github.com/phemmer/go-iptrie" trie "github.com/phemmer/go-iptrie"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/fsnotify/fsnotify" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// ==================== Constants and Globals ==================== // ==================== Constants and Globals ====================
@@ -120,7 +120,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
fileCfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder fileCfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
fileEncoder := zapcore.NewJSONEncoder(fileCfg.EncoderConfig) fileEncoder := zapcore.NewJSONEncoder(fileCfg.EncoderConfig)
fileSync, err := os.OpenFile(m.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) fileSync, err := os.OpenFile(m.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil { if err != nil {
m.logger.Warn("Failed to open log file, logging only to console", zap.String("path", m.LogFilePath), zap.Error(err)) m.logger.Warn("Failed to open log file, logging only to console", zap.String("path", m.LogFilePath), zap.Error(err))
m.logger = zap.New(zapcore.NewCore(consoleEncoder, consoleSync, logLevel)) m.logger = zap.New(zapcore.NewCore(consoleEncoder, consoleSync, logLevel))

View File

@@ -2,15 +2,14 @@ package caddywaf
import ( import (
"context" "context"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"testing" "testing"
"github.com/caddyserver/caddy/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/caddyserver/caddy/v2"
) )
func TestMiddleware_Provision(t *testing.T) { func TestMiddleware_Provision(t *testing.T) {

View File

@@ -7,8 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
) )
// ConfigLoader structure to encapsulate loading and parsing logic // ConfigLoader structure to encapsulate loading and parsing logic

View File

@@ -2,15 +2,14 @@
package caddywaf package caddywaf
import ( import (
"path/filepath"
"os" "os"
"path/filepath"
"testing" "testing"
"time" "time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
) )
func TestNewConfigLoader(t *testing.T) { func TestNewConfigLoader(t *testing.T) {

16
doc.go
View File

@@ -15,14 +15,16 @@
// - Dynamic configuration reloading // - Dynamic configuration reloading
// //
// Installation: // Installation:
// xcaddy build --with github.com/fabriziosalmi/caddy-waf //
// xcaddy build --with github.com/fabriziosalmi/caddy-waf
// //
// Basic usage in Caddyfile: // Basic usage in Caddyfile:
// waf { //
// rule_file rules.json // waf {
// ip_blacklist_file blacklist.txt // rule_file rules.json
// metrics_endpoint /waf_metrics // ip_blacklist_file blacklist.txt
// } // metrics_endpoint /waf_metrics
// }
// //
// For complete documentation, see: https://github.com/fabriziosalmi/caddy-waf // For complete documentation, see: https://github.com/fabriziosalmi/caddy-waf
package caddywaf package caddywaf

View File

@@ -101,7 +101,6 @@ func (gh *GeoIPHandler) GetCountryCode(remoteAddr string, geoIP *maxminddb.Reade
} }
func (gh *GeoIPHandler) isCountryInListWithCache(ip string, parsedIP net.IP, countryList []string, geoIP *maxminddb.Reader) (bool, error) { func (gh *GeoIPHandler) isCountryInListWithCache(ip string, parsedIP net.IP, countryList []string, geoIP *maxminddb.Reader) (bool, error) {
// Check cache first // Check cache first
if gh.geoIPCache != nil { if gh.geoIPCache != nil {
gh.geoIPCacheMutex.RLock() gh.geoIPCacheMutex.RLock()
@@ -127,7 +126,6 @@ func (gh *GeoIPHandler) isCountryInListWithCache(ip string, parsedIP net.IP, cou
} }
func (gh *GeoIPHandler) getCountryCodeWithCache(ip string, parsedIP net.IP, geoIP *maxminddb.Reader) string { func (gh *GeoIPHandler) getCountryCodeWithCache(ip string, parsedIP net.IP, geoIP *maxminddb.Reader) string {
// Check cache first for GetCountryCode as well for consistency and potential perf gain // Check cache first for GetCountryCode as well for consistency and potential perf gain
if gh.geoIPCache != nil { if gh.geoIPCache != nil {
gh.geoIPCacheMutex.RLock() gh.geoIPCacheMutex.RLock()

View File

@@ -5,14 +5,17 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/google/uuid" "github.com/google/uuid"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
type ContextKeyLogId string type (
type ContextKeyRule string ContextKeyLogId string
ContextKeyRule string
)
// ServeHTTP implements caddyhttp.Handler. // ServeHTTP implements caddyhttp.Handler.
// handler.go // handler.go
@@ -154,7 +157,6 @@ func (m *Middleware) handleResponseBodyPhase(recorder *responseRecorder, r *http
if m.processRuleMatch(recorder, r, &rule, body, state) { if m.processRuleMatch(recorder, r, &rule, body, state) {
return return
} }
} }
} }
} }
@@ -274,7 +276,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
} }
if phase == 1 { if phase == 1 {
m.logger.Debug("Checking for IP blacklisting", zap.String("remote_addr", r.RemoteAddr)) //Added log for checking before to isIPBlacklisted call m.logger.Debug("Checking for IP blacklisting", zap.String("remote_addr", r.RemoteAddr)) // Added log for checking before to isIPBlacklisted call
xForwardedFor := r.Header.Get("X-Forwarded-For") xForwardedFor := r.Header.Get("X-Forwarded-For")
if xForwardedFor != "" { if xForwardedFor != "" {
ips := strings.Split(xForwardedFor, ",") ips := strings.Split(xForwardedFor, ",")
@@ -293,7 +295,6 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
} }
} else { } else {
m.logger.Debug("X-Forwarded-For header present but empty or invalid") m.logger.Debug("X-Forwarded-For header present but empty or invalid")
} }
} else { } else {

View File

@@ -12,10 +12,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
trie "github.com/phemmer/go-iptrie" trie "github.com/phemmer/go-iptrie"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) { func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
@@ -496,6 +497,7 @@ func TestBlockedRequestPhase1_HeaderRegex_EmptyHeader(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200") assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
assert.Empty(t, w.Body.String(), "Response body should be empty") assert.Empty(t, w.Body.String(), "Response body should be empty")
} }
func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) { func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
logger := zap.NewNop() logger := zap.NewNop()
middleware := &Middleware{ middleware := &Middleware{
@@ -546,7 +548,6 @@ func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
assert.False(t, state.Blocked, "Request should not be blocked because header is missing") assert.False(t, state.Blocked, "Request should not be blocked because header is missing")
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200") assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
assert.Empty(t, w.Body.String(), "Response body should be empty") assert.Empty(t, w.Body.String(), "Response body should be empty")
} }
func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) { func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) {

View File

@@ -105,7 +105,6 @@ func (m *Middleware) logRequest(level zapcore.Level, msg string, r *http.Request
) )
m.logger.Log(level, msg, allFields...) m.logger.Log(level, msg, allFields...)
} }
} }
// redactSensitiveFields redacts sensitive information in the log fields. // redactSensitiveFields redacts sensitive information in the log fields.

View File

@@ -11,11 +11,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
trie "github.com/phemmer/go-iptrie" trie "github.com/phemmer/go-iptrie"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
) )
func TestExtractValue(t *testing.T) { func TestExtractValue(t *testing.T) {

View File

@@ -62,7 +62,6 @@ func NewResponseRecorder(w http.ResponseWriter) *responseRecorder {
func (r *responseRecorder) WriteHeader(statusCode int) { func (r *responseRecorder) WriteHeader(statusCode int) {
r.statusCode = statusCode r.statusCode = statusCode
r.ResponseWriter.WriteHeader(statusCode) r.ResponseWriter.WriteHeader(statusCode)
} }
// Header returns the response headers. // Header returns the response headers.

View File

@@ -187,7 +187,7 @@ func TestLoadRules(t *testing.T) {
"action": "block" "action": "block"
} }
]` ]`
os.WriteFile(validRuleFile, []byte(validRules), 0644) os.WriteFile(validRuleFile, []byte(validRules), 0o644)
invalidRuleFile := filepath.Join(tmpDir, "invalid_rules.json") invalidRuleFile := filepath.Join(tmpDir, "invalid_rules.json")
invalidRules := `[ invalidRules := `[
@@ -199,7 +199,7 @@ func TestLoadRules(t *testing.T) {
"score": -1 "score": -1
} }
]` ]`
os.WriteFile(invalidRuleFile, []byte(invalidRules), 0644) os.WriteFile(invalidRuleFile, []byte(invalidRules), 0o644)
tests := []struct { tests := []struct {
name string name string

5
tor.go
View File

@@ -9,8 +9,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/caddyserver/caddy/v2"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
) )
var torExitNodeURL = "https://check.torproject.org/torbulkexitlist" var torExitNodeURL = "https://check.torproject.org/torbulkexitlist"
@@ -134,7 +135,7 @@ func (t *TorConfig) readExistingBlacklist() ([]string, error) {
// writeBlacklist writes the updated IP blacklist to the file. // writeBlacklist writes the updated IP blacklist to the file.
func (t *TorConfig) writeBlacklist(ips []string) error { func (t *TorConfig) writeBlacklist(ips []string) error {
data := strings.Join(ips, "\n") data := strings.Join(ips, "\n")
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0644) err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o644)
if err != nil { if err != nil {
return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
} }

View File

@@ -6,9 +6,10 @@ import (
"os" "os"
"testing" "testing"
"github.com/caddyserver/caddy/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
) )
func TestTorConfig_Provision(t *testing.T) { func TestTorConfig_Provision(t *testing.T) {

View File

@@ -5,13 +5,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
trie "github.com/phemmer/go-iptrie" trie "github.com/phemmer/go-iptrie"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// Package caddywaf is a Caddy module providing web application firewall functionality. // Package caddywaf is a Caddy module providing web application firewall functionality.
@@ -27,8 +28,10 @@ var (
) )
// Define custom types for rule hits // Define custom types for rule hits
type RuleID string type (
type HitCount int RuleID string
HitCount int
)
// RuleCache caches compiled regex patterns for rules. // RuleCache caches compiled regex patterns for rules.
type RuleCache struct { type RuleCache struct {