AWS 应用程序负载均衡器上的 Websocket + SSL
Posted
技术标签:
【中文标题】AWS 应用程序负载均衡器上的 Websocket + SSL【英文标题】:Websocket + SSL on AWS Application Load Balancer 【发布时间】:2019-02-09 18:28:10 【问题描述】:我在 ElastickBeanstalk 上部署了一个 Django 应用程序。 我最近从 Classic -> Application 迁移了负载均衡器,以支持 Websocket(层由:Django-channels (~=1.1.8, channels-api==0.4.0)、Redis Elasticache AWS 和 Daphne (~= 1.4))。 HTTP、HTTPS 和 Web Socket 协议工作正常。
但我找不到通过安全 SSL 部署 Websocket 的方法。 它正在杀死我,它正在阻塞,因为来自浏览器的 HTTPS 连接将切断非安全的 ws:// 对等请求。
这是我的 ALB 配置 有没有人作为解决方案?
【问题讨论】:
您能指定使用的 django-channels 和 Daphne 版本吗?daphne~=1.4 channels~=1.1.8 channels-api==0.4.0
【参考方案1】:
经过2天的调查,我终于破解了这个配置!
答案如下:
正确的,最小的,aws - ALB 配置: 确实,我们需要
解码 SSL(这不是端到端加密)将所有流量转发给达芙妮。 我之所以没有在 web conf 中进行非常广泛的传播:“/ws/*”路由到 Daphne,是因为它确实为我提供了 HandShake OK,但是之后,什么都没有,nada,websocket 无法推回订户。我相信,原因是 Daphne 的回击不尊重您在 conf.xml 中自定义的自定义基本尾随 URL。另外,我不能确定这种解释。但我可以肯定的是,如果我不将所有流量转发给 Daphne,握手后它就不起作用了。
-
最低部署配置
.ebextensions/05_channels.config
files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/start_supervisor.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
sudo virtualenv -p /usr/bin/python2.7 /tmp/senv
source /tmp/senv/bin/activate && source /opt/python/current/env
sudo python --version > /tmp/version_check.txt
sudo pip install supervisor
sudo /usr/local/bin/supervisord -c /opt/python/current/app/fxf/custom_eb_deployment/supervisord.conf
sudo /usr/local/bin/supervisorctl -c /opt/python/current/app/fxf/custom_eb_deployment/supervisord.conf reread
sudo /usr/local/bin/supervisorctl -c /opt/python/current/app/fxf/custom_eb_deployment/supervisord.conf update
sudo /usr/local/bin/supervisorctl -c /opt/python/current/app/fxf/custom_eb_deployment/supervisord.conf restart all
sudo /usr/local/bin/supervisorctl -c /opt/python/current/app/fxf/custom_eb_deployment/supervisord.conf status
start_daphne.sh(备注我选择 8001 端口,根据我的 ALB 配置)
#!/usr/bin/env bash
source /opt/python/run/venv/bin/activate && source /opt/python/current/env
/opt/python/run/venv/bin/daphne -b 0.0.0.0 -p 8001 fxf.asgi:channel_layer
start_worker.sh
#!/usr/bin/env bash
source /opt/python/run/venv/bin/activate && source /opt/python/current/env
python /opt/python/current/app/fxf/manage.py runworker
`
[unix_http_server]
file=/tmp/supervisor.sock ; (the path to the socket file)
[supervisord]
logfile=/tmp/supervisord.log ; supervisord log file
loglevel=error ; info, debug, warn, trace
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
[program:Daphne]
environment=PATH="/opt/python/run/venv/bin"
command=sh /opt/python/current/app/fxf/custom_eb_deployment/start_daphne.sh --log-file /tmp/start_daphne.log
directory=/opt/python/current/app
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/tmp/daphne.out.log
stderr_logfile=/tmp/daphne.err.log
[program:Worker]
environment=PATH="/opt/python/run/venv/bin"
command=sh /opt/python/current/app/fxf/custom_eb_deployment/start_worker.sh --log-file /tmp/start_worker.log
directory=/opt/python/current/app
process_name=%(program_name)s_%(process_num)02d
numprocs=2
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/tmp/workers.out.log
stderr_logfile=/tmp/workers.err.log
; When resorting to send SIGKILL to the program to terminate it
; send SIGKILL to its whole process group instead,
; taking care of its children as well.
killasgroup=true
`
如果有些人还在为这个 conf 苦苦挣扎,我可能会在 medium 或其他东西上发布一个 tuto。 不要犹豫,在答案上推动我;)
【讨论】:
很高兴我能帮上忙。还有一个问题:您是否对禁用粘性政策有任何问题? 我的运行状况检查出现问题,因为请求被重定向到 daphne 并以代码 301 进行响应。我的环境运行良好,但运行状况检查报告严重的运行状况,因为它需要 200 OK 响应.你有遇到同样的问题吗? 确实如此。实际上很容易解决这个问题。 ALB 期望通过 HTTP 请求对任何进程进行健康检查端点。因此,如果您像我在上面所做的那样配置到 Daphne 的所有流量,则可以简单地在 django 中公开,一个仅返回 200 的端点。在配置过程中指定您的 health_check 路径,如“/status/”。在 urls.py 中`url(r'^status/$', get_health_status),` 在views.py 中:def get_health_status(request): return HttpResponse(status=200)
谢谢。最后,我必须在我的设置文件SECURE_REDIRECT_EXEMPT = [r'^status/$', ]
中将该 url 添加到 de ssl 重定向异常【参考方案2】:
我也在 SSL、EBS 和 Channels 1.x 上苦苦挣扎,使用的场景与您描述的完全相同,但最终我可以部署我的应用程序。 SSL 一直是问题所在,因为 Django 忽略了我在 routing.py
文件中针对所有 SSL 请求的路由,而在此之前一切正常。
我决定将所有 websockets 请求发送到服务器中的唯一根路径,例如 /ws/*
。然后向负载均衡器添加一个特定规则,它通过端口 443 接收所有这些请求,并将它们作为 HTTP 请求(不是 HTTPS!)重定向到端口 5000(Daphne 工作人员正在侦听)。这是假设在负载均衡器后面,VPC 足够安全。请注意,此配置可能涉及其他项目的安全问题。
现在我的负载均衡器配置如下所示
...因为来自浏览器的 HTTPS 连接将切断非安全的 ws:// 对等请求。
还有一件事。您应该使用 wss://
通过 HTTPS 启动 websocket 连接。你可以在.js
文件中写下这样的内容。
var wsScheme = window.location.protocol.includes('https') ? 'wss' : 'ws';
var wsPath = wsScheme + '://' + window.location.host + '/your/ws/path';
var ws = new ReconnectingWebSocket(wsPath);
祝你好运!
【讨论】:
感谢您的帮助!转发到 root /ws/* 对我来说只是握手OK。但后来就没有了。我必须首先将所有流量转发到从机上的 daphne 守护进程,然后再转发到标准 WSGI 或 django 守护进程以进行 websocket 处理。更多细节在我上面的答案中。干杯【参考方案3】:您应该使用 wss:// 而不是 ws://。 并更改有关代理的设置。我刚刚添加了我的 wsgi.conf。
<VirtualHost *:80>
WSGIPassAuthorization On
WSGIScriptAlias / /opt/python/current/app/config/wsgi.py
RewriteEngine On
RewriteCond %HTTP:X-Forwarded-Proto =http
RewriteRule .* https://%HTTP:Host%REQUEST_URI [L,R=permanent]
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
ProxyPreserveHost On
ProxyRequests Off
ProxyPass "/ws/chat" "ws://**your site**/ws/chat" Keepalive=On
ProxyPassReverse "/ws/chat" "ws://**your site**/ws/chat" Keepalive=On
<Directory /opt/python/current/app/>
Require all granted
</Directory>
</VirtualHost>
然后它会给你 200 连接状态。 "/ws/chat/" 应该替换为您的 websocket url。
在制作此文件之前,您应该检查您的 daphne 服务器是否已打开。 问题我经历的是 djangoenv 和 daemon.config 的 worker。
首先,djangoenv 应该在一行。这意味着没有换行符。 其次,如果你使用django channel v2,那么它不需要worker。所以删除它。
这是我的 daemon.config(我使用 8001 端口。):
files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/run_supervised_daemon.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
# Get django environment variables
djangoenv=`cat /opt/python/current/env | tr '\n' ',' | sed 's/%/%%/g' | sed 's/export //g' | sed 's/$PATH/%(ENV_PATH)s/g' | sed 's/$PYTHONPATH//g' | sed 's/$LD_LIBRARY_PATH//g'`
djangoenv=$djangoenv%?
# Create daemon configuraiton script
daemonconf="[program:daphne]
; Set full path to channels program if using virtualenv
command=/opt/python/run/venv/bin/daphne -b 0.0.0.0 -p 8001 config.asgi:application
directory=/opt/python/current/app
user=ec2-user
numprocs=1
stdout_logfile=/var/log/stdout_daphne.log
stderr_logfile=/var/log/stderr_daphne.log
autostart=true
autorestart=true
startsecs=10
; Need to wait for currently executing tasks to finish at shutdown.
; Increase this if you have very long running tasks.
stopwaitsecs = 600
; When resorting to send SIGKILL to the program to terminate it
; send SIGKILL to its whole process group instead,
; taking care of its children as well.
killasgroup=true
; if rabbitmq is supervised, set its priority higher
; so it starts first
priority=998
environment=$djangoenv"
# Create the supervisord conf script
echo "$daemonconf" | sudo tee /opt/python/etc/daemon.conf
# Add configuration script to supervisord conf (if not there already)
if ! grep -Fxq "[include]" /opt/python/etc/supervisord.conf
then
echo "[include]" | sudo tee -a /opt/python/etc/supervisord.conf
echo "files: daemon.conf" | sudo tee -a /opt/python/etc/supervisord.conf
fi
# Reread the supervisord config
sudo /usr/local/bin/supervisorctl -c /opt/python/etc/supervisord.conf reread
# Update supervisord in cache without restarting all services
sudo /usr/local/bin/supervisorctl -c /opt/python/etc/supervisord.conf update
# Start/Restart processes through supervisord
sudo /usr/local/bin/supervisorctl -c /opt/python/etc/supervisord.conf restart daphne
然后仔细检查您的安全组 alb 到 ec2。祝你好运!
【讨论】:
您能否提供一种通过 .ebextensions 添加代理配置的方法?我试图创建一个 httpd/conf.d/websocket_ssl.conflisten 80 <VirtualHost *:80> <Proxy *> Require all granted </Proxy> ProxyPreserveHost on LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so ... </VirtualHost>
但没有成功。然后在我尝试直接在实例上调整 /etc/httpd/wsgi.conf 并重新启动后,但也没有成功。
* 当然我正在测试 wss:///
对于您的问题,您是否尝试过增加空闲超时? EC2 服务 > 负载均衡器(选择您的 ALB)> 属性 > 编辑属性:空闲超时:3600 -> 应用
抱歉延迟检查。现在我解决了这个问题。我的问题是错误的 redis url。将 conf 添加到 .ebextension 对我有用。(我更新了我的答案。)。仍然陷入这个问题,给我更多信息将有助于回答。以上是关于AWS 应用程序负载均衡器上的 Websocket + SSL的主要内容,如果未能解决你的问题,请参考以下文章
HAProxy 上的哪些设置需要与 AWS ALB(应用程序负载均衡器)配合使用?