芹菜花在生产中的安全



我希望使用Flower(https://github.com/mher/flower)来监视我的Celery任务,而不是django-admin,如他们的文档(http://docs.celeryproject.org/en/latest/userguide/monitoring.html#flower-real-time-celery-web-monitor)中所推荐的那样。但是,因为我是新手,所以我对Flower的页面仅基于HTTP而不是HTTPS的方式感到有些困惑。如何为我的 Celery 任务启用安全性,以便任何老用户都不能只是访问无需登录的网站 http://flowerserver.com:5555 并更改某些内容?

我已经考虑了Celery自己的文档,但不幸的是,他们没有提到如何保护Flower的api或web ui。它所说的一切:[Need more text here]

谢谢!

更新:我的问题部分是这里的副本:如何将身份验证和端点添加到 Django 芹菜花监控?

但是,我在这里澄清了他的问题,询问如何使用在同一台远程计算机上包含nginx,gunicorn和芹菜的环境来运行它。我也想知道如何设置 Flower 的外部可访问网址,但如果可能的话,我也更喜欢像 https 而不是 http 这样的东西(或者某种保护 webui 和远程访问它的方法)。我还需要知道,对于任何可能访问 Flower 内部 API 的人来说,让 Flower 运行是否是一个相当大的安全风险,以及保护它的最佳方式是什么,或者是否应该完全禁用它并根据需要使用。

您可以使用 --auth 标志运行花朵,该标志将使用特定的 google 电子邮件进行身份验证:

celery flower --auth=your.email@gmail.com

编辑 1

新版本的Flower需要更多的标志和一个在Google开发者控制台上注册的OAuth2客户端:

celery flower 
    --auth=your.email@gmail.com 
    --oauth2_key="client_id" 
    --oauth2_secret="client_secret" 
    --oauth2_redirect_uri="http://example.com:5555/login"

oauth2_redirect_uri必须是实际的花卉登录网址,并且还必须添加到Google开发控制台中的授权重定向网址中。

不幸的是,此功能在当前的稳定版本0.7.2中无法正常工作,但现在在开发版本中已修复,0.8.0-dev通过此提交。

编辑 2

您可以使用基本身份验证配置 Flower:

celery flower --basic_auth=user1:password1,user2:password2

然后阻止除本地主机之外的所有端口的 5555 端口,并为 nginx 或 apache 配置反向代理:

ProxyRequests off
ProxyPreserveHost On
ProxyPass / http://localhost:5555

然后确保代理模组已打开:

sudo a2enmod proxy
sudo a2enmod proxy_http

如果您无法在单独的子域上设置它,例如:flower.example.com(上面的配置),您可以将其设置为example.com/flower

url_prefix一起奔跑花:

celery flower --url_prefix=flower --basic_auth=user1:password1,user2:password2

在 Apache 配置中:

ProxyPass /flower http://localhost:5555

当然,请确保配置SSL,否则没有意义:)

我已经在 Django 端 https://pypi.org/project/django-revproxy/使用代理弄清楚了它。所以 Flower 隐藏在 Django 身份验证后面,它比基本身份验证更灵活。而且你不需要在NGINX中重写规则。

花 0.9.5 及更高

版本

URL 前缀必须移动到代理路径:https://github.com/mher/flower/pull/766

urls.py

urlpatterns = [
    FlowerProxyView.as_url(),
    ...
]

views.py

class FlowerProxyView(UserPassesTestMixin, ProxyView):
    # `flower` is Docker container, you can use `localhost` instead
    upstream = 'http://{}:{}'.format('flower', 5555)
    url_prefix = 'flower'
    rewrite = (
        (r'^/{}$'.format(url_prefix), r'/{}/'.format(url_prefix)),
     )
    def test_func(self):
        return self.request.user.is_superuser
    @classmethod
    def as_url(cls):
        return re_path(r'^(?P<path>{}.*)$'.format(cls.url_prefix), cls.as_view())

花 0.9.4 及更低

版本

urls.py

urlpatterns = [
    re_path(r'^flower/?(?P<path>.*)$', FlowerProxyView.as_view()),
    ...
]

views.py

from django.contrib.auth.mixins import UserPassesTestMixin
from revproxy.views import ProxyView

class FlowerProxyView(UserPassesTestMixin, ProxyView):
    # `flower` is Docker container, you can use `localhost` instead
    upstream = 'http://flower:5555'
    def test_func(self):
        return self.request.user.is_superuser

我想在我的网络服务器的子目录上开花,所以我的nginx反向代理配置如下所示:

location /flower/ {
    proxy_pass http://localhost:5555/;
    proxy_redirect off;
    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-Protocol $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_http_version 1.1;
    auth_basic  "Restricted";
    auth_basic_user_file  /etc/nginx/.htpasswd;
}

现在我可以通过 www.example.com/flower 获得花朵(受密码保护)

其中大部分来自有关配置 nginx 反向代理的 Flower 文档页面:

http://flower.readthedocs.org/en/latest/reverse-proxy.html

我遵循了@petr-přikryl使用代理视图的方法。但是我无法让它验证身份验证(我认为从来没有调用过test_func)。相反,我选择将其嵌入 Django Admin 视图中,并使用 AdminSite.admin_view()(如此处所述)使用 Django Admin 身份验证包装视图。

