fix(audit): strip IPv6 brackets before INET insert
When a client connects over IPv6 loopback, net/http's RemoteAddr is
'[::1]:port'. The previous clientIP() returned '[::1]' (brackets and
all) which Postgres's INET type rejects with
'invalid input syntax for type inet: "[::1]" (SQLSTATE 22P02)'.
Live local-smoke caught this — every state-changing endpoint emitted
the audit event, the INSERT rolled back, and a warning landed in the
log. The user-facing operation succeeded so the caller never noticed,
but audit_log stayed empty.
Fix:
- Use net.SplitHostPort which returns IPv6 hosts without brackets.
- Add stripBrackets() as a belt-and-braces for X-Forwarded-For
headers that wrap the IP themselves (some proxies do).
Refs: M4.2
This commit is contained in:
+13
-11
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -87,22 +88,23 @@ func (s *statusRecorder) WriteHeader(c int) {
|
||||
func clientIP(r *http.Request) string {
|
||||
if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
|
||||
if i := strings.IndexByte(fwd, ','); i > 0 {
|
||||
return strings.TrimSpace(fwd[:i])
|
||||
return stripBrackets(strings.TrimSpace(fwd[:i]))
|
||||
}
|
||||
return strings.TrimSpace(fwd)
|
||||
return stripBrackets(strings.TrimSpace(fwd))
|
||||
}
|
||||
if host, _, ok := splitHostPort(r.RemoteAddr); ok {
|
||||
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
// net.SplitHostPort returns IPv6 without brackets already.
|
||||
return host
|
||||
}
|
||||
return r.RemoteAddr
|
||||
return stripBrackets(r.RemoteAddr)
|
||||
}
|
||||
|
||||
// splitHostPort is a port-tolerant version of net.SplitHostPort that doesn't
|
||||
// error on missing port.
|
||||
func splitHostPort(s string) (string, string, bool) {
|
||||
i := strings.LastIndexByte(s, ':')
|
||||
if i < 0 {
|
||||
return s, "", false
|
||||
// stripBrackets removes the `[...]` wrapping IPv6 hosts pick up from
|
||||
// net/http's RemoteAddr in some Go versions, since Postgres `inet` rejects
|
||||
// `[::1]` but accepts `::1`.
|
||||
func stripBrackets(s string) string {
|
||||
if len(s) >= 2 && s[0] == '[' && s[len(s)-1] == ']' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s[:i], s[i+1:], true
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user