ops_ingress_reject_handler.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package admin
  2. import (
  3. "net/http"
  4. "net/netip"
  5. "strconv"
  6. "strings"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/gin-gonic/gin"
  10. )
  11. var ingressRejectReasons = map[string]struct{}{
  12. "query_api_key_deprecated": {}, "api_key_required": {}, "invalid_api_key": {},
  13. "invalid_auth_rate_limited": {},
  14. "api_key_auth_overloaded": {},
  15. "api_key_disabled": {}, "ip_restricted": {}, "user_inactive": {}, "group_deleted": {},
  16. "group_disabled": {}, "group_not_allowed": {}, "group_unassigned": {}, "other": {},
  17. }
  18. var ingressRejectRouteFamilies = map[string]struct{}{
  19. "antigravity": {}, "gemini": {}, "codex": {}, "messages": {}, "responses": {},
  20. "chat_completions": {}, "images": {}, "videos": {}, "embeddings": {}, "models": {}, "other": {},
  21. }
  22. var ingressRejectProtocols = map[string]struct{}{
  23. "google": {}, "anthropic": {}, "openai": {}, "gateway": {}, "other": {},
  24. }
  25. // ListIngressRejects returns bounded security aggregates, never raw credentials or request bodies.
  26. func (h *OpsHandler) ListIngressRejects(c *gin.Context) {
  27. if h.opsService == nil {
  28. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  29. return
  30. }
  31. page, pageSize := response.ParsePagination(c)
  32. if pageSize > 200 {
  33. pageSize = 200
  34. }
  35. startTime, endTime, err := parseOpsTimeRange(c, "1h")
  36. if err != nil {
  37. response.BadRequest(c, err.Error())
  38. return
  39. }
  40. filter := &service.OpsIngressRejectFilter{Page: page, PageSize: pageSize}
  41. if !startTime.IsZero() {
  42. filter.StartTime = &startTime
  43. }
  44. if !endTime.IsZero() {
  45. filter.EndTime = &endTime
  46. }
  47. if filter.RejectReason, err = parseIngressRejectEnum(c, "reason", ingressRejectReasons); err != nil {
  48. response.BadRequest(c, err.Error())
  49. return
  50. }
  51. if filter.RouteFamily, err = parseIngressRejectEnum(c, "route_family", ingressRejectRouteFamilies); err != nil {
  52. response.BadRequest(c, err.Error())
  53. return
  54. }
  55. if filter.Protocol, err = parseIngressRejectEnum(c, "protocol", ingressRejectProtocols); err != nil {
  56. response.BadRequest(c, err.Error())
  57. return
  58. }
  59. if raw := strings.TrimSpace(c.Query("client_ip")); raw != "" {
  60. addr, parseErr := netip.ParseAddr(raw)
  61. if parseErr != nil {
  62. response.BadRequest(c, "Invalid client_ip")
  63. return
  64. }
  65. addr = addr.Unmap()
  66. if addr.Is6() {
  67. addr = netip.PrefixFrom(addr, 64).Masked().Addr()
  68. }
  69. filter.ClientIP = addr.String()
  70. }
  71. if filter.UserID, err = parseOptionalPositiveID(c, "user_id"); err != nil {
  72. response.BadRequest(c, err.Error())
  73. return
  74. }
  75. if filter.APIKeyID, err = parseOptionalPositiveID(c, "api_key_id"); err != nil {
  76. response.BadRequest(c, err.Error())
  77. return
  78. }
  79. result, err := h.opsService.ListIngressRejects(c.Request.Context(), filter)
  80. if err != nil {
  81. response.ErrorFrom(c, err)
  82. return
  83. }
  84. response.Success(c, result)
  85. }
  86. func (h *OpsHandler) GetIngressRejectHealth(c *gin.Context) {
  87. if h.opsService == nil {
  88. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  89. return
  90. }
  91. if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
  92. response.ErrorFrom(c, err)
  93. return
  94. }
  95. response.Success(c, h.opsService.GetIngressRejectHealth())
  96. }
  97. func parseIngressRejectEnum(c *gin.Context, name string, allowed map[string]struct{}) (string, error) {
  98. value := strings.TrimSpace(c.Query(name))
  99. if value == "" {
  100. return "", nil
  101. }
  102. if _, ok := allowed[value]; !ok {
  103. return "", &ingressRejectQueryError{message: "Invalid " + name}
  104. }
  105. return value, nil
  106. }
  107. func parseOptionalPositiveID(c *gin.Context, name string) (*int64, error) {
  108. raw := strings.TrimSpace(c.Query(name))
  109. if raw == "" {
  110. return nil, nil
  111. }
  112. value, err := strconv.ParseInt(raw, 10, 64)
  113. if err != nil || value <= 0 {
  114. return nil, &ingressRejectQueryError{message: "Invalid " + name}
  115. }
  116. return &value, nil
  117. }
  118. type ingressRejectQueryError struct{ message string }
  119. func (e *ingressRejectQueryError) Error() string { return e.message }