From 3916c45ec433257a693d3973a9a9695bf47959a5 Mon Sep 17 00:00:00 2001 From: mleku Date: Thu, 12 Dec 2024 20:09:57 +0000 Subject: [PATCH] enable automatic auth to pubkey with no-auth addresses --- cmd/realy/main.go | 1 + realy/config/config.go | 2 +- realy/handleAuth.go | 2 +- realy/handleCount.go | 12 ++-------- realy/handleEvent.go | 8 +------ realy/handleReq.go | 12 ++-------- realy/noauth.go | 50 ++++++++++++++++++++++++++++++++++++++++++ realy/version | 2 +- web/websocket.go | 4 ++-- 9 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 realy/noauth.go diff --git a/cmd/realy/main.go b/cmd/realy/main.go index 68d9e4a..ecf173b 100644 --- a/cmd/realy/main.go +++ b/cmd/realy/main.go @@ -66,6 +66,7 @@ func main() { r := &app.Relay{C: cfg, Store: storage} go app.MonitorResources(c) var server *realy.Server + log.I.S(cfg.NoAuthAddresses) if server, err = realy.NewServer(realy.ServerParams{ Ctx: c, Cancel: cancel, diff --git a/realy/config/config.go b/realy/config/config.go index a47feb0..7d6e384 100644 --- a/realy/config/config.go +++ b/realy/config/config.go @@ -37,7 +37,7 @@ type C struct { MemLimit no `env:"MEMLIMIT" default:"250000000" usage:"set memory limit, default is 250Mb"` UseCompact bo `env:"USE_COMPACT" default:"false" usage:"use the compact database encoding for the ratel event store"` Compression st `env:"COMPRESSION" default:"none" usage:"compress the database, [none|snappy|zstd]"` - NoAuthAddresses []st `env:"NO_AUTH_ADDRESSES" usage:"IP addresses that don't require auth (for such things as clients using the relay as a cache relay)'"` + NoAuthAddresses []st `env:"NO_AUTH_ADDRESSES" usage:"IP addresses that don't require auth, optionally add a hex pubkey after a ':' to make the address automatically populate the websocket authed pubkey (mainly for use with cache relay feature of nostrudel)"` // NWC st `env:"NWC" usage:"NWC connection string for relay to interact with an NWC enabled wallet"` // todo } diff --git a/realy/handleAuth.go b/realy/handleAuth.go index 2e98070..8b16f59 100644 --- a/realy/handleAuth.go +++ b/realy/handleAuth.go @@ -42,7 +42,7 @@ func (s *Server) handleAuth(ws *web.Socket, req by) (msg by) { if err = okenvelope.NewFrom(env.Event.ID, true, by{}).Write(ws); chk.E(err) { return } - log.D.F("%s authed to pubkey,%0x", ws.RealRemote(), env.Event.PubKey) + log.D.F("%s authed to pubkey %0x", ws.RealRemote(), env.Event.PubKey) ws.SetAuthed(st(env.Event.PubKey)) } } diff --git a/realy/handleCount.go b/realy/handleCount.go index ee51f96..cc3eef2 100644 --- a/realy/handleCount.go +++ b/realy/handleCount.go @@ -1,8 +1,6 @@ package realy import ( - "strings" - "realy.lol/context" "realy.lol/envelopes/authenvelope" "realy.lol/envelopes/closedenvelope" @@ -20,13 +18,7 @@ func (s *Server) handleCount(c context.T, ws *web.Socket, req by, store store.I) if !ok { return normalize.Restricted.F("this relay does not support NIP-45") } - var noAuth bo - for _, v := range s.noAuthAddresses { - if strings.HasPrefix(v, ws.RealRemote()) { - // we are not requiring auth from this address (should be private address) - noAuth = true - } - } + noAuth := s.checkNoAuth(ws) if !noAuth && (ws.AuthRequested() && len(ws.Authed()) == 0) { return by("awaiting auth for count") } @@ -43,7 +35,7 @@ func (s *Server) handleCount(c context.T, ws *web.Socket, req by, store store.I) return normalize.Error.F("COUNT has no ") } allowed := env.Filters - if accepter, ok := s.relay.(relay.ReqAcceptor); ok { + if accepter, ok := s.relay.(relay.ReqAcceptor); ok && !noAuth { var accepted bo allowed, accepted = accepter.AcceptReq(c, ws.Req(), env.Subscription.T, env.Filters, by(ws.Authed())) diff --git a/realy/handleEvent.go b/realy/handleEvent.go index a9c142e..3b3099f 100644 --- a/realy/handleEvent.go +++ b/realy/handleEvent.go @@ -22,13 +22,7 @@ import ( func (s *Server) handleEvent(c cx, ws *web.Socket, req by, sto store.I) (msg by) { log.T.F("handleEvent %s %s %v", ws.RealRemote(), req, s.noAuthAddresses) - var noAuth bo - for _, v := range s.noAuthAddresses { - if strings.HasPrefix(v, ws.RealRemote()) { - // we are not requiring auth from this address (should be private address) - noAuth = true - } - } + noAuth := s.checkNoAuth(ws) if !noAuth && ws.AuthRequested() && len(ws.Authed()) == 0 { return by("awaiting auth for event") } diff --git a/realy/handleReq.go b/realy/handleReq.go index 012af1d..ea48710 100644 --- a/realy/handleReq.go +++ b/realy/handleReq.go @@ -3,8 +3,6 @@ package realy import ( "errors" "sort" - "strings" - "github.com/dgraph-io/badger/v4" "realy.lol/envelopes/authenvelope" @@ -25,13 +23,7 @@ import ( ) func (s *Server) handleReq(c cx, ws *web.Socket, req by, sto store.I) (r by) { - var noAuth bo - for _, v := range s.noAuthAddresses { - if strings.HasPrefix(v, ws.RealRemote()) { - // we are not requiring auth from this address (should be private address) - noAuth = true - } - } + noAuth := s.checkNoAuth(ws) if !noAuth && ws.AuthRequested() && len(ws.Authed()) == 0 { return by("awaiting auth for req") } @@ -45,7 +37,7 @@ func (s *Server) handleReq(c cx, ws *web.Socket, req by, sto store.I) (r by) { log.I.F("extra '%s'", rem) } allowed := env.Filters - if accepter, ok := s.relay.(relay.ReqAcceptor); ok { + if accepter, ok := s.relay.(relay.ReqAcceptor); ok && !noAuth { var accepted bo allowed, accepted = accepter.AcceptReq(c, ws.Req(), env.Subscription.T, env.Filters, by(ws.Authed())) diff --git a/realy/noauth.go b/realy/noauth.go new file mode 100644 index 0000000..d77406a --- /dev/null +++ b/realy/noauth.go @@ -0,0 +1,50 @@ +package realy + +import ( + "realy.lol/ec/schnorr" + "strings" + "realy.lol/web" + "realy.lol/hex" +) + +// checkNoAuth examines whether the server has been configured to allow the +// client to have access based on their IP address, and optionally automatically +// stores their public key as the authed pubkey, thus automatically assuming that +// connections from this address are for a client using a given address. +// +// This is somewhat insecure, in that other apps that connect to this relay will +// be treated as though they are logged in with the provided key, meaning a +// malware or other malicious nostr-savvy app that knows the relay is connected +// to an address that is whitelisted, could then gain access to privileged +// messages. But this is only a small vulnerability, said attacker will not be +// able to sign for the key to fabricate events unless the signer is +// misconfigured. +func (s *Server) checkNoAuth(ws *web.Socket) (noAuth bo) { + if len(ws.Authed()) == 0 { + for _, v := range s.noAuthAddresses { + if strings.HasPrefix(v, ws.RealRemote()) { + // we are not requiring auth from this address (should be private address) + if strings.Contains(v, ":") { + // If there is a colon in the field then the second field should be a hex encoded + // public key. Anything else will be silently ignored. + split := strings.Split(v, ":") + if len(split) == 2 { + var k by + var err er + // check that the key is proper + if k, err = hex.Dec(split[1]); !chk.E(err) { + if len(k) == schnorr.PubKeyBytesLen { + log.I.S("whitelisted address has pubkey", split[1]) + ws.SetAuthed(st(k)) + noAuth = true + } + } + } + } else { + noAuth = true + } + } + } + } + return +} diff --git a/realy/version b/realy/version index 90f1642..ab74b63 100644 --- a/realy/version +++ b/realy/version @@ -1 +1 @@ -v1.3.6 \ No newline at end of file +v1.3.7 \ No newline at end of file diff --git a/web/websocket.go b/web/websocket.go index a6e2b67..2ee7469 100644 --- a/web/websocket.go +++ b/web/websocket.go @@ -39,7 +39,7 @@ func (ws *Socket) RequestAuth() { ws.authRequested.Store(true) } func (ws *Socket) setRemoteFromReq(r *http.Request) { var rr string - // reverse proxy should populate this field so we see the remote not the proxy + // reverse proxy should populate this field, so we see the remote not the proxy rem := r.Header.Get("X-Forwarded-For") if rem != "" { splitted := strings.Split(rem, " ") @@ -49,7 +49,7 @@ func (ws *Socket) setRemoteFromReq(r *http.Request) { if len(splitted) == 2 { rr = splitted[1] } - // in case upstream doesn't set this or we are directly listening instead of + // in case upstream doesn't set this, or we are directly listening instead of // via reverse proxy or just if the header field is missing, put the // connection remote address into the websocket state data. if rr == "" {