Add routing IP rule matcher
Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
e5024d5d0a
commit
679def0151
4 changed files with 146 additions and 6 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/traefik/traefik/v2/pkg/ip"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
|
||||
"github.com/vulcand/predicate"
|
||||
|
@ -16,6 +17,7 @@ var funcs = map[string]func(*mux.Route, ...string) error{
|
|||
"Host": host,
|
||||
"HostHeader": host,
|
||||
"HostRegexp": hostRegexp,
|
||||
"ClientIP": clientIP,
|
||||
"Path": path,
|
||||
"PathPrefix": pathPrefix,
|
||||
"Method": methods,
|
||||
|
@ -155,6 +157,27 @@ func host(route *mux.Route, hosts ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func clientIP(route *mux.Route, clientIPs ...string) error {
|
||||
checker, err := ip.NewChecker(clientIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
|
||||
}
|
||||
|
||||
strategy := ip.RemoteAddrStrategy{}
|
||||
|
||||
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
|
||||
ok, err := checker.Contains(strategy.GetIP(req))
|
||||
if err != nil {
|
||||
log.FromContext(req.Context()).Warnf("\"ClientIP\" matcher: could not match remote address : %w", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return ok
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostRegexp(route *mux.Route, hosts ...string) error {
|
||||
router := route.Subrouter()
|
||||
for _, host := range hosts {
|
||||
|
|
|
@ -17,6 +17,7 @@ func Test_addRoute(t *testing.T) {
|
|||
desc string
|
||||
rule string
|
||||
headers map[string]string
|
||||
remoteAddr string
|
||||
expected map[string]int
|
||||
expectedError bool
|
||||
}{
|
||||
|
@ -519,6 +520,112 @@ func Test_addRoute(t *testing.T) {
|
|||
"http://plopi/a": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ClientIP empty",
|
||||
rule: "ClientIP(``)",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Invalid ClientIP",
|
||||
rule: "ClientIP(`invalid`)",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Non matching ClientIP",
|
||||
rule: "ClientIP(`10.10.1.1`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Non matching IPv6",
|
||||
rule: "ClientIP(`10::10`)",
|
||||
remoteAddr: "::1",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP",
|
||||
rule: "ClientIP(`10.0.0.0`)",
|
||||
remoteAddr: "10.0.0.0:8456",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IPv6",
|
||||
rule: "ClientIP(`10::10`)",
|
||||
remoteAddr: "10::10",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP among several IP",
|
||||
rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Non Matching IP with CIDR",
|
||||
rule: "ClientIP(`11.0.0.0/24`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Non Matching IPv6 with CIDR",
|
||||
rule: "ClientIP(`11::/16`)",
|
||||
remoteAddr: "10::",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP with CIDR",
|
||||
rule: "ClientIP(`10.0.0.0/16`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IPv6 with CIDR",
|
||||
rule: "ClientIP(`10::/16`)",
|
||||
remoteAddr: "10::10",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP among several CIDR",
|
||||
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP among non matching CIDR and matching IP",
|
||||
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Matching IP among matching CIDR and non matching IP",
|
||||
rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)",
|
||||
remoteAddr: "10.0.0.0",
|
||||
expected: map[string]int{
|
||||
"http://tchouk/toto": http.StatusOK,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -545,6 +652,10 @@ func Test_addRoute(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
|
||||
|
||||
// Useful for the ClientIP matcher
|
||||
req.RemoteAddr = test.remoteAddr
|
||||
|
||||
for key, value := range test.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue