middleware.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package audit
  2. import (
  3. "bytes"
  4. "database/sql"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net"
  9. "net/http"
  10. "strings"
  11. "time"
  12. "udeServer/internal/log"
  13. udeModel "udeServer/internal/model"
  14. "udeServer/internal/response"
  15. )
  16. // 捕获响应
  17. type wrappedResponseWriter struct {
  18. http.ResponseWriter
  19. statusCode int
  20. body *bytes.Buffer
  21. }
  22. func newWrappedResponseWriter(w http.ResponseWriter) *wrappedResponseWriter {
  23. return &wrappedResponseWriter{
  24. ResponseWriter: w,
  25. statusCode: http.StatusOK,
  26. body: bytes.NewBuffer(nil),
  27. }
  28. }
  29. // Write re-records the response body (buffered for the audit entry).
  30. func (w *wrappedResponseWriter) Write(b []byte) (int, error) {
  31. w.body.Write(b)
  32. return w.ResponseWriter.Write(b)
  33. }
  34. // WriteHeader re-records the status code.
  35. func (w *wrappedResponseWriter) WriteHeader(code int) {
  36. w.statusCode = code
  37. w.ResponseWriter.WriteHeader(code)
  38. }
  39. // genericResp parses the unified response envelope.
  40. type genericResp struct {
  41. Code int `json:"code"`
  42. Message string `json:"message"`
  43. Data json.RawMessage `json:"data"`
  44. }
  45. // skipLogPaths silences self-generation noise: the log management endpoints
  46. // would otherwise audit every entry they produce, and the archive download
  47. // response is a huge binary blob with no audit value.
  48. var skipLogPaths = map[string]bool{
  49. "/api/v1/systemLog/list": true,
  50. "/api/v1/systemLog/audit": true,
  51. "/api/v1/systemLog/link": true,
  52. "/api/v1/logRestore/list": true,
  53. "/api/v1/logArchive/list": true,
  54. "/api/v1/logArchive/download": true,
  55. }
  56. // LogMiddleware audits every HTTP request: it captures request/response
  57. // bodies, computes digests, uploads the raw bodies to MinIO, builds a
  58. // t_system_log record guarded by an HMAC-SM3 signature and stores it
  59. // asynchronously. Deleted per sdf-serve build: the entry is built from the
  60. // unified response envelope (udeServer answers HTTP 200 always, business
  61. // codes live in the body), so ResponseCode records the body code.
  62. type LogMiddleware struct {
  63. }
  64. func NewLogMiddleware() *LogMiddleware {
  65. return &LogMiddleware{}
  66. }
  67. // Handle is the middleware closure, constructed with the system-log and
  68. // config models plus the service name of this deployment.
  69. func (m *LogMiddleware) Handle(logModel udeModel.TSystemLogModel, configModel udeModel.TConfigModel, serviceName string) func(next http.HandlerFunc) http.HandlerFunc {
  70. return func(next http.HandlerFunc) http.HandlerFunc {
  71. return func(w http.ResponseWriter, r *http.Request) {
  72. if skipLogPaths[r.URL.Path] {
  73. next.ServeHTTP(w, r)
  74. return
  75. }
  76. // 1.获取TraceId和SpanId以及请求元数据
  77. ctx, traceID, spanID, userID, userName, _ := EnsureTraceAndSpanID(r.Context())
  78. // 2.读请求体
  79. reqBody, _ := io.ReadAll(r.Body)
  80. r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
  81. reqHeader := filterRequestHeader(r.Header)
  82. log.RecordDebug(ctx, "请求头:%v", reqHeader)
  83. // 3.捕获响应
  84. rw := newWrappedResponseWriter(w)
  85. start := time.Now()
  86. next.ServeHTTP(rw, r.WithContext(ctx))
  87. duration := time.Since(start).Milliseconds()
  88. // 4.解析响应统一封装
  89. respBody := rw.body.Bytes()
  90. var resp genericResp
  91. httpCode := int64(response.CodeServerError)
  92. message := ""
  93. parseFailed := false
  94. if err := json.Unmarshal(respBody, &resp); err != nil {
  95. parseFailed = true
  96. log.RecordWarn(ctx, "[LogHandle] 无法解析响应 JSON, Path:%s, Status:%d, Raw:%s",
  97. r.URL.Path, rw.statusCode, string(respBody))
  98. } else {
  99. httpCode = int64(resp.Code)
  100. message = resp.Message
  101. if resp.Code != response.CodeOK {
  102. log.RecordError(ctx, fmt.Errorf("[Request-Fail] Path=%s Code=%d Msg=%q",
  103. r.URL.Path, resp.Code, resp.Message))
  104. } else {
  105. log.RecordInfo(ctx, "[Request-Success] Path=%s Code=%d Msg=%s Data:%+v",
  106. r.URL.Path, resp.Code, resp.Message, string(resp.Data))
  107. }
  108. }
  109. // 5. 上传 & 摘要
  110. client, logConfig := GetGlobalConfig(configModel)
  111. reqDigestStr, reqUrl, reqErr := UploadAndDigest(reqBody, traceID, spanID, "[Http-Request]", client, r.Header.Get("Content-Type"))
  112. if reqErr != nil {
  113. log.RecordError(ctx, reqErr)
  114. }
  115. respDigestStr, respUrl, respErr := UploadAndDigest(respBody, traceID, spanID, "[Http-Response]", client, "application/json")
  116. if respErr != nil {
  117. log.RecordError(ctx, respErr)
  118. }
  119. // 6.构造日志对象
  120. entry := &udeModel.TSystemLog{
  121. Iden: NewUUID(),
  122. TraceId: traceID,
  123. SpanId: spanID,
  124. ParentSpanId: sql.NullString{String: "", Valid: false}, //顶层链路父SpanId为空时填写null
  125. Type: SystemLog,
  126. ServiceName: serviceName,
  127. UriPath: sql.NullString{String: r.URL.Path, Valid: true},
  128. HttpMethod: sql.NullString{String: r.Method, Valid: true},
  129. Method: deriveHandlerName(r.URL.Path),
  130. Level: ChooseLevelByBusinessCode(httpCode),
  131. AppKey: sql.NullString{Valid: false},
  132. UserId: sql.NullString{String: userID, Valid: userID != ""},
  133. UserName: sql.NullString{String: userName, Valid: userName != ""},
  134. ClientIp: sql.NullString{String: GetClientIPPreferPublic(r), Valid: true},
  135. RequestHeader: reqHeader,
  136. RequestBodyDigest: reqDigestStr,
  137. RequestBodyFileKey: reqUrl,
  138. ResponseCode: httpCode,
  139. ResponseBodyDigest: respDigestStr,
  140. ResponseBodyFileKey: respUrl,
  141. DurationMs: duration,
  142. CreatedAt: time.Now(),
  143. AuditStatus: UnAudited,
  144. }
  145. // 7.填充ErrMsg
  146. switch {
  147. case parseFailed:
  148. entry.ErrorMessage = sql.NullString{String: "Invalid response format", Valid: true}
  149. case resp.Code != response.CodeOK:
  150. entry.ErrorMessage = sql.NullString{String: message, Valid: true}
  151. default:
  152. entry.ErrorMessage = sql.NullString{Valid: false}
  153. }
  154. // 8.填充Hmac值,复用缓冲区;密钥为空时用空串计算,保持列非空
  155. macBuffer := make([]byte, 0, 32)
  156. if mac, hErr := CalculateHMAC(entry, logConfig.HmacSecret, macBuffer); hErr == nil {
  157. entry.HMac = mac
  158. } else {
  159. log.RecordError(ctx, hErr)
  160. }
  161. // 9.按配置库中 log_record_level 过滤后异步入库
  162. save := SaveToDBByLogLevel(logConfig.LogRecordLevel, entry.Level)
  163. if save {
  164. go AsyncInsertLog(logModel, entry)
  165. }
  166. }
  167. }
  168. }
  169. // deriveHandlerName derives the business handler name from the request path
  170. // (last path segment + "Handler").
  171. func deriveHandlerName(path string) string {
  172. clean := strings.Trim(path, "/")
  173. parts := strings.Split(clean, "/")
  174. if len(parts) == 0 {
  175. return ""
  176. }
  177. last := parts[len(parts)-1]
  178. return last + "Handler"
  179. }
  180. var keepHeaders = map[string]bool{
  181. "content-type": true,
  182. // x-api-key/x-user-id 等 udeServer 还没有鉴权体系,暂无注入来源
  183. }
  184. // filterRequestHeader keeps only whitelisted request headers.
  185. func filterRequestHeader(header map[string][]string) sql.NullString {
  186. filtered := make(map[string][]string)
  187. for k, v := range header {
  188. if keepHeaders[strings.ToLower(k)] {
  189. filtered[k] = v
  190. }
  191. }
  192. if headerBytes, err := json.Marshal(filtered); err == nil {
  193. return sql.NullString{
  194. String: string(headerBytes),
  195. Valid: true,
  196. }
  197. }
  198. return sql.NullString{Valid: false}
  199. }
  200. // GetClientIPPreferPublic extracts the client IP, preferring public IPs.
  201. func GetClientIPPreferPublic(r *http.Request) string {
  202. ip := getClientIP(r, true)
  203. if ip == "" {
  204. ip = getClientIP(r, false)
  205. }
  206. return ip
  207. }
  208. // getClientIP resolves the client IP honouring proxy headers, optionally
  209. // hiding private addresses.
  210. func getClientIP(r *http.Request, excludePrivate bool) string {
  211. // 1. 从 X-Forwarded-For 获取第一个真实IP
  212. xff := r.Header.Get("X-Forwarded-For")
  213. if xff != "" {
  214. parts := strings.Split(xff, ",")
  215. for _, ip := range parts {
  216. ip = strings.TrimSpace(ip)
  217. if ip != "" && (!excludePrivate || isPublicIP(ip)) {
  218. return ip
  219. }
  220. }
  221. }
  222. // 2. 从 X-Real-IP 获取反向代理地址
  223. if ip := strings.TrimSpace(r.Header.Get("X-Real-IP")); ip != "" {
  224. if !excludePrivate || isPublicIP(ip) {
  225. return ip
  226. }
  227. }
  228. // 3. 从 RemoteAddr 获取代理地址
  229. ip, _, err := net.SplitHostPort(r.RemoteAddr)
  230. if err == nil && (!excludePrivate || isPublicIP(ip)) {
  231. return ip
  232. }
  233. return ""
  234. }
  235. // isPublicIP reports whether the IP address is public/external.
  236. func isPublicIP(ip string) bool {
  237. parsedIP := net.ParseIP(ip)
  238. if parsedIP == nil {
  239. return false
  240. }
  241. privateBlocks := []*net.IPNet{
  242. // IPv4 私有地址段
  243. {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)},
  244. {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)},
  245. {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)},
  246. // IPv6 loopback
  247. {IP: net.ParseIP("::1"), Mask: net.CIDRMask(128, 128)},
  248. }
  249. for _, block := range privateBlocks {
  250. if block.Contains(parsedIP) {
  251. return false
  252. }
  253. }
  254. return true
  255. }