middleware.go 8.8 KB

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