Skip to content

Commit b0811fd

Browse files
Introduce ip allow list support for IPv4/IPv6 (#5)
1 parent 2384484 commit b0811fd

File tree

12 files changed

+140
-8
lines changed

12 files changed

+140
-8
lines changed

cmd/frpc/sub/http.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func init() {
3636
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
3737
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
3838
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
39+
httpCmd.PersistentFlags().StringVarP(&ipsAllowList, "ips_allow_list", "", "", "lists the rules to configure which IP addresses and subnet masks can access your client (e.g \"192.168.0.0/16, 255.255.0.0\")- IPv4/IPv6 support")
3940
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
4041
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
4142
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
@@ -70,6 +71,9 @@ var httpCmd = &cobra.Command{
7071
cfg.HostHeaderRewrite = hostHeaderRewrite
7172
cfg.UseEncryption = useEncryption
7273
cfg.UseCompression = useCompression
74+
if ipsAllowList != "" {
75+
cfg.IpsAllowList = strings.Split(ipsAllowList, ",")
76+
}
7377

7478
err = cfg.CheckForCli()
7579
if err != nil {

cmd/frpc/sub/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ var (
6464
subDomain string
6565
httpUser string
6666
httpPwd string
67+
ipsAllowList string
6768
locations string
6869
hostHeaderRewrite string
6970
role string

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/gorilla/mux v1.8.0
1414
github.com/gorilla/websocket v1.4.2
1515
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
16+
github.com/jpillora/ipfilter v1.2.7
1617
github.com/leodido/go-urn v1.2.1 // indirect
1718
github.com/onsi/ginkgo v1.16.4
1819
github.com/onsi/gomega v1.13.0
@@ -21,7 +22,7 @@ require (
2122
github.com/prometheus/client_golang v1.11.0
2223
github.com/rodaine/table v1.0.1
2324
github.com/spf13/cobra v1.1.3
24-
github.com/stretchr/testify v1.7.0
25+
github.com/stretchr/testify v1.8.0
2526
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
2627
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
2728
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect

go.sum

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
222222
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
223223
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
224224
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
225+
github.com/jpillora/ipfilter v1.2.7 h1:fB+fIa/VtgjOrHjkR3Sw47dHYhZGCae/dIWc/Vur++U=
226+
github.com/jpillora/ipfilter v1.2.7/go.mod h1:QS0miOgSqkxAsnTKLADlahASDOExe2K2pdoswGRt+FM=
225227
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
226228
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
227229
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -300,6 +302,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
300302
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
301303
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
302304
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
305+
github.com/phuslu/iploc v1.0.20220730 h1:Ly2Casvb9LVnaDg06RfkET6AwkMCUXrNANKJX40vsoE=
306+
github.com/phuslu/iploc v1.0.20220730/go.mod h1:gsgExGWldwv1AEzZm+Ki9/vGfyjkL33pbSr9HGpt2Xg=
303307
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
304308
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
305309
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -366,13 +370,15 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
366370
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
367371
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
368372
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
373+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
369374
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
370375
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
371376
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
372377
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
373378
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
374-
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
375-
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
379+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
380+
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
381+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
376382
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
377383
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
378384
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
@@ -381,6 +387,8 @@ github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mn
381387
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
382388
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
383389
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
390+
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
391+
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
384392
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
385393
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
386394
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
@@ -665,8 +673,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
665673
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
666674
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
667675
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
668-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
669676
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
677+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
678+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
670679
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
671680
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
672681
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

pkg/config/proxy.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ type HTTPProxyConf struct {
162162
Locations []string `ini:"locations" json:"locations"`
163163
HTTPUser string `ini:"http_user" json:"http_user"`
164164
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
165+
IpsAllowList []string `ini:"ips_allow_list" json:"ips_allow_list"`
165166
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
166167
Headers map[string]string `ini:"-" json:"headers"`
167168
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
@@ -760,6 +761,7 @@ func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
760761
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
761762
cfg.HTTPUser = pMsg.HTTPUser
762763
cfg.HTTPPwd = pMsg.HTTPPwd
764+
cfg.IpsAllowList = pMsg.IpsAllowList
763765
cfg.Headers = pMsg.Headers
764766
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
765767
}
@@ -774,6 +776,7 @@ func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
774776
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
775777
pMsg.HTTPUser = cfg.HTTPUser
776778
pMsg.HTTPPwd = cfg.HTTPPwd
779+
pMsg.IpsAllowList = cfg.IpsAllowList
777780
pMsg.Headers = cfg.Headers
778781
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
779782
}

pkg/msg/msg.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ type NewProxy struct {
102102
Locations []string `json:"locations,omitempty"`
103103
HTTPUser string `json:"http_user,omitempty"`
104104
HTTPPwd string `json:"http_pwd,omitempty"`
105+
IpsAllowList []string `json:"ips_allow_list,omitempty"`
105106
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
106107
Headers map[string]string `json:"headers,omitempty"`
107108
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`

pkg/util/vhost/http.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
124124
// Register register the route config to reverse proxy
125125
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
126126
func (rp *HTTPReverseProxy) Register(routeCfg RouteConfig) error {
127-
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, routeCfg.RouteByHTTPUser, &routeCfg)
127+
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, routeCfg.RouteByHTTPUser, routeCfg.IpsAllowList, &routeCfg)
128128
if err != nil {
129129
return err
130130
}
@@ -185,6 +185,26 @@ func (rp *HTTPReverseProxy) CheckAuth(domain, location, routeByHTTPUser, user, p
185185
return true
186186
}
187187

188+
// CheckClientOriginIpAddr to prevent IP spoofing, be sure to delete any pre-existing X-Forwarded-For header coming from the client or an untrusted proxy.
189+
func (rp *HTTPReverseProxy) CheckClientOriginIpAddr(domain, location, routeByHTTPUser, addr string) bool {
190+
if addr != "" {
191+
frpLog.Debug("Received client ip addr: %s", addr)
192+
ips := strings.Split(addr, ", ")
193+
if len(ips) > 1 {
194+
// Selecting the first ip in the list, it's safe to take it once we ensured the first ip cannot be set by untrusted proxies or the client
195+
addr = ips[0]
196+
}
197+
}
198+
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
199+
if ok {
200+
if vr.ipFilter != nil {
201+
frpLog.Debug("validating client origin ip %s", addr)
202+
return vr.ipFilter.Allowed(addr)
203+
}
204+
}
205+
return true
206+
}
207+
188208
// getVhost trys to get vhost router by route policy.
189209
func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (*Router, bool) {
190210
findRouter := func(inDomain, inLocation, inRouteByHTTPUser string) (*Router, bool) {
@@ -293,6 +313,17 @@ func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
293313
return
294314
}
295315

316+
// Identifying the originating IP address of a client connecting to a web server through a proxy server
317+
addr := req.Header.Get("X-Forwarded-For")
318+
if addr == "" {
319+
// For server direct access, remote address is in "IP:port" format
320+
addr, _, _ = net.SplitHostPort(req.RemoteAddr)
321+
}
322+
if !rp.CheckClientOriginIpAddr(domain, location, user, addr) {
323+
http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
324+
return
325+
}
326+
296327
newreq := rp.injectRequestInfoToCtx(req)
297328
if req.Method == http.MethodConnect {
298329
rp.connectHandler(rw, newreq)

pkg/util/vhost/router.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package vhost
22

33
import (
44
"errors"
5+
frpLog "github.com/fatedier/frp/pkg/util/log"
56
"sort"
67
"strings"
78
"sync"
9+
10+
"github.com/jpillora/ipfilter"
811
)
912

1013
var (
@@ -23,6 +26,7 @@ type Router struct {
2326
domain string
2427
location string
2528
httpUser string
29+
ipFilter *ipfilter.IPFilter
2630

2731
// store any object here
2832
payload interface{}
@@ -34,7 +38,7 @@ func NewRouters() *Routers {
3438
}
3539
}
3640

37-
func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
41+
func (r *Routers) Add(domain, location, httpUser string, ipsAllowList []string, payload interface{}) error {
3842
r.mutex.Lock()
3943
defer r.mutex.Unlock()
4044

@@ -51,10 +55,20 @@ func (r *Routers) Add(domain, location, httpUser string, payload interface{}) er
5155
vrs = make([]*Router, 0, 1)
5256
}
5357

58+
var ipFilter *ipfilter.IPFilter
59+
frpLog.Debug("adding allow list %s", ipsAllowList)
60+
if ipsAllowList != nil {
61+
ipFilter = ipfilter.New(ipfilter.Options{
62+
AllowedIPs: ipsAllowList,
63+
BlockByDefault: true,
64+
})
65+
}
66+
5467
vr := &Router{
5568
domain: domain,
5669
location: location,
5770
httpUser: httpUser,
71+
ipFilter: ipFilter,
5872
payload: payload,
5973
}
6074
vrs = append(vrs, vr)

pkg/util/vhost/vhost.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type RouteConfig struct {
8484
Username string
8585
Password string
8686
Headers map[string]string
87+
IpsAllowList []string
8788
RouteByHTTPUser string
8889

8990
CreateConnFn CreateConnFunc
@@ -98,12 +99,13 @@ func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err
9899
routeByHTTPUser: cfg.RouteByHTTPUser,
99100
rewriteHost: cfg.RewriteHost,
100101
userName: cfg.Username,
102+
ipsAllowList: cfg.IpsAllowList,
101103
passWord: cfg.Password,
102104
mux: v,
103105
accept: make(chan net.Conn),
104106
ctx: ctx,
105107
}
106-
err = v.registryRouter.Add(cfg.Domain, cfg.Location, cfg.RouteByHTTPUser, l)
108+
err = v.registryRouter.Add(cfg.Domain, cfg.Location, cfg.RouteByHTTPUser, cfg.IpsAllowList, l)
107109
if err != nil {
108110
return
109111
}
@@ -234,6 +236,7 @@ type Listener struct {
234236
rewriteHost string
235237
userName string
236238
passWord string
239+
ipsAllowList []string
237240
mux *Muxer // for closing Muxer
238241
accept chan net.Conn
239242
ctx context.Context

server/group/http.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (g *HTTPGroup) Register(
9393
// the first proxy in this group
9494
tmp := routeConfig // copy object
9595
tmp.CreateConnFn = g.createConn
96-
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, routeConfig.RouteByHTTPUser, &tmp)
96+
err = g.ctl.vhostRouter.Add(routeConfig.Domain, routeConfig.Location, routeConfig.RouteByHTTPUser, routeConfig.IpsAllowList, &tmp)
9797
if err != nil {
9898
return
9999
}

0 commit comments

Comments
 (0)