naive.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package naive
  2. import (
  3. "context"
  4. _ "embed"
  5. "fmt"
  6. "log/slog"
  7. "math/rand/v2"
  8. "net/http"
  9. "net/netip"
  10. "time"
  11. "github.com/TecharoHQ/anubis/internal"
  12. "github.com/TecharoHQ/anubis/internal/honeypot"
  13. "github.com/TecharoHQ/anubis/lib/policy/checker"
  14. "github.com/TecharoHQ/anubis/lib/store"
  15. "github.com/a-h/templ"
  16. "github.com/google/uuid"
  17. "github.com/nikandfor/spintax"
  18. )
  19. //go:generate go tool github.com/a-h/templ/cmd/templ generate
  20. // XXX(Xe): All of this was generated by ChatGPT, GLM 4.6, and GPT-OSS 120b. This is pseudoprofound bullshit in spintax[1] format so that the bullshit generator can emit plausibly human-authored text while being very computationally cheap.
  21. //
  22. // It feels somewhat poetic to use spammer technology in Anubis.
  23. //
  24. // [1]: https://outboundly.ai/blogs/what-is-spintax-and-how-to-use-it/
  25. //
  26. //go:embed spintext.txt
  27. var spintext string
  28. //go:embed titles.txt
  29. var titles string
  30. //go:embed affirmations.txt
  31. var affirmations string
  32. func New(st store.Interface, lg *slog.Logger) (*Impl, error) {
  33. affirmation, err := spintax.Parse(affirmations)
  34. if err != nil {
  35. return nil, fmt.Errorf("can't parse affirmations: %w", err)
  36. }
  37. body, err := spintax.Parse(spintext)
  38. if err != nil {
  39. return nil, fmt.Errorf("can't parse bodies: %w", err)
  40. }
  41. title, err := spintax.Parse(titles)
  42. if err != nil {
  43. return nil, fmt.Errorf("can't parse titles: %w", err)
  44. }
  45. lg.Debug("initialized basic bullshit generator", "affirmations", affirmation.Count(), "bodies", body.Count(), "titles", title.Count())
  46. return &Impl{
  47. st: st,
  48. infos: store.JSON[honeypot.Info]{Underlying: st, Prefix: "honeypot:info"},
  49. uaWeight: store.JSON[int]{Underlying: st, Prefix: "honeypot:user-agent"},
  50. networkWeight: store.JSON[int]{Underlying: st, Prefix: "honeypot:network"},
  51. affirmation: affirmation,
  52. body: body,
  53. title: title,
  54. lg: lg.With("component", "honeypot/naive"),
  55. }, nil
  56. }
  57. type Impl struct {
  58. st store.Interface
  59. infos store.JSON[honeypot.Info]
  60. uaWeight store.JSON[int]
  61. networkWeight store.JSON[int]
  62. lg *slog.Logger
  63. affirmation, body, title spintax.Spintax
  64. }
  65. func (i *Impl) incrementUA(ctx context.Context, userAgent string) int {
  66. result, _ := i.uaWeight.Get(ctx, internal.SHA256sum(userAgent))
  67. result++
  68. i.uaWeight.Set(ctx, internal.SHA256sum(userAgent), result, time.Hour)
  69. return result
  70. }
  71. func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
  72. result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
  73. result++
  74. i.networkWeight.Set(ctx, internal.SHA256sum(network), result, time.Hour)
  75. return result
  76. }
  77. func (i *Impl) CheckUA() checker.Impl {
  78. return checker.Func(func(r *http.Request) (bool, error) {
  79. result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
  80. if result >= 25 {
  81. return true, nil
  82. }
  83. return false, nil
  84. })
  85. }
  86. func (i *Impl) CheckNetwork() checker.Impl {
  87. return checker.Func(func(r *http.Request) (bool, error) {
  88. result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
  89. if result >= 25 {
  90. return true, nil
  91. }
  92. return false, nil
  93. })
  94. }
  95. func (i *Impl) Hash() string {
  96. return internal.SHA256sum("naive honeypot")
  97. }
  98. func (i *Impl) makeAffirmations() []string {
  99. count := rand.IntN(5) + 1
  100. var result []string
  101. for j := 0; j < count; j++ {
  102. result = append(result, i.affirmation.Spin())
  103. }
  104. return result
  105. }
  106. func (i *Impl) makeSpins() []string {
  107. count := rand.IntN(5) + 1
  108. var result []string
  109. for j := 0; j < count; j++ {
  110. result = append(result, i.body.Spin())
  111. }
  112. return result
  113. }
  114. func (i *Impl) makeTitle() string {
  115. return i.title.Spin()
  116. }
  117. func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  118. t0 := time.Now()
  119. lg := internal.GetRequestLogger(i.lg, r)
  120. id := r.PathValue("id")
  121. if id == "" {
  122. id = uuid.NewString()
  123. }
  124. realIP, _ := internal.RealIP(r)
  125. if !realIP.IsValid() {
  126. realIP = netip.MustParseAddr(r.Header.Get("X-Real-Ip"))
  127. }
  128. network, ok := internal.ClampIP(realIP)
  129. if !ok {
  130. lg.Error("clampIP failed", "output", network, "ok", ok)
  131. http.Error(w, "The cake is a lie", http.StatusTeapot)
  132. return
  133. }
  134. networkCount := i.incrementNetwork(r.Context(), network.String())
  135. uaCount := i.incrementUA(r.Context(), r.UserAgent())
  136. stage := r.PathValue("stage")
  137. if stage == "init" {
  138. lg.Debug("found new entrance point", "id", id, "stage", stage, "userAgent", r.UserAgent(), "clampedIP", network)
  139. } else {
  140. switch {
  141. case networkCount%256 == 0, uaCount%256 == 0:
  142. lg.Warn("found possible crawler", "id", id, "network", network)
  143. }
  144. }
  145. spins := i.makeSpins()
  146. affirmations := i.makeAffirmations()
  147. title := i.makeTitle()
  148. var links []link
  149. for _, affirmation := range affirmations {
  150. links = append(links, link{
  151. href: uuid.NewString(),
  152. body: affirmation,
  153. })
  154. }
  155. templ.Handler(
  156. base(title, i.maze(spins, links)),
  157. templ.WithStreaming(),
  158. templ.WithStatus(http.StatusOK),
  159. ).ServeHTTP(w, r)
  160. t1 := time.Since(t0)
  161. honeypot.Timings.WithLabelValues("naive").Observe(float64(t1.Milliseconds()))
  162. }
  163. type link struct {
  164. href string
  165. body string
  166. }