Skip to content

Commit 28258e5

Browse files
committed
Use X-Real-IP to identify clients #213
1 parent 9125f5a commit 28258e5

File tree

7 files changed

+127
-14
lines changed

7 files changed

+127
-14
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ Professional support for Rdiffweb is available by contacting [IKUS Soft](https:/
107107

108108
# Changelog
109109

110+
## 2.4.4 (2002-09-15)
111+
112+
This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
113+
114+
* Use `X-Real-IP` to identify client IP address to mitigate Brute-Force attack #213
115+
110116
## 2.4.3 (2022-09-14)
111117

112118
This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
@@ -237,7 +243,7 @@ Maintenance release to fix minor issues
237243
* Fix to retrieve user quota only for valid user_root #135
238244
* Add option `disable-ssh-keys` to disable SSH Key management
239245
* Use absolute URL everywhere
240-
* Add support for `X-Forward-For`, `X-Forward-proto` and other reverse proxy header when generating absolute URL
246+
* Add support for `X-Forwarded-For`, `X-Forwarded-proto` and other reverse proxy header when generating absolute URL
241247
* Drop Debian Stretch support
242248
* Implement a new background scheduler using apscheduler #82
243249
* Use background job to send email notification to avoid blocking web page loading #47

doc/networking.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
You may need an Apache server in case:
66

7-
* you need to serve multiple web services from the same IP;
8-
* you need more security (like HTTP + SSL).
7+
* you need to serve multiple web services from the same IP;
8+
* you need more security (like HTTP + SSL).
99

1010
This section doesn't explain how to install and configure your Apache server.
1111
This is out-of-scope. The following is only provided as a suggestion and is in
@@ -21,13 +21,14 @@ no way a complete reference.
2121

2222
**Basic configuration**
2323

24-
Add the following to your Apache configuration. It's recommended to create a
24+
Add the following to your Apache configuration. It's recommended to create a
2525
file in `/etc/apache2/sites-available/rdiffweb`.
2626

2727
<VirtualHost *:80>
2828
ServerName rdiffweb.mydomain.com
2929
ProxyPass / http://localhost:8080/ retry=5
3030
ProxyPassReverse / http://localhost:8080/
31+
RemoteIPHeader X-Real-IP
3132
</VirtualHost>
3233

3334
**SSL configuration**
@@ -37,7 +38,7 @@ Here is an example with SSL configuration.
3738
<VirtualHost *:80>
3839
ServerName rdiffweb.mydomain.com
3940
ServerAdmin [email protected]
40-
# TODO Redirect HTTP to HTTPS
41+
# Redirect HTTP to HTTPS
4142
RewriteEngine on
4243
RewriteRule ^(.*)$ https://rdiffweb.mydomain.com$1 [L,R=301]
4344
<Location />
@@ -50,10 +51,10 @@ Here is an example with SSL configuration.
5051
ServerName rdiffweb.mydomain.com
5152
ServerAdmin [email protected]
5253

53-
# Hostaname resolution in /etc/hosts
5454
ProxyPass / http://localhost:8080/ retry=5
5555
ProxyPassReverse / http://localhost:8080/
5656
RequestHeader set X-Forwarded-Proto https
57+
RemoteIPHeader X-Real-IP
5758

5859
# SSL Configuration
5960
SSLEngine on
@@ -64,15 +65,17 @@ Here is an example with SSL configuration.
6465
Allow from all
6566
</Location>
6667
</VirtualHost>
67-
68-
Take special care of `RequestHeader set X-Forwarded-Proto https` setting used to pass the right protocol to rdiffweb.
68+
69+
Make sure you set `X-Real-IP` so that Rdiffweb knows the real IP address of the client. This is used in the rate limit to identify the client.
70+
71+
Make sure you set `X-Forwarded-Proto` correctly so that Rdiffweb knows that access is being made using the `https` scheme. This is used to correctly create the URL on the page.
6972

7073
## Configure Rdiffweb behind nginx reverse proxy
7174

7275
You may need a nginx server in case:
7376

74-
* you need to serve multiple web services from the same IP;
75-
* you need more security (like HTTP + SSL).
77+
* you need to serve multiple web services from the same IP;
78+
* you need more security (like HTTP + SSL).
7679

7780
This section doesn't explain how to install and configure your nginx server.
7881
This is out-of-scope. The following is only provided as a suggestion and is in
@@ -90,3 +93,7 @@ no way a complete reference.
9093
# Proxy
9194
proxy_pass http://127.0.0.1:8080/;
9295
}
96+
97+
Make sure you set `X-Real-IP` so that Rdiffweb knows the real IP address of the client. This is used in the rate limit to identify the client.
98+
99+
Make sure you set `X-Forwarded-Proto` correctly so that Rdiffweb knows that access is being made using the `https` scheme. This is used to correctly create the URL on the page.

