Сельдерей Цветочная Безопасность в Производстве
Я собираюсь использовать Flower ( https://github.com/mher/flower) для мониторинга моих задач в Celery вместо django-admin, как рекомендовано в их документах ( http://docs.celeryproject.org/en/latest/userguide/monitoring.html). Однако, поскольку я новичок в этом, меня немного смущает то, как страница Flower основана только на HTTP, а не на HTTPS. Как я могу включить защиту для своих задач в Celery, чтобы любой старый пользователь не мог просто зайти на сайт, не требующий входа в систему, http://flowerserver.com:5555/ и что-то изменить?
Я рассмотрел собственную документацию Celery по этому вопросу, но, к сожалению, там нет упоминаний о том, как защитить API или веб-интерфейс Flower. Все это говорит: [Need more text here]
Спасибо!
Обновление: мой вопрос частично дублирует здесь: Как мне добавить аутентификацию и конечную точку для Django Celery Flower Monitoring?
Однако я проясняю его вопрос здесь, спрашивая, как запустить его, используя среду, включающую nginx, gunicorn и celery на одной и той же удаленной машине. Я тоже задаюсь вопросом о том, как настроить внешний доступный URL-адрес Flower, но также предпочел бы что-то вроде https, а не http, если это возможно (или какой-то способ защиты webui и удаленного доступа к нему). Мне также нужно знать, является ли оставление работающего Flower значительным риском для безопасности для любого, кто может получить доступ к внутреннему API-интерфейсу Flower, и каким может быть наилучший способ обеспечить это, или же его следует просто отключить и использовать только для некоторых целей. нужна основа.
8 ответов
Вы можете запустить flower с флагом --auth, который будет аутентифицироваться с использованием определенного адреса электронной почты Google:
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-адресом для входа в систему, а также должен быть добавлен к авторизованным URL-адресам перенаправления в консоли разработки Google.
К сожалению, эта функция не работает должным образом в текущей стабильной версии 0.7.2
, но теперь это исправлено в версии для разработчиков 0.8.0-dev
с этим коммитом.
Изменить 2:
Вы можете настроить Flower с помощью обычной аутентификации:
celery flower --basic_auth=user1:password1,user2:password2
Затем заблокируйте порт 5555 для всех, кроме localhost, и настройте обратный прокси для 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 auth, который более гибок, чем базовый auth. И вам не нужно правило перезаписи в NGINX.
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
Кредиты https://www.webnach.ru/django-auth-for-celery-flower.html
Flower 0.9.5 и выше
Префикс URL-адреса необходимо переместить в путь прокси: https://github.com/mher/flower/pull/766
urls.py
urlpatterns = [
FlowerProxyView.as_url(),
...
]
views.py
class FlowerProxyView(UserPassesTestMixin, ProxyView):
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())
Я хотел цветок в подкаталоге моего веб-сервера, поэтому моя конфигурация обратного прокси 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 о настройке обратного прокси-сервера nginx:
Я последовал подходу @petr-přikryl, используя прокси-представление. Однако я не смог заставить его проверить аутентификацию (я не думаю, что
test_func
когда-либо называется). Вместо этого я решил встроить это в представления администратора Django и использовать
AdminSite.admin_view()
(как описано здесь ), чтобы обернуть представление аутентификацией администратора Django.
В частности, я внес следующие изменения:
# 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)
Наконец, нам нужно убедиться, что
--url_prefix
устанавливается при запуске цветка, поэтому я настроил его на запуск в нашей производственной среде и среде разработки:
celery flower --app=my_app.celery:app --url_prefix=admin/flower
Это ответ на сообщение Петра Пршикрыла. django-revproxy не работает в моем проекте Django 4.1.x. Я сталкиваюсь с ошибкойAttributeError: 'HttpResponse' object has no attribute '_headers'
. Многие другие сталкиваются с той же проблемой. brianmay в ветке выпуска утверждает: «Я думаю, что этот проект практически мертв, извините».
Я пошел с другой библиотекой, чтобы служить обходным путем.
Установить джанго-прокси
Вот как выглядит мой код.
# 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= важен, потому что это позволит прокси-серверу обслуживать статические файлы, которые запрашивает цветок.
Если вы используете docker compose, вам, вероятно, потребуется изменить имя хоста вremoteurl
строка в функции, чтобы отразить то же самое службы. Например, моя служба правильно называетсяflower
в моемdocker-compose.yaml
файл. Поэтому я бы изменил строку сf"http://localhost:5555/flower/"
кf"http://flower:5555/flower/"
Чтобы разгрузить приложение django, я предлагаю вам использовать заголовок, чтобы использовать nginx для прокси-сервера Flower server. Это выглядит следующим образом:
- пользователь запрашивает цветочную дорожку (например,
/task
) - nginx
proxy_pass
запрос к вашему приложению, как обычно - ваше приложение django решает принять или отклонить запрос (например, на основе аутентификации)
- если ваше приложение принимает запрос, оно возвращает ответ с HTTP-заголовком вместе со строкой внутреннего местоположения , то есть путем, к которому пользователь не может получить доступ напрямую
- nginx перехватывает ответ вместо того, чтобы пересылать его пользователю, и использует его как новый путь с возможностью на этот раз доступа к внутренним местоположениям, в нашем случае к серверу цветов.
Если запрос отклонен, просто не используйте
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"
Да, на цветке нет аутентификации, поскольку он просто общается с брокером, но если вы запускаете его через SSL, тогда базовая аутентификация должна быть достаточно хорошей.
Как HTTP и HTTPS повлияют на безопасность Celery? На какие логины вы ссылаетесь?
Цветочные мониторы к очереди сельдерея, прикрепляясь к рабочим. При настройке Flower вам необходимо указать строку подключения [broker]://[user_name]:[password]@[database_address]:[port]/[instance]. Имя пользователя и пароль - это учетные данные для входа в базу данных по вашему выбору.
Если вы имеете в виду этот логин, не будет достаточно просто отключить / удалить их логины?