From 27abae69eae23e417a148688ecf2b2a256904d40 Mon Sep 17 00:00:00 2001 From: drev74 Date: Sat, 11 Oct 2025 22:36:20 +0300 Subject: [PATCH] feat: add golangci linter rules --- .golangci.yml | 122 +++++++++++++++++++++++++++++++++++++++++++++++ caddywaf.go | 12 ++--- caddywaf_test.go | 5 +- config.go | 3 +- config_test.go | 7 ++- doc.go | 16 ++++--- geoip.go | 2 - handler.go | 13 ++--- handler_test.go | 5 +- logging.go | 1 - request_test.go | 3 +- response.go | 1 - rules_test.go | 4 +- tor.go | 5 +- tor_test.go | 3 +- types.go | 13 +++-- 16 files changed, 171 insertions(+), 44 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4f45450 --- /dev/null +++ b/.golangci.yml @@ -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$ diff --git a/caddywaf.go b/caddywaf.go index 34af732..5fde7d5 100644 --- a/caddywaf.go +++ b/caddywaf.go @@ -25,16 +25,16 @@ import ( "strings" "sync" - "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" + "github.com/fsnotify/fsnotify" "github.com/oschwald/maxminddb-golang" trie "github.com/phemmer/go-iptrie" "go.uber.org/zap" "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 ==================== @@ -120,7 +120,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error { fileCfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 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 { 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)) diff --git a/caddywaf_test.go b/caddywaf_test.go index 5a18973..0294778 100644 --- a/caddywaf_test.go +++ b/caddywaf_test.go @@ -2,15 +2,14 @@ package caddywaf import ( "context" - "net/http" "net/http/httptest" "os" "testing" - "github.com/caddyserver/caddy/v2" - "github.com/stretchr/testify/assert" + + "github.com/caddyserver/caddy/v2" ) func TestMiddleware_Provision(t *testing.T) { diff --git a/config.go b/config.go index fdeb26b..9944df8 100644 --- a/config.go +++ b/config.go @@ -7,8 +7,9 @@ import ( "strings" "time" - "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) // ConfigLoader structure to encapsulate loading and parsing logic diff --git a/config_test.go b/config_test.go index 7b6c94b..fbe7911 100644 --- a/config_test.go +++ b/config_test.go @@ -2,15 +2,14 @@ package caddywaf import ( - "path/filepath" - "os" + "path/filepath" "testing" "time" - "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" - "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) func TestNewConfigLoader(t *testing.T) { diff --git a/doc.go b/doc.go index cfb1d27..637dda4 100644 --- a/doc.go +++ b/doc.go @@ -15,14 +15,16 @@ // - Dynamic configuration reloading // // Installation: -// xcaddy build --with github.com/fabriziosalmi/caddy-waf +// +// xcaddy build --with github.com/fabriziosalmi/caddy-waf // // Basic usage in Caddyfile: -// waf { -// rule_file rules.json -// ip_blacklist_file blacklist.txt -// metrics_endpoint /waf_metrics -// } +// +// waf { +// rule_file rules.json +// ip_blacklist_file blacklist.txt +// metrics_endpoint /waf_metrics +// } // // For complete documentation, see: https://github.com/fabriziosalmi/caddy-waf -package caddywaf \ No newline at end of file +package caddywaf diff --git a/geoip.go b/geoip.go index de04d01..e23745f 100644 --- a/geoip.go +++ b/geoip.go @@ -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) { - // Check cache first if gh.geoIPCache != nil { 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 { - // Check cache first for GetCountryCode as well for consistency and potential perf gain if gh.geoIPCache != nil { gh.geoIPCacheMutex.RLock() diff --git a/handler.go b/handler.go index 37baf40..3049a18 100644 --- a/handler.go +++ b/handler.go @@ -5,14 +5,17 @@ import ( "net/http" "strings" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/google/uuid" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) -type ContextKeyLogId string -type ContextKeyRule string +type ( + ContextKeyLogId string + ContextKeyRule string +) // ServeHTTP implements caddyhttp.Handler. // handler.go @@ -154,7 +157,6 @@ func (m *Middleware) handleResponseBodyPhase(recorder *responseRecorder, r *http if m.processRuleMatch(recorder, r, &rule, body, state) { return } - } } } @@ -274,7 +276,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i } 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") if xForwardedFor != "" { ips := strings.Split(xForwardedFor, ",") @@ -293,7 +295,6 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i } } else { m.logger.Debug("X-Forwarded-For header present but empty or invalid") - } } else { diff --git a/handler_test.go b/handler_test.go index eacda61..855e240 100644 --- a/handler_test.go +++ b/handler_test.go @@ -12,10 +12,11 @@ import ( "testing" "time" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" trie "github.com/phemmer/go-iptrie" "github.com/stretchr/testify/assert" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) 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.Empty(t, w.Body.String(), "Response body should be empty") } + func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) { logger := zap.NewNop() 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.Equal(t, http.StatusOK, w.Code, "Expected status code 200") assert.Empty(t, w.Body.String(), "Response body should be empty") - } func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) { diff --git a/logging.go b/logging.go index 3714441..707152b 100644 --- a/logging.go +++ b/logging.go @@ -105,7 +105,6 @@ func (m *Middleware) logRequest(level zapcore.Level, msg string, r *http.Request ) m.logger.Log(level, msg, allFields...) } - } // redactSensitiveFields redacts sensitive information in the log fields. diff --git a/request_test.go b/request_test.go index a572ef0..f06d0d3 100644 --- a/request_test.go +++ b/request_test.go @@ -11,11 +11,12 @@ import ( "testing" "time" - "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" trie "github.com/phemmer/go-iptrie" "github.com/stretchr/testify/assert" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) func TestExtractValue(t *testing.T) { diff --git a/response.go b/response.go index ca4c005..6c88f3b 100644 --- a/response.go +++ b/response.go @@ -62,7 +62,6 @@ func NewResponseRecorder(w http.ResponseWriter) *responseRecorder { func (r *responseRecorder) WriteHeader(statusCode int) { r.statusCode = statusCode r.ResponseWriter.WriteHeader(statusCode) - } // Header returns the response headers. diff --git a/rules_test.go b/rules_test.go index 74460ea..b218d7e 100644 --- a/rules_test.go +++ b/rules_test.go @@ -187,7 +187,7 @@ func TestLoadRules(t *testing.T) { "action": "block" } ]` - os.WriteFile(validRuleFile, []byte(validRules), 0644) + os.WriteFile(validRuleFile, []byte(validRules), 0o644) invalidRuleFile := filepath.Join(tmpDir, "invalid_rules.json") invalidRules := `[ @@ -199,7 +199,7 @@ func TestLoadRules(t *testing.T) { "score": -1 } ]` - os.WriteFile(invalidRuleFile, []byte(invalidRules), 0644) + os.WriteFile(invalidRuleFile, []byte(invalidRules), 0o644) tests := []struct { name string diff --git a/tor.go b/tor.go index 2d3a975..44a9c0e 100644 --- a/tor.go +++ b/tor.go @@ -9,8 +9,9 @@ import ( "strings" "time" - "github.com/caddyserver/caddy/v2" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" ) 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. func (t *TorConfig) writeBlacklist(ips []string) error { data := strings.Join(ips, "\n") - err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0644) + err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o644) if err != nil { return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename } diff --git a/tor_test.go b/tor_test.go index c1093f2..4312c4b 100644 --- a/tor_test.go +++ b/tor_test.go @@ -6,9 +6,10 @@ import ( "os" "testing" - "github.com/caddyserver/caddy/v2" "github.com/stretchr/testify/assert" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" ) func TestTorConfig_Provision(t *testing.T) { diff --git a/types.go b/types.go index 6b53844..c26c9bc 100644 --- a/types.go +++ b/types.go @@ -5,13 +5,14 @@ import ( "sync" "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" trie "github.com/phemmer/go-iptrie" "go.uber.org/zap" "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. @@ -27,8 +28,10 @@ var ( ) // Define custom types for rule hits -type RuleID string -type HitCount int +type ( + RuleID string + HitCount int +) // RuleCache caches compiled regex patterns for rules. type RuleCache struct {