我有一个金字塔应用程序,在某些地方使用
request.environ['REMOTE_ADDR']
。
该应用程序由端口 6543 上的 Python Paste 提供服务,并且侦听端口 80 的 nginx 服务器将请求转发到 Paste 服务器。
nginx 配置的灵感来自 Pyramid 食谱:
server {
listen 80; ## listen for ipv4
listen [::]:80 default ipv6only=on; ## listen for ipv6
server_name localhost;
access_log /var/log/nginx/localhost.access.log;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:6543;
}
在 Pyramid 应用程序中,变量 request.environ['REMOTE_ADDR'] 现在始终等于 127.0.0.1。 我看到了一些解决这个问题的策略,但我不知道是否有推荐的方法来做到这一点。
这是我正在考虑的:
如果需要,添加一个 NewRequest 订阅者来替换 request.environ['REMOTE_ADDR']:
if 'HTTP_X_REAL_IP' in event.request.environ:
event.request.environ['REMOTE_ADDR'] = event.request.environ['HTTP_X_REAL_IP']
在到达 Pyramid 层之前使用 wsgi 中间件修改 request.environ。
还有其他事
您使用哪种策略来部署 Pyramid 应用程序? 如果我有两个 nginx 代理会发生什么? (第一个服务于 LAN,第二个服务于直接连接到互联网的机器)。
如果您通过
paste.deploy.config.PrefixMiddleware
在 WSGI 管道中使用 use = egg:PasteDeploy#prefix
,它将自动将 X-Forwarded-For
转换为 REMOTE_ADDR
。它对于反向代理的其他属性也很有用,例如,它会将 X-Forwarded-Proto
转换为 wsgi.url_scheme
,以确保如果用户使用 https 访问,则生成的 URL 也是 https。
http://pythonpaste.org/deploy/class-paste.deploy.config.PrefixMiddleware.html
我在 nginx 后面使用 gevent 服务器,并使用
request.client_addr
来获取客户端的 IP 地址。
如果您没有 WSGI pipline,或者不想使用
paste
,则添加事件处理程序:
config.add_subscriber(reverseProxyProtocolCorrection,'pyramid.events.NewRequest')
事件处理程序可以这样读:
def reverseProxyProtocolCorrection(event):
event.request.scheme = 'https' if event.request.headers['X-Forwarded-Proto']=='https' else 'http'
event.request.remote_addr=parseXForward(event.request.headers['X-Forwarded-For'],event.request.remote_addr)
并确保您的代理设置了标头
即使接受的答案(基于
paste.deploy.config.PrefixMiddleware
)大部分是正确的,PasteDeploy
包内有一个大问题。它将
request.remote_addr
替换为链中的第一个 IP。在另一侧,代理服务器通常将新 IP 附加到右侧。我将使用 AWS 负载均衡器 作为示例。
假设正确的客户端全局 IP 是
1.1.1.1
。但客户端正在添加 X-Forwarded-For
标头,其值为 192.168.1.10
。通过负载均衡器后,将附加实际 IP,结果标头将如下所示:
X-Forwarded-For 192.168.1.10,1.1.1.1
PasteDeploy
正在使用第一个IP,因此192.168.1.10
将被添加为客户端远程地址。如果您的逻辑基于客户端 IP,那么什么可能会导致潜在的漏洞。
最后添加的 IP 应在负载均衡器后面使用,以确保它是最后一跳的真实全局 IP。
基于
PasteDeploy
我创建了自己的具有正确行为的金字塔中间件。
the_middleware.py
存储在项目根目录中的文件如下所示:
class ALBFilterMiddleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
if 'HTTP_X_FORWARDED_FOR' in environ:
# add LAST IP in chain
environ['REMOTE_ADDR'] = [
x.strip(' ') for x in environ.pop('HTTP_X_FORWARDED_FOR').split(',')
][-1]
if 'HTTP_X_FORWARDED_SCHEME' in environ:
environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_SCHEME')
elif 'HTTP_X_FORWARDED_PROTO' in environ:
environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_PROTO')
return self.app(environ, start_response)
def alb_filter_factory(global_conf, **app_conf):
def filter(app):
return ALBFilterMiddleware(app)
return filter
应用程序 INI 配置应如下所示:
; this entrypoint will be used when you build app
; application = get_app('app.ini', 'the_entrypoint_name')
[pipeline:the_entrypoint_name]
pipeline = the_middleware_name your_app
[app:your_app]
use = call:app_file_with_main_func:main
[filter:the_middleware_name]
; the_middleware.py
use = call:the_middleware:alb_filter_factory
在 nginx 中:
location / {
proxy_pass http://yourapp;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
在 php 中我这样做:
if($_SERVER["HTTP_X_FORWARDED_FOR"]){
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
}else{
$ip = $_SERVER["REMOTE_ADDR"];
}
也许在Python中是类似的,$ip是你的真实ip