From 2ff12869cd61146f93091efbfa4afc3691cf2af5 Mon Sep 17 00:00:00 2001 From: bel Date: Thu, 20 Nov 2025 08:28:25 -0700 Subject: [PATCH] impl cidr as password:CIDR:http://target --- config/config.go | 6 ++++-- go.mod | 1 + go.sum | 7 +++++++ server/proxy.go | 6 ++++++ server/server.go | 41 +++++++++++++++++++++++++++++++++++++++++ server/server_test.go | 29 +++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 6873ab5..392f719 100755 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ import ( type Proxy struct { Auth string + From string To string } @@ -60,14 +61,15 @@ func GetRoutes() map[string]Proxy { s := conf.Get("proxy2").GetString() var dict map[string]string if err := yaml.Unmarshal([]byte(s), &dict); err == nil && len(s) > 0 { - pattern := regexp.MustCompile(`(([^:]*):)?([a-z0-9]*:.*)`) + pattern := regexp.MustCompile(`(([^:]*):)?(([^:]*):)?([a-z0-9]*:.*)`) result := map[string]Proxy{} for k, v := range dict { submatches := pattern.FindAllStringSubmatch(v, -1) log.Printf("%+v", submatches) result[k] = Proxy{ Auth: submatches[0][2], - To: submatches[0][3], + From: submatches[0][4], + To: submatches[0][5], } } return result diff --git a/go.mod b/go.mod index 57343c7..e208937 100644 --- a/go.mod +++ b/go.mod @@ -13,5 +13,6 @@ require gopkg.in/yaml.v2 v2.4.0 require ( github.com/kr/pretty v0.1.0 // indirect + github.com/yl2chen/cidranger v1.0.2 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index 8e5e260..c357619 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ gitea.inhome.blapointe.com/local/args v0.0.0-20240109214601-658deda479a4 h1:4qBH gitea.inhome.blapointe.com/local/args v0.0.0-20240109214601-658deda479a4/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc= gitea.inhome.blapointe.com/local/logb v0.0.0-20231109150430-1221d87a6dbc h1:u3akQkq12V8xWXlcDgjZxIK6hqo6f1eHd9KOxAKMoKc= gitea.inhome.blapointe.com/local/logb v0.0.0-20231109150430-1221d87a6dbc/go.mod h1:KwilawX4UgD4HxSJAVFEzkuckrnHeQrd49KwUX6GpYU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -9,10 +10,16 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/server/proxy.go b/server/proxy.go index 341160f..51a10b5 100755 --- a/server/proxy.go +++ b/server/proxy.go @@ -58,6 +58,12 @@ func (s *Server) lookupAuth(host string) (string, error) { return v.String(), err } +func (s *Server) lookupFrom(host string) (string, error) { + v := packable.NewString() + err := s.db.Get(nsRouting, host+"//from", v) + return v.String(), err +} + func mapKey(host string) string { host = strings.Split(host, ".")[0] host = strings.Split(host, ":")[0] diff --git a/server/server.go b/server/server.go index 923b9e1..ba4752c 100755 --- a/server/server.go +++ b/server/server.go @@ -12,6 +12,7 @@ import ( "net" "net/http" "net/url" + "regexp" "strconv" "strings" "time" @@ -62,6 +63,9 @@ func (s *Server) Route(src string, dst config.Proxy) error { if err != nil { return err } + if err := s.db.Set(nsRouting, src+"//from", packable.NewString(dst.From)); err != nil { + return err + } if err := s.db.Set(nsRouting, src+"//auth", packable.NewString(dst.Auth)); err != nil { return err } @@ -167,12 +171,44 @@ func (s *Server) Pre(foo http.HandlerFunc) http.HandlerFunc { log.Printf("failed to auth: expected %q but got %q", auth, p) w.Header().Set("WWW-Authenticate", "Basic") http.Error(w, "unexpected basic auth", http.StatusUnauthorized) + } else if from, err := s.lookupFrom(mapKey(r.Host)); err != nil { + log.Printf("failed to lookup from for %s (%s): %v", r.Host, mapKey(r.Host), err) + http.Error(w, err.Error(), http.StatusBadGateway) + } else if err := assertFrom(from, r.RemoteAddr); err != nil { + log.Printf("failed to from: expected %q but got %q: %v", from, r.RemoteAddr, err) + http.Error(w, "unexpected from", http.StatusUnauthorized) } else { foo(w, r) } } } +func assertFrom(from, remoteAddr string) error { + if from == "" { + return nil + } + + pattern := regexp.MustCompile(`[0-9](:[0-9]+)$`).FindStringSubmatchIndex(remoteAddr) + if len(pattern) == 4 { + remoteAddr = remoteAddr[:pattern[2]] + } + + remoteIP := net.ParseIP(remoteAddr) + if remoteIP == nil { + return fmt.Errorf("cannot parse remote %q", remoteAddr) + } + + _, net, err := net.ParseCIDR(from) + if err != nil { + panic(err) + } + if net.Contains(remoteIP) { + return nil + } + + return fmt.Errorf("expected like %q but got like %q", from, remoteAddr) +} + func withMeta(w http.ResponseWriter, r *http.Request) (*http.Request, func()) { meta := map[string]string{ "ts": strconv.FormatInt(time.Now().Unix(), 10), @@ -212,14 +248,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) List(w http.ResponseWriter) { keys := s.db.Keys(nsRouting) hostURL := map[string]string{} + hostFrom := map[string]string{} for _, key := range keys { u, _ := s.lookup(key) if u != nil && strings.TrimSuffix(key, "//auth") == key { hostURL[key] = u.String() } + if u != nil && strings.TrimSuffix(key, "//from") == key { + hostFrom[key] = u.String() + } } json.NewEncoder(w).Encode(map[string]any{ "hostsToURLs": hostURL, + "hostsToFrom": hostFrom, }) } diff --git a/server/server_test.go b/server/server_test.go index 2346919..c5597e7 100755 --- a/server/server_test.go +++ b/server/server_test.go @@ -105,3 +105,32 @@ func TestCORS(t *testing.T) { } }) } + +func TestAssertFrom(t *testing.T) { + cases := map[string]struct { + from string + remote string + err bool + }{ + "empty": {}, + "ipv6 localhost": { + from: "::1/128", + remote: "::1:12345", + }, + "ipv4 localhost": { + from: "127.0.0.1/32", + remote: "127.0.0.1:12345", + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + err := assertFrom(c.from, c.remote) + got := err != nil + if got != c.err { + t.Errorf("expected err=%v but got %v", c.err, err) + } + }) + } +}