| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- package naive
- import (
- "context"
- _ "embed"
- "fmt"
- "log/slog"
- "math/rand/v2"
- "net/http"
- "net/netip"
- "time"
- "github.com/TecharoHQ/anubis/internal"
- "github.com/TecharoHQ/anubis/internal/honeypot"
- "github.com/TecharoHQ/anubis/lib/policy/checker"
- "github.com/TecharoHQ/anubis/lib/store"
- "github.com/a-h/templ"
- "github.com/google/uuid"
- "github.com/nikandfor/spintax"
- )
- //go:generate go tool github.com/a-h/templ/cmd/templ generate
- // 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.
- //
- // It feels somewhat poetic to use spammer technology in Anubis.
- //
- // [1]: https://outboundly.ai/blogs/what-is-spintax-and-how-to-use-it/
- //
- //go:embed spintext.txt
- var spintext string
- //go:embed titles.txt
- var titles string
- //go:embed affirmations.txt
- var affirmations string
- func New(st store.Interface, lg *slog.Logger) (*Impl, error) {
- affirmation, err := spintax.Parse(affirmations)
- if err != nil {
- return nil, fmt.Errorf("can't parse affirmations: %w", err)
- }
- body, err := spintax.Parse(spintext)
- if err != nil {
- return nil, fmt.Errorf("can't parse bodies: %w", err)
- }
- title, err := spintax.Parse(titles)
- if err != nil {
- return nil, fmt.Errorf("can't parse titles: %w", err)
- }
- lg.Debug("initialized basic bullshit generator", "affirmations", affirmation.Count(), "bodies", body.Count(), "titles", title.Count())
- return &Impl{
- st: st,
- infos: store.JSON[honeypot.Info]{Underlying: st, Prefix: "honeypot:info"},
- uaWeight: store.JSON[int]{Underlying: st, Prefix: "honeypot:user-agent"},
- networkWeight: store.JSON[int]{Underlying: st, Prefix: "honeypot:network"},
- affirmation: affirmation,
- body: body,
- title: title,
- lg: lg.With("component", "honeypot/naive"),
- }, nil
- }
- type Impl struct {
- st store.Interface
- infos store.JSON[honeypot.Info]
- uaWeight store.JSON[int]
- networkWeight store.JSON[int]
- lg *slog.Logger
- affirmation, body, title spintax.Spintax
- }
- func (i *Impl) incrementUA(ctx context.Context, userAgent string) int {
- result, _ := i.uaWeight.Get(ctx, internal.SHA256sum(userAgent))
- result++
- i.uaWeight.Set(ctx, internal.SHA256sum(userAgent), result, time.Hour)
- return result
- }
- func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
- result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
- result++
- i.networkWeight.Set(ctx, internal.SHA256sum(network), result, time.Hour)
- return result
- }
- func (i *Impl) CheckUA() checker.Impl {
- return checker.Func(func(r *http.Request) (bool, error) {
- result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
- if result >= 25 {
- return true, nil
- }
- return false, nil
- })
- }
- func (i *Impl) CheckNetwork() checker.Impl {
- return checker.Func(func(r *http.Request) (bool, error) {
- result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
- if result >= 25 {
- return true, nil
- }
- return false, nil
- })
- }
- func (i *Impl) Hash() string {
- return internal.SHA256sum("naive honeypot")
- }
- func (i *Impl) makeAffirmations() []string {
- count := rand.IntN(5) + 1
- var result []string
- for j := 0; j < count; j++ {
- result = append(result, i.affirmation.Spin())
- }
- return result
- }
- func (i *Impl) makeSpins() []string {
- count := rand.IntN(5) + 1
- var result []string
- for j := 0; j < count; j++ {
- result = append(result, i.body.Spin())
- }
- return result
- }
- func (i *Impl) makeTitle() string {
- return i.title.Spin()
- }
- func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- t0 := time.Now()
- lg := internal.GetRequestLogger(i.lg, r)
- id := r.PathValue("id")
- if id == "" {
- id = uuid.NewString()
- }
- realIP, _ := internal.RealIP(r)
- if !realIP.IsValid() {
- realIP = netip.MustParseAddr(r.Header.Get("X-Real-Ip"))
- }
- network, ok := internal.ClampIP(realIP)
- if !ok {
- lg.Error("clampIP failed", "output", network, "ok", ok)
- http.Error(w, "The cake is a lie", http.StatusTeapot)
- return
- }
- networkCount := i.incrementNetwork(r.Context(), network.String())
- uaCount := i.incrementUA(r.Context(), r.UserAgent())
- stage := r.PathValue("stage")
- if stage == "init" {
- lg.Debug("found new entrance point", "id", id, "stage", stage, "userAgent", r.UserAgent(), "clampedIP", network)
- } else {
- switch {
- case networkCount%256 == 0, uaCount%256 == 0:
- lg.Warn("found possible crawler", "id", id, "network", network)
- }
- }
- spins := i.makeSpins()
- affirmations := i.makeAffirmations()
- title := i.makeTitle()
- var links []link
- for _, affirmation := range affirmations {
- links = append(links, link{
- href: uuid.NewString(),
- body: affirmation,
- })
- }
- templ.Handler(
- base(title, i.maze(spins, links)),
- templ.WithStreaming(),
- templ.WithStatus(http.StatusOK),
- ).ServeHTTP(w, r)
- t1 := time.Since(t0)
- honeypot.Timings.WithLabelValues("naive").Observe(float64(t1.Milliseconds()))
- }
- type link struct {
- href string
- body string
- }
|