Skip to content

Commit

Permalink
Use X-Real-IP to identify clients #213
Browse files Browse the repository at this point in the history
  • Loading branch information
ikus060 committed Sep 15, 2022
1 parent 9125f5a commit 28258e5
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 14 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -107,6 +107,12 @@ Professional support for Rdiffweb is available by contacting [IKUS Soft](https:/

# Changelog

## 2.4.4 (2002-09-15)

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.

* Use `X-Real-IP` to identify client IP address to mitigate Brute-Force attack #213

## 2.4.3 (2022-09-14)

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
Expand Down Expand Up @@ -237,7 +243,7 @@ Maintenance release to fix minor issues
* Fix to retrieve user quota only for valid user_root #135
* Add option `disable-ssh-keys` to disable SSH Key management
* Use absolute URL everywhere
* Add support for `X-Forward-For`, `X-Forward-proto` and other reverse proxy header when generating absolute URL
* Add support for `X-Forwarded-For`, `X-Forwarded-proto` and other reverse proxy header when generating absolute URL
* Drop Debian Stretch support
* Implement a new background scheduler using apscheduler #82
* Use background job to send email notification to avoid blocking web page loading #47
Expand Down
25 changes: 16 additions & 9 deletions doc/networking.md
Expand Up @@ -4,8 +4,8 @@

You may need an Apache server in case:

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

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

**Basic configuration**

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

<VirtualHost *:80>
ServerName rdiffweb.mydomain.com
ProxyPass / http://localhost:8080/ retry=5
ProxyPassReverse / http://localhost:8080/
RemoteIPHeader X-Real-IP
</VirtualHost>

**SSL configuration**
Expand All @@ -37,7 +38,7 @@ Here is an example with SSL configuration.
<VirtualHost *:80>
ServerName rdiffweb.mydomain.com
ServerAdmin me@mydomain.com
# TODO Redirect HTTP to HTTPS
# Redirect HTTP to HTTPS
RewriteEngine on
RewriteRule ^(.*)$ https://rdiffweb.mydomain.com$1 [L,R=301]
<Location />
Expand All @@ -50,10 +51,10 @@ Here is an example with SSL configuration.
ServerName rdiffweb.mydomain.com
ServerAdmin me@mydomain.com

# Hostaname resolution in /etc/hosts
ProxyPass / http://localhost:8080/ retry=5
ProxyPassReverse / http://localhost:8080/
RequestHeader set X-Forwarded-Proto https
RemoteIPHeader X-Real-IP

# SSL Configuration
SSLEngine on
Expand All @@ -64,15 +65,17 @@ Here is an example with SSL configuration.
Allow from all
</Location>
</VirtualHost>

Take special care of `RequestHeader set X-Forwarded-Proto https` setting used to pass the right protocol to rdiffweb.

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.

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.

## Configure Rdiffweb behind nginx reverse proxy

You may need a nginx server in case:

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

This section doesn't explain how to install and configure your nginx server.
This is out-of-scope. The following is only provided as a suggestion and is in
Expand All @@ -90,3 +93,7 @@ no way a complete reference.
# Proxy
proxy_pass http://127.0.0.1:8080/;
}

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.

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.
30 changes: 30 additions & 0 deletions rdiffweb/controller/tests/test_page_login.py
Expand Up @@ -277,6 +277,36 @@ def test_login_ratelimit(self):
self.assertStatus(429)


class LoginPageRateLimitTestWithXForwardedFor(rdiffweb.test.WebCase):

default_config = {
'rate-limit': 5,
}

def test_login_ratelimit(self):
# Given an unauthenticate
# When requesting multple time the login page
for i in range(0, 6):
self.getPage('/login/', headers=[('X-Forwarded-For', '127.0.0.%s' % i)])
# Then a 429 error (too many request) is return
self.assertStatus(429)


class LoginPageRateLimitTestWithXRealIP(rdiffweb.test.WebCase):

default_config = {
'rate-limit': 5,
}

def test_login_ratelimit(self):
# Given an unauthenticate
# When requesting multple time the login page
for i in range(0, 6):
self.getPage('/login/', headers=[('X-Real-IP', '127.0.0.%s' % i)])
# Then a 200 is return.
self.assertStatus(200)


class LogoutPageTest(rdiffweb.test.WebCase):
def test_getpage_without_login(self):
# Accessing logout page directly will redirect to "/".
Expand Down
3 changes: 0 additions & 3 deletions rdiffweb/main.py
Expand Up @@ -46,9 +46,6 @@ def add_ip(record):
request = cherrypy.serving.request
remote = request.remote
record.ip = remote.name or remote.ip
# If the request was forwarded by a reverse proxy
if 'X-Forwarded-For' in request.headers:
record.ip = request.headers['X-Forwarded-For']
return True

def add_username(record):
Expand Down
4 changes: 3 additions & 1 deletion rdiffweb/rdw_app.py
Expand Up @@ -38,6 +38,7 @@
import rdiffweb.tools.i18n
import rdiffweb.tools.proxy
import rdiffweb.tools.ratelimit
import rdiffweb.tools.real_ip
import rdiffweb.tools.secure_headers
from rdiffweb.controller import Controller
from rdiffweb.controller.api import ApiPage
Expand Down Expand Up @@ -72,8 +73,9 @@
}


@cherrypy.tools.proxy()
@cherrypy.tools.proxy(remote=None)
@cherrypy.tools.secure_headers()
@cherrypy.tools.real_ip()
class Root(LocationsPage):
def __init__(self):
self.login = LoginPage()
Expand Down
38 changes: 38 additions & 0 deletions rdiffweb/tools/enrich_session.py
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# rdiffweb, A web interface to rdiff-backup repositories
# Copyright (C) 2012-2021 rdiffweb contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime

import cherrypy


def enrich_session():
"""
Store ephemeral information into user's session. e.g.: last IP address, user-agent
"""
# When session is not enable, simply validate credentials
sessions_on = cherrypy.request.config.get('tools.sessions.on', False)
if not sessions_on:
return
# Get information related to the current request
request = cherrypy.serving.request
ip_address = request.remote.ip
cherrypy.session['ip_address'] = ip_address
cherrypy.session['user_agent'] = request.headers.get('User-Agent', None)
cherrypy.session['access_time'] = datetime.datetime.now()


cherrypy.tools.enrich_session = cherrypy.Tool('before_handler', enrich_session, priority=60)
33 changes: 33 additions & 0 deletions rdiffweb/tools/real_ip.py
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# rdiffweb, A web interface to rdiff-backup repositories
# Copyright (C) 2012-2021 rdiffweb contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import cherrypy


def real_ip(remote='X-Real-IP'):
"""
Update the `remote.ip` from the `X-Real-IP` field.
"""

request = cherrypy.serving.request

value = request.headers.get(remote)
if value:
request.remote.ip = value


cherrypy.tools.real_ip = cherrypy.Tool('before_request_body', real_ip, priority=31)

0 comments on commit 28258e5

Please sign in to comment.