Skip to content

Commit 4881681

Browse files
Christ Borgeshepelyuk
authored andcommitted
feat: to tackle proxies, reads xforwardedfor first then remoteaddr
1 parent 7804180 commit 4881681

File tree

3 files changed

+72
-15
lines changed

3 files changed

+72
-15
lines changed

.traefik.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ import: github.com/traefik-plugins/traefikgeoip2
88

99
testData:
1010
dbPath: 'GeoLite2-Country.mmdb'
11+
preferXForwardedForHeader: false

middleware.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ func ResetLookup() {
2121

2222
// Config the plugin configuration.
2323
type Config struct {
24-
DBPath string `json:"dbPath,omitempty"`
24+
DBPath string `json:"dbPath,omitempty"`
25+
PreferXForwardedForHeader bool
2526
}
2627

2728
// CreateConfig creates the default plugin configuration.
@@ -33,17 +34,19 @@ func CreateConfig() *Config {
3334

3435
// TraefikGeoIP2 a traefik geoip2 plugin.
3536
type TraefikGeoIP2 struct {
36-
next http.Handler
37-
name string
37+
next http.Handler
38+
name string
39+
preferXForwardedForHeader bool
3840
}
3941

4042
// New created a new TraefikGeoIP2 plugin.
4143
func New(_ context.Context, next http.Handler, cfg *Config, name string) (http.Handler, error) {
4244
if _, err := os.Stat(cfg.DBPath); err != nil {
4345
log.Printf("[geoip2] DB not found: db=%s, name=%s, err=%v", cfg.DBPath, name, err)
4446
return &TraefikGeoIP2{
45-
next: next,
46-
name: name,
47+
next: next,
48+
name: name,
49+
preferXForwardedForHeader: cfg.PreferXForwardedForHeader,
4750
}, nil
4851
}
4952

@@ -68,8 +71,9 @@ func New(_ context.Context, next http.Handler, cfg *Config, name string) (http.H
6871
}
6972

7073
return &TraefikGeoIP2{
71-
next: next,
72-
name: name,
74+
next: next,
75+
name: name,
76+
preferXForwardedForHeader: cfg.PreferXForwardedForHeader,
7377
}, nil
7478
}
7579

@@ -83,12 +87,7 @@ func (mw *TraefikGeoIP2) ServeHTTP(reqWr http.ResponseWriter, req *http.Request)
8387
return
8488
}
8589

86-
ipStr := req.RemoteAddr
87-
tmp, _, err := net.SplitHostPort(ipStr)
88-
if err == nil {
89-
ipStr = tmp
90-
}
91-
90+
ipStr := getClientIP(req, mw.preferXForwardedForHeader)
9291
res, err := lookup(net.ParseIP(ipStr))
9392
if err != nil {
9493
log.Printf("[geoip2] Unable to find: ip=%s, err=%v", ipStr, err)
@@ -106,3 +105,22 @@ func (mw *TraefikGeoIP2) ServeHTTP(reqWr http.ResponseWriter, req *http.Request)
106105

107106
mw.next.ServeHTTP(reqWr, req)
108107
}
108+
109+
func getClientIP(req *http.Request, preferXForwardedForHeader bool) string {
110+
if preferXForwardedForHeader {
111+
// Check X-Forwarded-For header first
112+
forwardedFor := req.Header.Get("X-Forwarded-For")
113+
if forwardedFor != "" {
114+
ips := strings.Split(forwardedFor, ",")
115+
return strings.TrimSpace(ips[0])
116+
}
117+
}
118+
119+
// If X-Forwarded-For is not present or retrieval is not enabled, fallback to RemoteAddr
120+
remoteAddr := req.RemoteAddr
121+
tmp, _, err := net.SplitHostPort(remoteAddr)
122+
if err == nil {
123+
remoteAddr = tmp
124+
}
125+
return remoteAddr
126+
}

middleware_test.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111
)
1212

1313
const (
14-
ValidIP = "188.193.88.199"
15-
ValidIPNoCity = "20.1.184.61"
14+
ValidIP = "188.193.88.199"
15+
ValidAlternateIP = "188.193.88.200"
16+
ValidIPNoCity = "20.1.184.61"
1617
)
1718

1819
func TestGeoIPConfig(t *testing.T) {
@@ -123,6 +124,43 @@ func TestGeoIPFromRemoteAddr(t *testing.T) {
123124
assertHeader(t, req, mw.IPAddressHeader, "qwerty")
124125
}
125126

127+
func TestGeoIPFromXForwardedFor(t *testing.T) {
128+
mwCfg := mw.CreateConfig()
129+
mwCfg.DBPath = "./GeoLite2-City.mmdb"
130+
mwCfg.PreferXForwardedForHeader = true
131+
132+
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
133+
mw.ResetLookup()
134+
instance, _ := mw.New(context.TODO(), next, mwCfg, "traefik-geoip2")
135+
136+
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
137+
req.RemoteAddr = fmt.Sprintf("%s:9999", ValidIP)
138+
req.Header.Set("X-Forwarded-For", ValidAlternateIP)
139+
instance.ServeHTTP(httptest.NewRecorder(), req)
140+
assertHeader(t, req, mw.CountryHeader, "DE")
141+
assertHeader(t, req, mw.RegionHeader, "BY")
142+
assertHeader(t, req, mw.CityHeader, "Munich")
143+
assertHeader(t, req, mw.IPAddressHeader, ValidAlternateIP)
144+
145+
req = httptest.NewRequest(http.MethodGet, "http://localhost", nil)
146+
req.RemoteAddr = fmt.Sprintf("%s:9999", ValidIP)
147+
req.Header.Set("X-Forwarded-For", ValidAlternateIP+",188.193.88.100")
148+
instance.ServeHTTP(httptest.NewRecorder(), req)
149+
assertHeader(t, req, mw.CountryHeader, "DE")
150+
assertHeader(t, req, mw.RegionHeader, "BY")
151+
assertHeader(t, req, mw.CityHeader, "Munich")
152+
assertHeader(t, req, mw.IPAddressHeader, ValidAlternateIP)
153+
154+
req = httptest.NewRequest(http.MethodGet, "http://localhost", nil)
155+
req.RemoteAddr = ValidIP + ":9999"
156+
req.Header.Set("X-Forwarded-For", "qwerty")
157+
instance.ServeHTTP(httptest.NewRecorder(), req)
158+
assertHeader(t, req, mw.CountryHeader, mw.Unknown)
159+
assertHeader(t, req, mw.RegionHeader, mw.Unknown)
160+
assertHeader(t, req, mw.CityHeader, mw.Unknown)
161+
assertHeader(t, req, mw.IPAddressHeader, "qwerty")
162+
}
163+
126164
func TestGeoIPCountryDBFromRemoteAddr(t *testing.T) {
127165
mwCfg := mw.CreateConfig()
128166
mwCfg.DBPath = "./GeoLite2-Country.mmdb"

0 commit comments

Comments
 (0)