All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
155 lines
6.4 KiB
Go
155 lines
6.4 KiB
Go
package logger
|
||
|
||
import (
|
||
"bytes"
|
||
"io"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
"go.uber.org/zap"
|
||
"go.uber.org/zap/zapcore"
|
||
)
|
||
|
||
func TestSanitizeJSONRecursivelyMasksRequestAndResponseSecrets(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
raw := []byte(`{"Password":"p@ss","items":[{"access_token":"token-value","count":3}],"profile":{"private_url":"https://secret.example/a"}}`)
|
||
result := sanitizeBody(raw, BodyPolicyJSON)
|
||
if strings.Contains(result.Content, "p@ss") || strings.Contains(result.Content, "token-value") || strings.Contains(result.Content, "secret.example") {
|
||
t.Fatalf("访问日志仍包含敏感值:%s", result.Content)
|
||
}
|
||
if !strings.Contains(result.Content, redactedValue) {
|
||
t.Fatalf("访问日志未输出统一脱敏占位:%s", result.Content)
|
||
}
|
||
if result.Truncated {
|
||
t.Fatal("短载荷不应标记为截断")
|
||
}
|
||
}
|
||
|
||
func TestFiberAccessLogSanitizesFinalRequestAndResponseJSON(t *testing.T) {
|
||
var output bytes.Buffer
|
||
encoderConfig := zap.NewProductionEncoderConfig()
|
||
log := zap.New(zapcore.NewCore(
|
||
zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(&output), zapcore.InfoLevel,
|
||
))
|
||
app := fiber.New()
|
||
app.Use(func(c *fiber.Ctx) error {
|
||
c.Locals("requestid", "request-test-1")
|
||
return c.Next()
|
||
})
|
||
app.Use(MiddlewareWithLogger(log))
|
||
app.Post("/tokens", func(c *fiber.Ctx) error {
|
||
return c.JSON(fiber.Map{"access_token": "response-token", "data": fiber.Map{"ok": true}})
|
||
})
|
||
|
||
request := httptest.NewRequest("POST", "/tokens?Authorization=query-secret&page=1", strings.NewReader(`{"password":"request-secret"}`))
|
||
request.Header.Set("Content-Type", "application/json")
|
||
response, err := app.Test(request)
|
||
if err != nil {
|
||
t.Fatalf("执行 Fiber 请求失败:%v", err)
|
||
}
|
||
_, _ = io.Copy(io.Discard, response.Body)
|
||
_ = response.Body.Close()
|
||
|
||
logged := output.String()
|
||
for _, secret := range []string{"request-secret", "response-token", "query-secret"} {
|
||
if strings.Contains(logged, secret) {
|
||
t.Fatalf("最终 JSON 访问日志泄露测试凭证 %q:%s", secret, logged)
|
||
}
|
||
}
|
||
for _, field := range []string{"request_id", "status", "duration_ms", "request_body_truncated", "response_body_truncated"} {
|
||
if !strings.Contains(logged, field) {
|
||
t.Fatalf("最终 JSON 访问日志缺少字段 %q:%s", field, logged)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSensitiveRouteMatrixNeverLogsRawPayload(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
path string
|
||
contentType string
|
||
body string
|
||
secret string
|
||
}{
|
||
{name: "登录JSON", path: "/api/auth/login", contentType: "application/json", body: `{"username":"safe-user","password":"json-secret"}`, secret: "json-secret"},
|
||
{name: "企微XML", path: "/api/callback/wecom", contentType: "application/xml", body: `<xml><Encrypt>xml-secret</Encrypt></xml>`, secret: "xml-secret"},
|
||
{name: "支付表单", path: "/api/callback/alipay", contentType: "application/x-www-form-urlencoded", body: "order_no=SAFE-1&sign=form-secret", secret: "form-secret"},
|
||
{name: "文件multipart", path: "/api/admin/storage/upload", contentType: "multipart/form-data; boundary=x", body: "--x\r\nContent-Disposition: form-data; name=\"file\"; filename=\"secret.bin\"\r\nContent-Type: application/octet-stream\r\n\r\nfile-bytes-secret\r\n--x--\r\n", secret: "file-bytes-secret"},
|
||
{name: "导出二进制", path: "/api/admin/export-tasks/1/download", contentType: "application/octet-stream", body: "binary-secret", secret: "binary-secret"},
|
||
{name: "支付不可解析", path: "/api/callback/wechat-pay", contentType: "application/json", body: `{"sign":"broken-secret"`, secret: "broken-secret"},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
var output bytes.Buffer
|
||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||
app := fiber.New()
|
||
app.Use(MiddlewareWithLogger(log))
|
||
app.Post(tc.path, func(c *fiber.Ctx) error {
|
||
return c.Status(fiber.StatusOK).Type("json").SendString(`{"access_token":"response-secret","status":"ok"}`)
|
||
})
|
||
request := httptest.NewRequest("POST", tc.path+"?signature=query-secret", strings.NewReader(tc.body))
|
||
request.Header.Set("Content-Type", tc.contentType)
|
||
response, err := app.Test(request)
|
||
if err != nil {
|
||
t.Fatalf("执行敏感路由请求失败:%v", err)
|
||
}
|
||
_, _ = io.Copy(io.Discard, response.Body)
|
||
_ = response.Body.Close()
|
||
logged := output.String()
|
||
for _, secret := range []string{tc.secret, "query-secret", "response-secret"} {
|
||
if strings.Contains(logged, secret) {
|
||
t.Fatalf("敏感路由日志泄露 %q:%s", secret, logged)
|
||
}
|
||
}
|
||
if !strings.Contains(logged, "body_policy") || !strings.Contains(logged, "sha256") {
|
||
t.Fatalf("敏感路由日志缺少策略或摘要:%s", logged)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestSensitiveRouteLongBodyRecordsTruncationWithoutRawSecret(t *testing.T) {
|
||
var output bytes.Buffer
|
||
log := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(&output), zapcore.InfoLevel))
|
||
app := fiber.New()
|
||
app.Use(MiddlewareWithLogger(log))
|
||
app.Post("/api/admin/storage/upload", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) })
|
||
secret := strings.Repeat("long-file-secret", MaxBodyLogSize)
|
||
request := httptest.NewRequest("POST", "/api/admin/storage/upload", strings.NewReader(secret))
|
||
request.Header.Set("Content-Type", "application/octet-stream")
|
||
response, err := app.Test(request, 10000)
|
||
if err != nil {
|
||
t.Fatalf("执行超长敏感请求失败:%v", err)
|
||
}
|
||
_ = response.Body.Close()
|
||
logged := output.String()
|
||
if strings.Contains(logged, "long-file-secret") || !strings.Contains(logged, `"request_body_truncated":true`) {
|
||
t.Fatalf("超长敏感载荷未安全摘要或未标记截断:%s", logged)
|
||
}
|
||
}
|
||
|
||
func TestSanitizeInvalidJSONNeverFallsBackToRawBody(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
raw := []byte(`{"password":"should-never-appear"`)
|
||
result := sanitizeBody(raw, BodyPolicyJSON)
|
||
if strings.Contains(result.Content, "should-never-appear") {
|
||
t.Fatalf("解析失败时不得回退原文:%s", result.Content)
|
||
}
|
||
if result.SHA256 == "" || result.Size != len(raw) {
|
||
t.Fatalf("解析失败摘要缺少大小或哈希:%+v", result)
|
||
}
|
||
}
|
||
|
||
func TestSanitizeQueryUsesSameSensitiveFieldRules(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
query := sanitizeQuery("page=1&Authorization=Bearer-secret&nonce=abc")
|
||
if strings.Contains(query.Content, "Bearer-secret") || strings.Contains(query.Content, "abc") {
|
||
t.Fatalf("query 敏感值未脱敏:%s", query.Content)
|
||
}
|
||
}
|