Changeset - f08fbf424898
[Not reviewed]
default
0 1 0
Mads Kiilerich (mads) - 5 years ago 2021-05-09 22:32:51
mads@kiilerich.com
Grafted from: ab3885a410f1
auth: don't trust clients too much - only trust the *last* IP in the X-Forwarded-For header

The X-Forwarded-For header contains a list of IP addresses, where each
proxy server appends the IP they see their request coming from.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For .

Trusting the *first* IP in HTTP_X_FORWARDED_FOR would allow clients to claim
any IP, which could be used to bypass IP restrictions configured in Kallithea.

Instead, only trust the last proxy in the chain, and thus only use the *last*
IP in HTTP_X_FORWARDED_FOR. (In setups where more than last IP should be
trusted, the last proxy server in the chain must be configured rewrite the
header accordingly.)
1 file changed with 7 insertions and 5 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/base.py
Show inline comments
 
@@ -19,105 +19,107 @@ kallithea.controllers.base
 
The base Controller API
 
Provides the BaseController class for subclassing. And usage in different
 
controllers
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Oct 06, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import base64
 
import datetime
 
import logging
 
import traceback
 
import warnings
 

	
 
import decorator
 
import paste.auth.basic
 
import paste.httpexceptions
 
import paste.httpheaders
 
import webob.exc
 
from tg import TGController, config, render_template, request, response, session
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 

	
 
import kallithea
 
from kallithea.lib import auth_modules, ext_json, webutils
 
from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
 
from kallithea.lib.exceptions import UserCreationError
 
from kallithea.lib.utils import get_repo_slug, is_valid_repo
 
from kallithea.lib.utils2 import AttributeDict, asbool, ascii_bytes, safe_int, safe_str, set_hook_environment
 
from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError
 
from kallithea.lib.webutils import url
 
from kallithea.model import db, meta
 
from kallithea.model.scm import ScmModel
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def render(template_path):
 
    return render_template({'url': url}, 'mako', template_path)
 

	
 

	
 
def _filter_proxy(ip):
 
    """
 
    HEADERS can have multiple ips inside the left-most being the original
 
    client, and each successive proxy that passed the request adding the IP
 
    address where it received the request from.
 
    HTTP_X_FORWARDED_FOR headers can have multiple IP addresses, with the
 
    leftmost being the original client. Each proxy that is forwarding the
 
    request will usually add the IP address it sees the request coming from.
 

	
 
    :param ip:
 
    The client might have provided a fake leftmost value before hitting the
 
    first proxy, so if we have a proxy that is adding one IP address, we can
 
    only trust the rightmost address.
 
    """
 
    if ',' in ip:
 
        _ips = ip.split(',')
 
        _first_ip = _ips[0].strip()
 
        _first_ip = _ips[-1].strip()
 
        log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
 
        return _first_ip
 
    return ip
 

	
 

	
 
def get_ip_addr(environ):
 
    proxy_key = 'HTTP_X_REAL_IP'
 
    proxy_key2 = 'HTTP_X_FORWARDED_FOR'
 
    def_key = 'REMOTE_ADDR'
 

	
 
    ip = environ.get(proxy_key)
 
    if ip:
 
        return _filter_proxy(ip)
 

	
 
    ip = environ.get(proxy_key2)
 
    if ip:
 
        return _filter_proxy(ip)
 

	
 
    ip = environ.get(def_key, '0.0.0.0')
 
    return _filter_proxy(ip)
 

	
 

	
 
def get_path_info(environ):
 
    """Return PATH_INFO from environ ... using tg.original_request if available.
 

	
 
    In Python 3 WSGI, PATH_INFO is a unicode str, but kind of contains encoded
 
    bytes. The code points are guaranteed to only use the lower 8 bit bits, and
 
    encoding the string with the 1:1 encoding latin1 will give the
 
    corresponding byte string ... which then can be decoded to proper unicode.
 
    """
 
    org_req = environ.get('tg.original_request')
 
    if org_req is not None:
 
        environ = org_req.environ
 
    return safe_str(environ['PATH_INFO'].encode('latin1'))
 

	
 

	
 
def log_in_user(user, remember, is_external_auth, ip_addr):
 
    """
 
    Log a `User` in and update session and cookies. If `remember` is True,
 
    the session cookie is set to expire in a year; otherwise, it expires at
 
    the end of the browser session.
 

	
 
    Returns populated `AuthUser` object.
 
    """
 
    # It should not be possible to explicitly log in as the default user.
 
    assert not user.is_default_user, user
 

	
 
    auth_user = AuthUser.make(dbuser=user, is_external_auth=is_external_auth, ip_addr=ip_addr)
0 comments (0 inline, 0 general)