I'm serving my django app behind a reverse proxy
The internet -> Nginx -> Gunicorn socket -> Django app
In the nginx config:
upstream my_server {
server unix:/webapps/my_app/run/gunicorn.sock fail_timeout=0;
}
The SSL is set up with certbot at the nginx level.
request.build_absolute_uri in views.py generates http links. How can I force it to generate https links?
By default Django ignore all X-Forwarded headers, base on Django docs.
Force read the X-Forwarded-Host header by setting USE_X_FORWARDED_HOST = True and set SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'). So in settings.py:
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
I use django behind apache2 so my solution was to put this on apache2
<VirtualHost *:443>
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
After adding headers mod:
a2enmod headers
And this on django setting.py:
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
With this all my build_absolute_uri started with https
There's a note in the django documentation here https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.build_absolute_uri:
Mixing HTTP and HTTPS on the same site is discouraged, therefore build_absolute_uri() will always generate an absolute URI with the same scheme the current request has. If you need to redirect users to HTTPS, it’s best to let your Web server redirect all HTTP traffic to HTTPS.
My django Rest API return image URL path http://blah/media/blah.jpg.
But I want to return https://blahblah.
Is there any attribute in settings.py?
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
I Inserted this and tried
SECURE_SSL_REDIRECT = True
but it throws 301 code and nothing rendered.
Media setting
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
For serving media files there are two ways to do:
By serving media and static files using nginx, in that case you need
to enable ssl in nginx and it will come as https://
by returning media file path from rest framework like /media/image1.jpg and adding your domain at client side like
https://mydomainxyz.com/media.image1.jpg
When you activate SECURE_SSL_REDIRECT in django it redirect every request to https server django development server do not work on https you will need nginx or apache server for that and enable ssl settings. This is the reason nothing is getting rendered. For more read here in django documentation
Previously I was working on my Django app on a local server and all my settings work with django-allauth (I was using manage.py runserver rather than nginx + gunicorn for staging/production)
Now, I'm implementing django-allauth to my staging website, but I can't make it work on my staging server (Note: In this example I've replaced my domain name to mydomain.com).
Basically after I clicked the link to login with Facebook at http://staging.mydomain.com/accounts/login/, it redirects to https://www.facebook.com/dialog/oauth?response_type=code&state=YXdAxg2WiIBo&redirect_uri=http%3A%2F%2Fstaging.mydomain.com%2Faccounts%2Ffacebook%2Flogin%2Fcallback%2F&client_id=1600059933550804&scope=email&auth_type=reauthenticate and I get the following errors:
Given URL is not allowed by the Application configuration.: One or
more of the given URLs is not allowed by the App's settings. It must
match the Website URL or Canvas URL, or the domain must be a subdomain
of one of the App's domains.
Here are my setting files:
nginx conf file
server {
listen 80;
server_name staging.mydomain.me;
location /static {
alias /home/myusername/sites/staging.mydomain.me/static;
}
location / {
proxy_set_header Host $host;
proxy_pass http://unix:/tmp/staging.mydomain.me.socket;
}
}
settings.py
...
SITE_ID = 1
LOGIN_REDIRECT_URL = '/'
ACCOUNT_EMAIL_REQUIRED = True
SOCIALACCOUNT_PROVIDERS = {
'facebook': {
'SCOPE': ['email'],
'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
'METHOD': 'oauth2',
'VERIFIED_EMAIL': False,
}
}
AUTH_USER_MODEL = 'users.User'
ACCOUNT_ADAPTER = 'users.adapters.AccountAdapter'
ACCOUNT_SIGNUP_FORM_CLASS = 'users.forms.UserApplicationForm'
ACCOUNT_LOGOUT_ON_GET = True
...
Settings in Facebook app
Settings in Sites [http://staging.mydomain.com/admin/sites/site/1/]
Settings in Social application [http://staging.mydomain.com/admin/socialaccount/socialapp/2/]
Try one of theese 2 things:
create a test app for your facebook app, and set the staging.mydomain.com domain for it, configure a brand new social app (allauth) with the new api key and secret (for your new test app), a new django site, and so on...
change your facebook app settings to have staging.mydomain.com as its app domain.
I think both solutions will work, but I prefer the first one, as your staging app perfectly fits in definition of 'test app' and therefore, I think is a more elegant solution.
Hope this helps you.
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.
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 :)
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
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
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
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:
the user requests the flower path (e.g. /task)
nginx proxy_pass the request to your app, as usual
your django app chooses to accept or reject the request (e.g. based on authentification)
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
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"
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.
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?
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/"
I'm using Django 1.0.2 and trying to figure out how to get the #login_required working correctly.
When I use the built-in server it redirects to the default login_url, or my LOGIN_URL as defined in settings.py as expected.
What is not clear to me is how to deploy to the server where my site is not at the root. In my templates I use the url template tag, and in the views I can access request.META['SCRIPT_NAME'], but for some reason it doesn't seem to apply to the LOGIN_URL used.
What am I missing?
Thanks.
There is a setting called FORCE_SCRIPT_NAME that you should use to set the location of your script, if it's not hosted at the root directory. You can add it to the login/logout URLs:
# This is needed because we're running the app in non-root directory of web server
FORCE_SCRIPT_NAME = '/myapp.cgi/'
LOGIN_URL = FORCE_SCRIPT_NAME + 'accounts/login/'
LOGOUT_URL = FORCE_SCRIPT_NAME + 'accounts/logout/'
Reference: https://docs.djangoproject.com/en/dev/ref/settings/#force-script-name