【问题标题】:Celery Flower Security in Production生产中的芹菜花安全
【发布时间】:2013-11-10 10:55:09
【问题描述】:

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

我已经考虑过 Celery 的 own documentation,但不幸的是,他们没有提到如何保护 Flower 的 api 或 web ui。它说的是:[Need more text here]

谢谢!

更新:我的问题部分与此处重复:How do I add authentication and endpoint to Django Celery Flower Monitoring?

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

【问题讨论】:

  • 我决定使用 Fabric 通过 Upstart 启动/停止 Flower 服务器,并根据需要生成 ssh 远程端口转发。我将为除本地主机之外的所有人设置 iptables 阻止端口 5555。我希望 Flower 具有安全性以使远程访问更容易,但它似乎没有。感谢大家的帮助。
  • 有一种方法可以使用基本身份验证设置Flower:flower.readthedocs.org/en/latest/…,并在您的nginx或apache配置中使用ssl设置反向代理,这样身份验证将通过https完成。

标签: django nginx celery flower


【解决方案1】:

您可以使用 --auth 标志运行 Flower,它将使用特定的谷歌电子邮件进行身份验证:

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

编辑 1

新版本的 Flower 需要更多的标志和注册的 OAuth2 客户端 Google Developer Console

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 必须是实际的花卉登录 url,并且还必须添加到 Google 开发控制台中的授权重定向 url。

很遗憾,此功能在当前稳定版本0.7.2 中无法正常工作,但现在已在开发版本0.8.0-dev 中通过此commit 修复。

编辑 2

你可以使用basic authentication来配置Flower:

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

然后为除 localhost 之外的所有端口阻塞 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,否则就没意义了:)

【讨论】:

  • 这是一个写得非常好的答案,谢谢。现在花是否值得所有上述混乱 - 这是一个不同的问题。
  • 只是想指出花现在似乎有一个本地主机选项:celery flower -A proj --address=127.0.0.1 --port=5555
【解决方案2】:

我已经在 Django 端使用代理 https://pypi.org/project/django-revproxy/ 找到了它。所以Flower隐藏在Django auth后面,它比basic auth更灵活。而且你不需要在 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

【讨论】:

  • 这很棒。我对服务器所做的一项更改是upstream = 'http://{}:{}'.format('localhost', 5555)
【解决方案3】:

我想在我的网络服务器的子目录上放花,所以我的 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 访问 Flower(受密码保护)

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

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

【讨论】:

【解决方案4】:

我使用代理视图遵循@petr-přikryl 的方法。但是我无法通过它来验证身份验证(我认为从未调用过 test_func)。相反,我选择将其嵌入到 Django Admin 视图中,并使用AdminSite.admin_view()(如described here)通过 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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      要卸载 django 应用程序,我建议您使用 X-Accel-Redirect 标头以便使用 nginx 代理 Flower 服务器。如下:

      1. 用户请求花路(例如/task
      2. nginx proxy_pass 像往常一样向您的应用发出请求
      3. 您的 django 应用选择接受或拒绝请求(例如基于身份验证)
      4. 如果您的应用接受请求,它会返回一个带有X-Accel-Redirect HTTP-header 的响应以及一个内部位置的字符串,即用户无法直接访问的路径李>
      5. 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'),
      ]
      

      更改Flower的url-prefix很重要:

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

      【讨论】:

        【解决方案7】:

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

        Flower 通过附加到工作线程来监控 Celery 队列。设置 Flower 时,您需要提供连接字符串 [broker]://[user_name]:[password]@[database_address]:[port]/[instance]。用户名和密码是登录您选择的数据库的凭据。

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

        【讨论】:

        • 我指的是Flower提供的webui。我并不认为连接到 Flower 提供的 Web 界面需要用户名和密码。否则,即使有一个,web ui 也通过 HTTP 而不是 HTTPS 进行通信,所以我质疑通过非安全 Web 界面控制 Celery 的安全性。
        • 为什么需要登录才能访问 Flower UI?为了让 Flower 工作,您已经可以访问 Celery 后端代理,不是吗?如果您害怕窥探您的网络数据包,请设置访问控制,以便只有内部机器可以访问运行 Celery 的服务器。或者在我们的实践中,只有少数可以登录生产服务器的人可以运行 Flower。
        • 啊,这是我的新手。我正在使用 celery 在与我的 django 服务器相同的(远程)机器上运行时间密集型任务。澄清一下,您是在建议我设置 iptables 以仅允许 localhost 访问server:5555,然后使用 ssh 端口转发访问 Flower?我原以为会有一个更实用的带有登录名的 https 选项,但不是吗?否则,据我了解,一旦 Flower 设置为使用凭据登录代理,任何人都可以访问 ui 本身,不是吗?
        • HTTPS 选项在技术上可能是可行的,但设置它需要大量手动工作(注册证书、更改 Flower 源代码以使用该证书,如果可能的话)。在我看来,设置 iptables 和端口阻塞是更容易的选择。而且我宁愿锁定机器运行Flower并ssh进入机器运行它。本质上,您不想允许访问 Flower,除非请求来自 localhost。
        • 您可以让 Flower 与 Celery 或 Web Server 在同一台服务器上运行。但我不会推荐它。在我的实践中,我们有单独的 AWS 实例托管不同的应用程序(Web 服务器、芹菜等)。因此,如果其中一个失败,我们可以轻松启动另一个实例,而不会相互影响。
        猜你喜欢
        • 2018-06-11
        • 2023-03-18
        • 1970-01-01
        • 2019-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-05
        • 1970-01-01
        相关资源
        最近更新 更多