diff options
| author | Xe Iaso <me@xeiaso.net> | 2025-04-27 12:35:45 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2025-04-27 12:42:58 -0400 |
| commit | ef94cbcc7f9f90ef5c238413ee3305c305743a42 (patch) | |
| tree | c41253eefe23a91b83cec041c9ee0e86bac53da5 /cmd | |
| parent | 972cc990716c8593fc1f1d7061e6b707c6bccc51 (diff) | |
| download | x-ef94cbcc7f9f90ef5c238413ee3305c305743a42.tar.xz x-ef94cbcc7f9f90ef5c238413ee3305c305743a42.zip | |
feat(relayd): store and query TLS fingerprints
Release-Status: cut
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/relayd/main.go | 93 | ||||
| -rw-r--r-- | cmd/relayd/relayd.service | 6 |
2 files changed, 94 insertions, 5 deletions
diff --git a/cmd/relayd/main.go b/cmd/relayd/main.go index 3185b37..a658673 100644 --- a/cmd/relayd/main.go +++ b/cmd/relayd/main.go @@ -3,6 +3,8 @@ package main import ( "context" "crypto/tls" + "database/sql" + "errors" "flag" "fmt" "log" @@ -19,16 +21,20 @@ import ( "syscall" "time" + "github.com/avct/uasurfer" "github.com/google/uuid" + "github.com/jackc/puddle/v2" + _ "modernc.org/sqlite" "within.website/x/internal" ) var ( - bind = flag.String("bind", ":3004", "port to listen on") - certDir = flag.String("cert-dir", "/xe/pki", "where to read mounted certificates from") - certFname = flag.String("cert-fname", "tls.crt", "certificate filename") - keyFname = flag.String("key-fname", "tls.key", "key filename") - proxyTo = flag.String("proxy-to", "http://localhost:5000", "where to reverse proxy to") + bind = flag.String("bind", ":3004", "port to listen on") + certDir = flag.String("cert-dir", "/xe/pki", "where to read mounted certificates from") + certFname = flag.String("cert-fname", "tls.crt", "certificate filename") + fpDatabase = flag.String("fp-database", "", "location of fingerprint database") + keyFname = flag.String("key-fname", "tls.key", "key filename") + proxyTo = flag.String("proxy-to", "http://localhost:5000", "where to reverse proxy to") ) func main() { @@ -38,6 +44,7 @@ func main() { "bind", *bind, "cert-dir", *certDir, "cert-fname", *certFname, + "fp-database", *fpDatabase, "key-fname", *keyFname, "proxy-to", *proxyTo, ) @@ -82,6 +89,24 @@ func main() { log.Fatal(err) } + var db *sql.DB + + if fpDatabase != nil { + var err error + db, err = sql.Open("sqlite", *fpDatabase) + if err != nil { + log.Fatal(err) + } + if err := db.Ping(); err != nil { + log.Fatal(err) + } + } + + uaParser, err := uaParserPool() + if err != nil { + log.Fatal(err) + } + h := httputil.NewSingleHostReverseProxy(u) oldDirector := h.Director @@ -117,6 +142,47 @@ func main() { // req.Header.Set("X-TCP-Fingerprint-JA4T", tcpFP.String()) // } + if uap, err := uaParser.Acquire(req.Context()); err == nil { + defer uap.Release() + + uasurfer.ParseUserAgent(req.UserAgent(), uap.Value()) + + vers := uap.Value().Browser.Version + + req.Header.Set("Xe-X-Relayd-UserAgent-Browser", uap.Value().Browser.Name.StringTrimPrefix()) + req.Header.Set("Xe-X-Relayd-UserAgent-Browser-Version", fmt.Sprintf("%d.%d.%d", vers.Major, vers.Minor, vers.Patch)) + req.Header.Set("Xe-X-Relayd-UserAgent-OS", uap.Value().OS.Name.StringTrimPrefix()) + req.Header.Set("Xe-X-Relayd-UserAgent-Platform", uap.Value().OS.Platform.StringTrimPrefix()) + } + + if ja4 := req.Header.Get("X-TLS-Fingerprint-JA4"); db != nil && ja4 != "" { + var application, userAgent, notes sql.NullString + if err := db.QueryRowContext(req.Context(), "SELECT application, user_agent_string, notes FROM fingerprints WHERE ja4_fingerprint = ?", ja4).Scan(&application, &userAgent, ¬es); err == nil { + slog.Debug("found a hit", "application", application, "userAgent", userAgent, "notes", notes) + if application.Valid { + req.Header.Set("Xe-X-Relayd-Ja4-Application", application.String) + } + + if userAgent.Valid { + req.Header.Set("Xe-X-Relayd-Ja4-UserAgent", userAgent.String) + } + + if notes.Valid { + req.Header.Set("Xe-X-Relayd-Ja4-Notes", notes.String) + } + } else if errors.Is(err, sql.ErrNoRows) { + userAgent := req.UserAgent() + notes := fmt.Sprintf("Observed via relayd on host %s at %s", req.Host, time.Now().Format(time.RFC3339)) + if _, err := db.ExecContext(req.Context(), "INSERT INTO fingerprints(user_agent_string, notes, ja4_fingerprint) VALUES (?, ?, ?)", userAgent, notes, ja4); err != nil { + slog.Error("can't insert fingerprint into database", "err", err) + } + + req.Header.Set("Xe-X-Relayd-New-Client", "true") + } else { + slog.Debug("can't read from database", "err", err) + } + } + req.Header.Set("X-Forwarded-Host", req.URL.Host) req.Header.Set("X-Forwarded-Proto", "https") req.Header.Set("X-Forwarded-Scheme", "https") @@ -215,3 +281,20 @@ func (kpr *keypairReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certifica return kpr.cert, nil } + +func uaParserPool() (*puddle.Pool[*uasurfer.UserAgent], error) { + cons := func(context.Context) (*uasurfer.UserAgent, error) { + return &uasurfer.UserAgent{}, nil + } + des := func(ua *uasurfer.UserAgent) { + ua.Reset() + } + + pool, err := puddle.NewPool(&puddle.Config[*uasurfer.UserAgent]{ + Constructor: cons, + Destructor: des, + MaxSize: 512, + }) + + return pool, err +} diff --git a/cmd/relayd/relayd.service b/cmd/relayd/relayd.service index cb6fc07..519d36a 100644 --- a/cmd/relayd/relayd.service +++ b/cmd/relayd/relayd.service @@ -7,6 +7,12 @@ Restart=always RestartSec=30s EnvironmentFile=/etc/within.website/x/relayd.env LimitNOFILE=infinity +DynamicUser=true +CacheDirectory=relayd +CacheDirectoryMode=0755 +StateDirectory=relayd +StateDirectoryMode=0755 +ReadWritePaths=/run [Install] WantedBy=multi-user.target
\ No newline at end of file |
