57

I am looking to use Flower (https://github.com/mher/flower) to monitor my Celery tasks in place of the django-admin as reccomended in their docs (http://docs.celeryproject.org/en/latest/userguide/monitoring.html#flower-real-time-celery-web-monitor). However, because I am new to this I am a little confused about the way Flower's page is only based on HTTP, and not HTTPS. How can I enable security for my Celery tasks such that any old user can't just visit the no-login-needed website http://flowerserver.com:5555 and change something?

I have considered Celery's own documentation on this, but they unfortunately there is no mention of how to secure Flower's api or web ui. All it says: [Need more text here]

Thanks!

Update: My question is in part a duplicate of here: How do I add authentication and endpoint to Django Celery Flower Monitoring?

However, I clarify his question here by asking how to run it using an environment that includes nginx, gunicorn, and celery all on the same remote machine. I too am wondering about how to set up Flower's outside accessible url, but also would prefer something like https instead of http if possible (or some way of securing the webui and accessing it remotely). I also need to know if leaving Flower running is a considerable security risk for anyone who may gain access to Flower's internal API and what the best way for securing this could be, or if it should just be disabled altogether and used just on an as-needed basis.

Community
  • 1
  • 1
mh00h
  • 1,824
  • 3
  • 25
  • 45
  • I have decided to use Fabric to start/stop the Flower server via Upstart and generate an ssh remote port forward on an as-needed basis. I'll have iptables block port 5555 for everyone but localhost. I wish Flower had security to make remote access easier, but it would appear it has none. Thanks all for the help. – mh00h Oct 30 '13 at 22:43
  • There is a way to setup Flower with basic authentication: http://flower.readthedocs.org/en/latest/auth.html#http-basic-authentication, and setup reverse proxy in your nginx or apache configuration with ssl, this way authentication will be done over https. – lehins Aug 09 '14 at 01:17

8 Answers8

55

You can run flower with --auth flag, which will authenticate using a particular google email:

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

Edit 1:

New version of Flower requires couple more flags and a registered OAuth2 Client with 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 has to be the actual flower login url, and it also has to be added to authorized redirect url's in Google Development Console.

Unfortunately this feature doesn't work properly in current stable version 0.7.2, but it is now fixed in development version 0.8.0-dev with this commit.

Edit 2:

You can configure Flower using basic authentication:

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

Then block 5555 port for all but localhost and configure reverse proxy for nginx or for apache:

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

Then make sure proxy mod is on:

sudo a2enmod proxy
sudo a2enmod proxy_http

In case you can't set it up on a separate subdomain, ex: flower.example.com (config above), you can set it up for example.com/flower:

run flower with url_prefix:

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

in apache config:

ProxyPass /flower http://localhost:5555

Of course, make sure SSL is configured, otherwise there is no point :)

JP Ventura
  • 5,564
  • 6
  • 52
  • 69
lehins
  • 9,642
  • 2
  • 35
  • 49
  • 2
    This is an amazingly well written answer, thanks. Now whether flower is worth all the above mess - that's a different question. – Mikle Aug 11 '14 at 13:38
  • 3
    Just wanted to point out that flower seems to have a localhost option now: `celery flower -A proj --address=127.0.0.1 --port=5555` – Raj Jan 16 '15 at 19:34
  • Worth mentioning that there's a security vulnerability in OAuth authentication. https://github.com/mher/flower/issues/1217 – Denys Halenok Jun 07 '22 at 12:40
17

I have figured out it using proxy on Django side https://pypi.org/project/django-revproxy/. So Flower is hidden behind Django auth which is more flexible than basic auth. And you don't need rewrite rule in NGINX.

Flower 0.9.5 and higher

URL prefix must be moved into proxy path: 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())

Flower 0.9.4 and lower

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
Petr Přikryl
  • 1,641
  • 4
  • 22
  • 34
  • 1
    This is great. The one change I made to work on the server was `upstream = 'http://{}:{}'.format('localhost', 5555)` – GKeps Mar 05 '21 at 20:23
  • I tried this on latest version of flower, and this changes nothing. There is still no auth while accessing `http://localhost:5555/` or `http://localhost:5555/flower/` (Ran flower with url prefix). Did I do something wrong? – Aditya Singh May 03 '23 at 11:32
11

I wanted flower on a subdirectory of my webserver, so my nginx reverse proxy configuration looked like this:

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;
}

Now I can get to flower (password-protected) via www.example.com/flower

Most of this is derived from the Flower documentation page about configuring an nginx reverse proxy:

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

Scott Stafford
  • 43,764
  • 28
  • 129
  • 177
  • just wondering is that have a way to clear the basic authentication with timeout method or something? i do like to clear the authentication after a time.. – luvwinnie Jan 04 '20 at 15:16
  • @Leow see https://stackoverflow.com/questions/8034899/%20nginx-auth-basic-time-limitation – Scott Stafford Jan 09 '20 at 16:12
4

