fmt and feedback comments, no code changes
check / check (push) Successful in 4m12s

This commit is contained in:
sneak committed 2026-11-09 00:30:10 +00:00
1 parent f6a15deea3
commit b7059bfac8
3 files changed
+118 -37

No files matched your search

+81 -29
View File
@@ -397,18 +397,21 @@ var (
errNotPathPrefix = errors.New( errNotPathPrefix = errors.New(
"is not a path prefix starting with /, such as /assets/") "is not a path prefix starting with /, such as /assets/")
errNotTrapPath = errors.New( errNotTrapPath = errors.New(
"is not a path starting with / and without a ?, such as /wp-login.php") "is not a path starting with / and without a ?, such as /wp-login.php",
)
errNotWAFMode = errors.New("is not off, detect or block") errNotWAFMode = errors.New("is not off, detect or block")
errNotParanoiaLevel = errors.New("is not a paranoia level, from 1 to 4") errNotParanoiaLevel = errors.New("is not a paranoia level, from 1 to 4")
errNotRuleID = errors.New( errNotRuleID = errors.New(
"is not the id of a Core Rule Set rule, a whole number such as 942100") "is not the id of a Core Rule Set rule, a whole number such as 942100",
)
errSetupRuleID = errors.New( errSetupRuleID = errors.New(
"is from 900000 to 900999, the ids of the rules that set the Core Rule Set " + "is from 900000 to 900999, the ids of the rules that set the Core Rule Set " +
"up and of sww's own, which cannot be switched off") "up and of sww's own, which cannot be switched off")
errNotBoolean = errors.New("is not true or false") errNotBoolean = errors.New("is not true or false")
errNotLogRemoteURL = errors.New( errNotLogRemoteURL = errors.New(
"is not syslog+udp, syslog+tcp or syslog+tls with a host and a port, " + "is not syslog+udp, syslog+tcp or syslog+tls with a host and a port, " +
"and nothing more, such as syslog+tls://logs.example:6514") "and nothing more, such as syslog+tls://logs.example:6514",
)
errNoCertificate = errors.New("holds no PEM certificate") errNoCertificate = errors.New("holds no PEM certificate")
errNotFacility = errors.New("is not a syslog facility such as local0 or daemon") errNotFacility = errors.New("is not a syslog facility such as local0 or daemon")
errNotAppName = errors.New( errNotAppName = errors.New(
@@ -421,16 +424,21 @@ var (
"is not a header name followed by : and the header's value, " + "is not a header name followed by : and the header's value, " +
"such as Authorization:Bearer <token>") "such as Authorization:Bearer <token>")
errControlCharacter = errors.New( errControlCharacter = errors.New(
"holds a control character, such as the carriage return of a Windows line end") "holds a control character, such as the carriage return of a Windows line end",
)
errNotAlertEvent = errors.New( errNotAlertEvent = errors.New(
"is not ban, permanent_ban, waf_block, anomaly, reputation_hit, " + "is not ban, permanent_ban, waf_block, anomaly, reputation_hit, " +
"source_failure or file_error") "source_failure or file_error")
errNotNumberOrOff = errors.New("is not a whole number above zero, such as 60, or off") errNotNumberOrOff = errors.New(
"is not a whole number above zero, such as 60, or off",
)
errNotUTF8 = errors.New("is not valid UTF-8") errNotUTF8 = errors.New("is not valid UTF-8")
errNotASN = errors.New("is not an AS number such as AS64496") errNotASN = errors.New("is not an AS number such as AS64496")
errNotPercentItem = errors.New( errNotPercentItem = errors.New(
"is not a code, : and a percentage, such as AS64496:50 or cn:25") "is not a code, : and a percentage, such as AS64496:50 or cn:25")
errNotPercent = errors.New("is not a percentage, a whole number from 0 to 100") errNotPercent = errors.New(
"is not a percentage, a whole number from 0 to 100",
)
errListedTwice = errors.New("is listed twice") errListedTwice = errors.New("is listed twice")
errNotListURL = errors.New( errNotListURL = errors.New(
"is not an http or https URL without a user or a fragment, " + "is not an http or https URL without a user or a fragment, " +
@@ -439,11 +447,17 @@ var (
errNotAnHourOrMore = errors.New("is not a duration of 1h or more, such as 24h") errNotAnHourOrMore = errors.New("is not a duration of 1h or more, such as 24h")
errNotAction = errors.New( errNotAction = errors.New(
"is not deny, limit:<percent> such as limit:25, or log") "is not deny, limit:<percent> such as limit:25, or log")
errNotZone = errors.New("is not a DNS zone such as dnsbl.dronebl.org") errNotZone = errors.New(
errZoneTooLong = errors.New("is longer than 189 characters, too long for the " + "is not a DNS zone such as dnsbl.dronebl.org",
"names IPv6 clients are asked about by") )
errNotResolver = errors.New("is not an IP address with an optional port, " + errZoneTooLong = errors.New(
"such as 192.0.2.53 or [2001:db8::53]:5353") "is longer than 189 characters, too long for the " +
"names IPv6 clients are asked about by",
)
errNotResolver = errors.New(
"is not an IP address with an optional port, " +
"such as 192.0.2.53 or [2001:db8::53]:5353",
)
errNotLAPIURL = errors.New( errNotLAPIURL = errors.New(
"is not an http or https URL without a user or a fragment, " + "is not an http or https URL without a user or a fragment, " +
"such as http://172.17.0.1:8080") "such as http://172.17.0.1:8080")
@@ -460,7 +474,9 @@ var (
// setting that is set but invalid is an error that names it. // setting that is set but invalid is an error that names it.
// //
//nolint:funlen // one line for each setting, a list that grows with them //nolint:funlen // one line for each setting, a list that grows with them
func FromEnvironment(lookupEnv func(string) (string, bool)) (*Config, error) { func FromEnvironment(
lookupEnv func(string) (string, bool),
) (*Config, error) {
env := &environment{lookupEnv: lookupEnv} env := &environment{lookupEnv: lookupEnv}
cfg := &Config{ cfg := &Config{
ListenAddr: env.address("SWW_LISTEN_ADDR"), ListenAddr: env.address("SWW_LISTEN_ADDR"),
@@ -658,7 +674,10 @@ func (e *environment) lookup(name string) (string, bool) {
} }
if set { if set {
e.check(name, fmt.Errorf("is set, and so is %s; %w", fileName, errSetTwice)) e.check(
name,
fmt.Errorf("is set, and so is %s; %w", fileName, errSetTwice),
)
return value, set return value, set
} }
@@ -916,7 +935,10 @@ func (e *environment) zones(name string) []string {
logged[i] = MaskZoneKey(zone) logged[i] = MaskZoneKey(zone)
} }
e.settings = append(e.settings, slog.String(name, strings.Join(logged, ","))) e.settings = append(
e.settings,
slog.String(name, strings.Join(logged, ",")),
)
return zones return zones
} }
@@ -1058,7 +1080,9 @@ func (e *environment) headerNames(name, defaultValue string) []string {
// durationNotOff reads a setting that is a duration and, unlike a // durationNotOff reads a setting that is a duration and, unlike a
// timeout, cannot be off. // timeout, cannot be off.
func (e *environment) durationNotOff(name, defaultValue string) time.Duration { func (e *environment) durationNotOff(
name, defaultValue string,
) time.Duration {
duration, err := ParseDurationNotOff(e.value(name, defaultValue)) duration, err := ParseDurationNotOff(e.value(name, defaultValue))
e.check(name, err) e.check(name, err)
@@ -1257,7 +1281,10 @@ func (e *environment) instanceName() string {
// appName reads the setting that is the APP-NAME of the records the log // appName reads the setting that is the APP-NAME of the records the log
// lines are sent in, by default the instance name. Its value is checked // lines are sent in, by default the instance name. Its value is checked
// when it is set, and, while lines are sent, when it is the instance name. // when it is set, and, while lines are sent, when it is the instance name.
func (e *environment) appName(name, instanceName string, sending bool) string { func (e *environment) appName(
name, instanceName string,
sending bool,
) string {
value, set := e.lookup(name) value, set := e.lookup(name)
if !set { if !set {
value = instanceName value = instanceName
@@ -1280,7 +1307,10 @@ func (e *environment) appName(name, instanceName string, sending bool) string {
// checkInstanceNameForNtfy refuses an instance name that holds a control // checkInstanceNameForNtfy refuses an instance name that holds a control
// character while ntfySet, SWW_ALERT_NTFY_URL being set: ntfy is sent // character while ntfySet, SWW_ALERT_NTFY_URL being set: ntfy is sent
// the instance name in a header, which cannot hold one. // the instance name in a header, which cannot hold one.
func (e *environment) checkInstanceNameForNtfy(instanceName string, ntfySet bool) { func (e *environment) checkInstanceNameForNtfy(
instanceName string,
ntfySet bool,
) {
if ntfySet && strings.ContainsFunc(instanceName, unicode.IsControl) { if ntfySet && strings.ContainsFunc(instanceName, unicode.IsControl) {
e.check("SWW_INSTANCE_NAME", fmt.Errorf( e.check("SWW_INSTANCE_NAME", fmt.Errorf(
"%q %w, and is sent to ntfy in a header while SWW_ALERT_NTFY_URL is set", "%q %w, and is sent to ntfy in a header while SWW_ALERT_NTFY_URL is set",
@@ -1604,7 +1634,10 @@ func parseNamedNetblocks(value string) ([]anomaly.NamedNetblock, error) {
return nil, fmt.Errorf("%q %w", name, errListedTwice) return nil, fmt.Errorf("%q %w", name, errListedTwice)
} }
named = append(named, anomaly.NamedNetblock{Name: name, Netblock: netblock}) named = append(
named,
anomaly.NamedNetblock{Name: name, Netblock: netblock},
)
} }
return named, nil return named, nil
@@ -1843,8 +1876,11 @@ func parseHeaderNames(value string) ([]string, error) {
header := strings.ToLower(item) header := strings.ToLower(item)
switch header { switch header {
case "host": case "host":
return nil, fmt.Errorf("%q %w; the request's host is the field host", return nil, fmt.Errorf(
item, errHeaderTakenOut) "%q %w; the request's host is the field host",
item,
errHeaderTakenOut,
)
case "transfer-encoding": case "transfer-encoding":
return nil, fmt.Errorf("%q %w", item, errHeaderTakenOut) return nil, fmt.Errorf("%q %w", item, errHeaderTakenOut)
} }
@@ -1881,9 +1917,11 @@ func parseUpstreamURL(value string) (*url.URL, error) {
} }
onlySchemeAndHost := (upstream.Scheme == "http" || upstream.Scheme == "https") && onlySchemeAndHost := (upstream.Scheme == "http" || upstream.Scheme == "https") &&
upstream.Hostname() != "" && upstream.User == nil && upstream.Opaque == "" && upstream.Hostname() != "" && upstream.User == nil &&
upstream.Opaque == "" &&
(upstream.Path == "" || upstream.Path == "/") && (upstream.Path == "" || upstream.Path == "/") &&
upstream.RawQuery == "" && upstream.Fragment == "" upstream.RawQuery == "" &&
upstream.Fragment == ""
if !onlySchemeAndHost { if !onlySchemeAndHost {
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL) return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
} }
@@ -1907,7 +1945,11 @@ func parseLogRemoteURL(value string) (*url.URL, error) {
return nil, fmt.Errorf("%q %w", value, errNotLogRemoteURL) return nil, fmt.Errorf("%q %w", value, errNotLogRemoteURL)
} }
schemes := []string{remotelog.SchemeUDP, remotelog.SchemeTCP, remotelog.SchemeTLS} schemes := []string{
remotelog.SchemeUDP,
remotelog.SchemeTCP,
remotelog.SchemeTLS,
}
port, err := strconv.ParseUint(remote.Port(), 10, 16) port, err := strconv.ParseUint(remote.Port(), 10, 16)
onlySchemeHostAndPort := slices.Contains(schemes, remote.Scheme) && onlySchemeHostAndPort := slices.Contains(schemes, remote.Scheme) &&
@@ -1970,7 +2012,9 @@ func isHTTPURL(u *url.URL) bool {
return (u.Scheme == "http" || u.Scheme == "https") && u.Hostname() != "" && return (u.Scheme == "http" || u.Scheme == "https") && u.Hostname() != "" &&
(u.Port() == "" || (err == nil && port != 0)) && (u.Port() == "" || (err == nil && port != 0)) &&
u.User == nil && u.Opaque == "" && u.Fragment == "" u.User == nil &&
u.Opaque == "" &&
u.Fragment == ""
} }
// isListURL reports whether value is a URL a list can be fetched from, as // isListURL reports whether value is a URL a list can be fetched from, as
@@ -2037,9 +2081,12 @@ func parseZones(value string) ([]string, error) {
for i, zone := range zones { for i, zone := range zones {
shown := MaskZoneKey(zone) shown := MaskZoneKey(zone)
listedBefore := slices.ContainsFunc(zones[:i], func(earlier string) bool { listedBefore := slices.ContainsFunc(
return strings.EqualFold(MaskZoneKey(earlier), shown) zones[:i],
}) func(earlier string) bool {
return strings.EqualFold(MaskZoneKey(earlier), shown)
},
)
switch { switch {
case len(zone) > maxZoneLength: case len(zone) > maxZoneLength:
@@ -2126,8 +2173,13 @@ func parseWebhookHeaders(value string) (http.Header, string, error) {
name, headerValue, found := strings.Cut(item, ":") name, headerValue, found := strings.Cut(item, ":")
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
if !found || !IsHeaderName(name) || strings.ContainsAny(headerValue, "\r\n\x00") { if !found || !IsHeaderName(name) ||
return nil, "", fmt.Errorf("item %d %w", i+1, errNotWebhookHeader) strings.ContainsAny(headerValue, "\r\n\x00") {
return nil, "", fmt.Errorf(
"item %d %w",
i+1,
errNotWebhookHeader,
)
} }
headers.Add(name, strings.TrimSpace(headerValue)) headers.Add(name, strings.TrimSpace(headerValue))
+14 -4
View File
@@ -133,8 +133,11 @@ func (f *File) Watch(ctx context.Context) {
} }
if err != nil { if err != nil {
f.params.ProcessLog.Error("cannot watch the lookup database for replacements", f.params.ProcessLog.Error(
"error", err.Error()) "cannot watch the lookup database for replacements",
"error",
err.Error(),
)
return return
} }
@@ -194,7 +197,10 @@ func (f *File) readAgain() {
f.params.Alerts.Raise(alerts.Alert{ f.params.Alerts.Raise(alerts.Alert{
Event: alerts.EventFileError, Event: alerts.EventFileError,
Reason: kept, Reason: kept,
Detail: map[string]any{"file": f.params.Path, "error": err.Error()}, Detail: map[string]any{
"file": f.params.Path,
"error": err.Error(),
},
}) })
f.params.ProcessLog.Error(kept, "error", err.Error()) f.params.ProcessLog.Error(kept, "error", err.Error())
@@ -212,7 +218,11 @@ func (f *File) use(reader *maxminddb.Reader) {
f.lastRead = f.params.Now() f.lastRead = f.params.Now()
f.mu.Unlock() f.mu.Unlock()
f.params.ProcessLog.Info("read the lookup database", "file", f.params.Path) f.params.ProcessLog.Info(
"read the lookup database",
"file",
f.params.Path,
)
} }
// read reads the lookup database at path. The whole file is read into // read reads the lookup database at path. The whole file is read into
+23 -4
View File
@@ -19,27 +19,34 @@ const (
// ActionForward is a request discriminator allowed, which rproxy passes to // ActionForward is a request discriminator allowed, which rproxy passes to
// the app. // the app.
ActionForward = "forward" ActionForward = "forward"
// ActionTooLarge is a request over the header limits. // ActionTooLarge is a request over the header limits.
ActionTooLarge = "too_large" ActionTooLarge = "too_large"
// ActionRateLimited is a request refused because it took its client // ActionRateLimited is a request refused because it took its client
// over a rate limit, which bans the client. // over a rate limit, which bans the client.
ActionRateLimited = "rate_limited" ActionRateLimited = "rate_limited"
// ActionBanned is a request refused because a ban covers its client, // ActionBanned is a request refused because a ban covers its client,
// or because it matched a ban rule, asked for a trap path or the // or because it matched a ban rule, asked for a trap path or the
// CrowdSec decision list lists its client, each of which bans the // CrowdSec decision list lists its client, each of which bans the
// client. // client.
ActionBanned = "banned" ActionBanned = "banned"
// ActionRuleBlocked is a request refused because it matched a block // ActionRuleBlocked is a request refused because it matched a block
// rule. // rule.
ActionRuleBlocked = "rule_blocked" ActionRuleBlocked = "rule_blocked"
// ActionWAFBlocked is a request refused because the Core Rule Set // ActionWAFBlocked is a request refused because the Core Rule Set
// scored it at or over SWW_WAF_ANOMALY_THRESHOLD. // scored it at or over SWW_WAF_ANOMALY_THRESHOLD.
ActionWAFBlocked = "waf_blocked" ActionWAFBlocked = "waf_blocked"
// ActionDenied is a request refused because its client is in // ActionDenied is a request refused because its client is in
// SWW_DENY_NETS, in a blocklist while SWW_BLOCKLIST_ACTION is deny, // SWW_DENY_NETS, in a blocklist while SWW_BLOCKLIST_ACTION is deny,
// or listed by a DNSBL zone, or scored a hit by AbuseIPDB, while // or listed by a DNSBL zone, or scored a hit by AbuseIPDB, while
// SWW_REPUTATION_ACTION is deny. // SWW_REPUTATION_ACTION is deny.
ActionDenied = "denied" ActionDenied = "denied"
// ActionCountryDenied is a request refused for its client's country. // ActionCountryDenied is a request refused for its client's country.
ActionCountryDenied = "country_denied" ActionCountryDenied = "country_denied"
// ActionAdmin is a request discriminator answered at one of its own // ActionAdmin is a request discriminator answered at one of its own
@@ -255,7 +262,11 @@ func FormatTime(t time.Time) string {
// Milliseconds is d in milliseconds, to the microsecond. // Milliseconds is d in milliseconds, to the microsecond.
func Milliseconds(d time.Duration) float64 { func Milliseconds(d time.Duration) float64 {
return float64(d.Microseconds()) / float64(time.Millisecond/time.Microsecond) return float64(
d.Microseconds(),
) / float64(
time.Millisecond/time.Microsecond,
)
} }
// NewProcessLogger returns the logger for the process's own messages: // NewProcessLogger returns the logger for the process's own messages:
@@ -263,17 +274,25 @@ func Milliseconds(d time.Duration) float64 {
// as a request line's, and instanceName, SWW_INSTANCE_NAME, as instance. // as a request line's, and instanceName, SWW_INSTANCE_NAME, as instance.
// It writes only the messages at level, SWW_LOG_LEVEL, or more severe; // It writes only the messages at level, SWW_LOG_LEVEL, or more severe;
// the request lines Write writes are never held back. // the request lines Write writes are never held back.
func NewProcessLogger(w io.Writer, instanceName string, level slog.Level) *slog.Logger { func NewProcessLogger(
w io.Writer,
instanceName string,
level slog.Level,
) *slog.Logger {
handler := slog.NewJSONHandler(w, &slog.HandlerOptions{ handler := slog.NewJSONHandler(w, &slog.HandlerOptions{
Level: level, Level: level,
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey && len(groups) == 0 { if attr.Key == slog.TimeKey && len(groups) == 0 {
return slog.String(slog.TimeKey, FormatTime(attr.Value.Time())) return slog.String(
slog.TimeKey,
FormatTime(attr.Value.Time()),
)
} }
return attr return attr
}, },
}) })
return slog.New(handler).With("type", "process", "instance", instanceName) return slog.New(handler).
With("type", "process", "instance", instanceName)
} }