rdiffweb/controller/tests/test_page_login.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,36 @@ def test_login_ratelimit(self):
277277
self.assertStatus(429)
278278

279279

280+
class LoginPageRateLimitTestWithXForwardedFor(rdiffweb.test.WebCase):
281+
282+
default_config = {
283+
'rate-limit': 5,
284+
}
285+
286+
def test_login_ratelimit(self):
287+
# Given an unauthenticate
288+
# When requesting multple time the login page
289+
for i in range(0, 6):
290+
self.getPage('/login/', headers=[('X-Forwarded-For', '127.0.0.%s' % i)])
291+
# Then a 429 error (too many request) is return
292+
self.assertStatus(429)
293+
294+
295+
class LoginPageRateLimitTestWithXRealIP(rdiffweb.test.WebCase):
296+
297+
default_config = {
298+
'rate-limit': 5,
299+
}
300+
301+
def test_login_ratelimit(self):
302+
# Given an unauthenticate
303+
# When requesting multple time the login page
304+
for i in range(0, 6):
305+
self.getPage('/login/', headers=[('X-Real-IP', '127.0.0.%s' % i)])
306+
# Then a 200 is return.
307+
self.assertStatus(200)
308+
309+
280310
class LogoutPageTest(rdiffweb.test.WebCase):
281311
def test_getpage_without_login(self):
282312
# Accessing logout page directly will redirect to "/".

rdiffweb/main.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ def add_ip(record):
4646
request = cherrypy.serving.request
4747
remote = request.remote
4848
record.ip = remote.name or remote.ip
49-
# If the request was forwarded by a reverse proxy
50-
if 'X-Forwarded-For' in request.headers:
51-
record.ip = request.headers['X-Forwarded-For']
5249
return True
5350

5451
def add_username(record):

rdiffweb/rdw_app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import rdiffweb.tools.i18n
3939
import rdiffweb.tools.proxy
4040
import rdiffweb.tools.ratelimit
41+
import rdiffweb.tools.real_ip
4142
import rdiffweb.tools.secure_headers
4243
from rdiffweb.controller import Controller
4344
from rdiffweb.controller.api import ApiPage
@@ -72,8 +73,9 @@
7273
}
7374

7475

75-
@cherrypy.tools.proxy()
76+
@cherrypy.tools.proxy(remote=None)
7677
@cherrypy.tools.secure_headers()
78+
@cherrypy.tools.real_ip()
7779
class Root(LocationsPage):
7880
def __init__(self):
7981
self.login = LoginPage()

rdiffweb/tools/enrich_session.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
# rdiffweb, A web interface to rdiff-backup repositories
3+
# Copyright (C) 2012-2021 rdiffweb contributors
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
import datetime
18+
19+
import cherrypy
20+
21+
22+
def enrich_session():
23+
"""
24+
Store ephemeral information into user's session. e.g.: last IP address, user-agent
25+
"""
26+
# When session is not enable, simply validate credentials
27+
sessions_on = cherrypy.request.config.get('tools.sessions.on', False)
28+
if not sessions_on:
29+
return
30+
# Get information related to the current request
31+
request = cherrypy.serving.request
32+
ip_address = request.remote.ip
33+
cherrypy.session['ip_address'] = ip_address
34+
cherrypy.session['user_agent'] = request.headers.get('User-Agent', None)
35+
cherrypy.session['access_time'] = datetime.datetime.now()
36+
37+
38+
cherrypy.tools.enrich_session = cherrypy.Tool('before_handler', enrich_session, priority=60)

rdiffweb/tools/real_ip.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
# rdiffweb, A web interface to rdiff-backup repositories
3+
# Copyright (C) 2012-2021 rdiffweb contributors
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
import cherrypy
19+
20+
21+
def real_ip(remote='X-Real-IP'):
22+
"""
23+
Update the `remote.ip` from the `X-Real-IP` field.
24+
"""
25+
26+
request = cherrypy.serving.request
27+
28+
value = request.headers.get(remote)
29+
if value:
30+
request.remote.ip = value
31+
32+
33+
cherrypy.tools.real_ip = cherrypy.Tool('before_request_body', real_ip, priority=31)

0 commit comments

Comments
 (0)