mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-03 13:52:42 +00:00
e0f4468b03
Signed-off-by: Xe Iaso <me@xeiaso.net>
196 lines
4.3 KiB
Go
196 lines
4.3 KiB
Go
package naive
|
|
|
|
import (
|
|
_ "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/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-infos"},
|
|
affirmation: affirmation,
|
|
body: body,
|
|
title: title,
|
|
lg: lg.With("component", "honeypot/naive"),
|
|
}, nil
|
|
}
|
|
|
|
type Impl struct {
|
|
st store.Interface
|
|
infos store.JSON[honeypot.Info]
|
|
lg *slog.Logger
|
|
|
|
affirmation, body, title spintax.Spintax
|
|
}
|
|
|
|
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) clampIP(addr netip.Addr) netip.Prefix {
|
|
fallback := netip.MustParsePrefix(addr.String() + "/32")
|
|
switch {
|
|
case addr.Is4() || addr.Is4In6():
|
|
result, err := addr.Prefix(24)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return result
|
|
|
|
case addr.Is6():
|
|
result, err := addr.Prefix(48)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return result
|
|
|
|
default:
|
|
return fallback
|
|
}
|
|
}
|
|
|
|
func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
t0 := time.Now()
|
|
|
|
id := r.PathValue("id")
|
|
if id == "" {
|
|
id = uuid.NewString()
|
|
}
|
|
|
|
realIP, _ := internal.RealIP(r)
|
|
if !realIP.IsValid() {
|
|
i.lg.Error("the real IP is somehow invalid, bad middleware stack?")
|
|
http.Error(w, "The cake is a lie", http.StatusTeapot)
|
|
return
|
|
}
|
|
|
|
network := i.clampIP(realIP)
|
|
|
|
stage := r.PathValue("stage")
|
|
|
|
var info honeypot.Info
|
|
var err error
|
|
|
|
if stage == "init" {
|
|
i.lg.Debug("found new entrance point", "id", id, "userAgent", r.UserAgent(), "clampedIP", network)
|
|
|
|
info = honeypot.Info{
|
|
CreatedAt: time.Now(),
|
|
UserAgent: r.UserAgent(),
|
|
IPAddress: realIP.String(),
|
|
HitCount: 1,
|
|
}
|
|
|
|
i.infos.Set(r.Context(), network.String(), info, time.Hour)
|
|
} else {
|
|
info, err = i.infos.Get(r.Context(), network.String())
|
|
if err != nil {
|
|
info = honeypot.Info{
|
|
CreatedAt: time.Now(),
|
|
UserAgent: r.UserAgent(),
|
|
IPAddress: realIP.String(),
|
|
HitCount: 1,
|
|
}
|
|
i.infos.Set(r.Context(), network.String(), info, time.Hour)
|
|
} else {
|
|
info.HitCount++
|
|
i.infos.Set(r.Context(), network.String(), info, time.Hour)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|