具体来说,我进行了以下更改:

# Pipfile
[packages]
...
django-revproxy="*"
# admin.py
class MyAdminSite(admin.AdminSite):
    # ...
    def get_urls(self):
        from django.urls import re_path
        # Because this is hosted in the root `urls.py` under `/admin` this 
        # makes the total prefix /admin/flower
        urls = super().get_urls()
        urls += [
            re_path(
                r"^(?P<path>flower.*)$",
                self.admin_view(FlowerProxyView.as_view()),
            )
        ]
        return urls
# views.py
from __future__ import annotations
from django.urls import re_path
from revproxy.views import ProxyView

class FlowerProxyView(ProxyView):
    # Need `/admin/` here because the embedded view in the admin app drops the
    # `/admin` prefix before sending the URL to the ProxyView
    upstream = "http://{}:{}/admin/".format("localhost", 5555)

最后,我们需要确保在运行 flower 时设置了--url_prefix,因此我将其设置为在我们的生产和开发环境中像这样运行:

celery flower --app=my_app.celery:app --url_prefix=admin/flower

为了卸载 django 应用程序,我建议你使用 X-Accel-Redirect 标头来使用 nginx 来代理 Flower 服务器。具体如下:

  1. 用户请求花朵路径(例如 /task
  2. nginx 像往常一样proxy_pass对应用
  3. 的请求
  4. 你的 Django 应用程序选择接受或拒绝请求(例如,基于身份验证)
  5. 如果您的应用接受请求,它将返回一个带有 HTTP 标头X-Accel-Redirect以及内部位置字符串的响应,即用户无法直接访问的路径
  6. nginx拦截响应而不是将其转发给用户,并将其用作新路径,这次可以访问内部位置,在我们的例子中是Flower服务器

如果请求被拒绝,请不要使用X-Accel-Redirect,并像实现任何其他被拒绝的请求一样处理这种情况。

nginx.conf:

upstream celery_server {
    server /var/run/celery/flower.sock;
}
upstream app_server {
    server /var/run/gunicorn/asgi.sock;
}
server {
    listen 80;
    location /protected/task {
        internal;  # returns 404 if accessed directly
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_pass http://celery_server/task;
    }
    location / {
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        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_set_header X-Forwarded-Host $server_name;
        proxy_pass http://app_server;
    }
}

views.py:

from django.contrib.admin.views.decorators import staff_member_required
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

class XAccelRedirectResponse(HttpResponse):
    def __init__(self, path, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self['X-Accel-Redirect'] = '/protected' + path
        del self['Content-Type']  # necessary

# I chose to only allow staff members, i.e. whose who can access the admin panel
@staff_member_required
@csrf_exempt
def task_app(request, path):
    query_str = request.META['QUERY_STRING']  # you must keep the query string
    return XAccelRedirectResponse(f'/task/{path}?{query_str}')

urls.py:

from django.urls import re_path
from app import views

urlpatterns = [
    re_path('task/(?P<path>.*)', views.task_app, name='task'),
]

改变花的url-prefix很重要:

celery flower --unix-socket="/var/run/celery/flower.sock" --url-prefix="task"

这是对Petr Přikryl帖子的回复。 django-revproxy 无法在我的 Django 4.1.x 项目上运行。我遇到错误AttributeError: 'HttpResponse' object has no attribute '_headers'。许多其他人也面临同样的问题。Brianmay在问题线程中声称,"我认为这个项目基本上已经死了,对不起。

我使用了一个不同的库作为解决方法。

安装 django-proxy

这就是我的代码的样子。

# urls.py
from django.urls import re_path
from myapp.views import flower

urlpatterns = [
    re_path("flower/(?P<path>.*)", flower),
]
# views.py
from django.views.decorators.csrf import csrf_exempt
from proxy.views import proxy_view
@csrf_exempt
def flower(request, path):
    extra_requests_args = {}
    remoteurl = f"http://localhost:5555/flower/" + path
    return proxy_view(request, remoteurl, extra_requests_args)

然后用芹菜跑

$ celery --app myproject flower --loglevel INFO --url_prefix=flower

然后,您可以在浏览器中查看它,通过 Django 提供,http://localhost:8000/flower/。

附加说明:

-

-url_prefix= 很重要,因为这将允许代理提供 flower 请求的静态文件。

如果您使用的是 docker 撰写,则可能需要更改 flower 函数中 remoteurl 字符串中的主机名以反映服务的相同内容。例如,我的服务在我的docker-compose.yaml文件中被适当地称为flower。因此,我会将字符串从 f"http://localhost:5555/flower/" 更改为f"http://flower:5555/flower/"

是的,花朵上没有身份验证,因为它只是与代理交谈,但是如果您通过 SSL 运行它,那么基本身份验证应该足够好。

HTTP 和 HTTPS 将如何影响 Celery 的安全性?您指的是哪些用户登录?

花监视器通过附加到工人来监视芹菜队列。设置 Flower 时,需要提供连接字符串 [broker]://[user_name]:[password]@[database_address]:[端口]/[实例]。用户名和密码是登录到所选数据库的凭据。

如果您指的是此登录名,那么简单地禁用/删除其登录名就足够了吗?

相关内容

  • 没有找到相关文章

最新更新