mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-12 01:20:45 +02:00
* fix(s3/audit): emit audit log for successful GET/HEAD Successful GET/HEAD object requests never produced a fluent audit entry because those handlers write the response directly (streaming for GET, WriteHeader for HEAD) and never reach a PostLog call site. The wiki advertises GET as an audited verb, so the asymmetry surprises operators who rely on the log for read-access auditing. Move the safety net into the track() middleware: tag each request with an audit-tracking flag, let PostLog/PostAccessLog (delete path) mark it, and emit a single fallback entry after the handler returns when nothing fired. The recorder's status flows into the fallback so the audit row still reflects 200/206 vs 404 etc. No double logging for handlers that already emit (write helpers, error paths, bulk delete). Refs #9463 * fix(s3/audit): defensive nil checks on audit-tracking helpers Address PR review: guard against nil request and nil *atomic.Bool stored under the audit-tracking key. The conditions are unreachable today (the key is private and we only ever store new(atomic.Bool)), but the checks are free and keep the helpers safe if a future caller misbehaves. * test(s3/audit): track() audit fallback coverage + stale comment cleanup (#9469) test(s3/audit): cover track() fallback wiring + cleanup Adds two unit tests in weed/s3api/stats_test.go that exercise the audit-tracking flag set up by track(): one verifies the fallback path fires when a handler writes the response directly (the GET/HEAD object regression in #9463), the other verifies the flag is set when a handler emits PostLog itself so the fallback is skipped. To make the wiring observable without standing up fluent, PostLog now marks the audit flag before short-circuiting on a nil Logger; production behavior is unchanged (no logger, no posting) but the flag stays consistent. Also drops two stale comments in s3api_object_handlers.go that still referenced proxyToFiler — that helper was removed when GET/HEAD started streaming from volume servers directly. Stacks on #9467.
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package s3err
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/request_id"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestGetAccessLogUsesAmzRequestID(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
|
req = req.WithContext(request_id.Set(req.Context(), "req-123"))
|
|
|
|
log := GetAccessLog(req, http.StatusOK, ErrNone)
|
|
|
|
assert.Equal(t, "req-123", log.RequestID)
|
|
}
|
|
|
|
func TestGetAccessLogRemoteIP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
remoteAddr string
|
|
xRealIP string
|
|
xForwardedFor string
|
|
expectedRemote string
|
|
}{
|
|
{
|
|
name: "falls back to RemoteAddr (port stripped) when no headers set",
|
|
remoteAddr: "10.89.0.1:35832",
|
|
expectedRemote: "10.89.0.1",
|
|
},
|
|
{
|
|
name: "preserves IPv6 host from RemoteAddr",
|
|
remoteAddr: "[2001:db8::1]:35832",
|
|
expectedRemote: "2001:db8::1",
|
|
},
|
|
{
|
|
name: "returns RemoteAddr unchanged when no port present",
|
|
remoteAddr: "@",
|
|
expectedRemote: "@",
|
|
},
|
|
{
|
|
name: "uses X-Real-IP when X-Forwarded-For is absent",
|
|
remoteAddr: "10.89.0.1:35832",
|
|
xRealIP: "203.0.113.7",
|
|
expectedRemote: "203.0.113.7",
|
|
},
|
|
{
|
|
name: "prefers X-Forwarded-For over X-Real-IP",
|
|
remoteAddr: "10.89.0.1:35832",
|
|
xRealIP: "203.0.113.7",
|
|
xForwardedFor: "198.51.100.42",
|
|
expectedRemote: "198.51.100.42",
|
|
},
|
|
{
|
|
name: "uses first hop in X-Forwarded-For chain",
|
|
remoteAddr: "10.89.0.1:35832",
|
|
xForwardedFor: "198.51.100.42, 10.0.0.5, 10.89.0.1",
|
|
expectedRemote: "198.51.100.42",
|
|
},
|
|
{
|
|
name: "skips empty leading entries in X-Forwarded-For",
|
|
remoteAddr: "10.89.0.1:35832",
|
|
xForwardedFor: ", 198.51.100.42",
|
|
expectedRemote: "198.51.100.42",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
|
req.RemoteAddr = tc.remoteAddr
|
|
if tc.xRealIP != "" {
|
|
req.Header.Set("X-Real-IP", tc.xRealIP)
|
|
}
|
|
if tc.xForwardedFor != "" {
|
|
req.Header.Set("X-Forwarded-For", tc.xForwardedFor)
|
|
}
|
|
|
|
log := GetAccessLog(req, http.StatusOK, ErrNone)
|
|
|
|
assert.Equal(t, tc.expectedRemote, log.RemoteIP)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuditTrackingFlag(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
|
assert.False(t, AuditAlreadyLogged(req), "untracked request reports not logged")
|
|
|
|
tracked := EnsureAuditTracking(req)
|
|
assert.NotSame(t, req, tracked, "EnsureAuditTracking returns a new request when no flag is present")
|
|
assert.False(t, AuditAlreadyLogged(tracked), "tracked request starts unlogged")
|
|
|
|
again := EnsureAuditTracking(tracked)
|
|
assert.Same(t, tracked, again, "EnsureAuditTracking is idempotent when flag already present")
|
|
|
|
MarkAuditLogged(tracked)
|
|
assert.True(t, AuditAlreadyLogged(tracked), "flag flips after MarkAuditLogged")
|
|
}
|