I followed @petr-přikryl's approach using a proxy view. However I couldn't get it to verify authentication (I don't think test_func is ever called). Instead I chose to embed this in the Django Admin views and use AdminSite.admin_view() (as described here) to wrap the view with Django Admin authentication.

Specifically, I made the following changes:

# 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)

Lastly, we need to make sure that --url_prefix is set when running flower, so I set it to run like this in our production and dev environments:

celery flower --app=my_app.celery:app --url_prefix=admin/flower
jwadsack
  • 5,708
  • 2
  • 40
  • 50
2

To offload the django app, I suggest you use the X-Accel-Redirect header in order to use nginx to proxy the Flower server. It goes as follow:

  1. the user requests the flower path (e.g. /task)
  2. nginx proxy_pass the request to your app, as usual
  3. your django app chooses to accept or reject the request (e.g. based on authentification)
  4. if your app accepts the request, it returns a response with X-Accel-Redirect HTTP-header together with a string of an internal location, i.e. a path that cannot be accessed directly by the user
  5. nginx intercepts the response instead of forwarding it to the user and uses it as a new path with the possibility this time to access internal locations, in our case the Flower server

If the request is rejected, simply do not use X-Accel-Redirect and handle the case as any other rejected request you'd implement.

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

It is important to change the url-prefix of Flower:

celery flower --unix-socket="/var/run/celery/flower.sock" --url-prefix="task"
  • This was what worked for me, and exactly how I wanted it too. The only location I used is "/protected/task" – iChux Nov 03 '22 at 14:34
2

This is a reply to Petr Přikryl's post. django-revproxy fails to work on my Django 4.1.x project. I am encountering error AttributeError: 'HttpResponse' object has no attribute '_headers'. Many others are facing the same issue. brianmay in the issue thread claims, "I think this project is basically dead, sorry."

I went with a different library to serve as a workaround.

Install django-proxy

This is what my code looks like.

# 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)

Then run celery with

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

You can then view it in your browser, served through Django, at http://localhost:8000/flower/.

Additional notes:

--url_prefix= is important because this will allow the proxy to serve the static files that flower requests.

If you are using docker compose, then you will likely need to change the hostname in the remoteurl string in the flower function to reflect the same of the service. For example, my service is appropriately called flower in my docker-compose.yaml file. Therefore, I would change the string from f"http://localhost:5555/flower/" to f"http://flower:5555/flower/"

ONMNZ
  • 322
  • 3
  • 8
1

Yep there's not auth on flower, since it's just talking to the broker, but if you run it over SSL then basic auth should be good enough.

Nick
  • 376
  • 3
  • 8
0

How would HTTP and HTTPS affect Celery security? What user logins are you referring to?

Flower monitors to a Celery queue by attaching to the workers. When setting up Flower you need to provide connection string [broker]://[user_name]:[password]@[database_address]:[port]/[instance]. User name and password are the credential to log into the database of your choice.

If you're referring to this login, wouldn't simply disable/remove their logins be suffice?

adam
  • 238
  • 4
  • 14
  • I am referring to the webui that Flower provides. I was not under the impression that a user name and password are required to connect to the web interface that Flower provides. Else, even if there is one, the web ui talks over HTTP, not HTTPS, so I question the security aspect of controlling Celery through a non-secure web interface. – mh00h Oct 30 '13 at 18:57
  • 1
    Why would a login be necessary to access Flower UI? In order for Flower to work you already have access to the Celery backend broker, no? If you're afraid of snooping your network packets, set up access control so only internal machines can access the server runs Celery. Or in our practice, only a handful of people who can log into the production servers can run Flower. – adam Oct 30 '13 at 19:20
  • Ah, this is where I'm new to this. I am using celery to run time-intensive tasks on the same (remote) machine as my django server. To clarify, you are suggesting I set up iptables to allow only the localhost access to http://server:5555, and then use ssh port forwarding to access Flower? I had thought there would be a more practical https option w/ a login, but no? Else as I understand it, once Flower is set up to log into the broker w/ credentials, anyone can access the ui itself, no? – mh00h Oct 30 '13 at 20:28
  • Also, are you saying that Flower is not something that one would never leave running all the time on a production server, even given the resources to do so? – mh00h Oct 30 '13 at 20:43
  • 1
    The HTTPS option might be technically possible but there is much manual work to set it up (registering certificates, changing Flower source code to use that certificates if that's possible at all). In my opinion, setting up iptables and port blocking is much easier alternative. And I'd rather lock down the machine runs Flower and ssh into the machine to run it. Essentially you don't want to allow access to Flower except the request comes from localhost. – adam Oct 30 '13 at 20:48
  • 1
    You can leave Flower running on the same server as Celery or Web Server. But I wouldn't recommend it. In my practice we have separate AWS instances hosting different applications (web server, celery, etc). So in case one of them fails, we can easily spin up another instance without affecting one another. – adam Oct 30 '13 at 20:51
  • my AWS machine's permissions have locked down port 6379 only to other production infrastructure. I have access to the actual machine Redis is running on (ssh key). Is it possible to somehow port forward through that? (I'm new to port-forwarding) – JiminyCricket Jan 27 '14 at 01:44