Dynamic Allowed Host in Django - django

I'm developing multitenant application using django. Every things works fine. But in the case of ALLOWED_HOST , I've little bit confusion, that how to manage dynamic domain name. I know i can use * for any numbers for domain, But i didn't wan't to use * in allowed host.
Here is my question is there any way to manage allowed host dynamically.

According to the Django doc,
Values in this list can be fully qualified names (e.g. 'www.example.com'), in which case they will be matched against the request’s Host header exactly (case-insensitive, not including port). A value beginning with a period can be used as a subdomain wildcard: '.example.com' will match example.com, www.example.com, and any other subdomain of example.com. A value of '*' will match anything; in this case you are responsible to provide your own validation of the Host header (perhaps in a middleware; if so this middleware must be listed first in MIDDLEWARE).
So, if you want to match a certain subdomain, you can use the subdomain wildcard as explained above. If you want a different validation, as the documentation says, the right way to do it would be to allow all hosts and write your own middleware.
If you don't know about middlewares, they are a simple mechanism to add functionality when certain events occur. For example, you can create a middleware function that executes whenever you get a request. This would be the right place for you to put your Host validation. You can get the host using the request object. Refer to the official docs on middleware if you want to know more.
A simple middleware class for your problem could look something like -
import requests
from django.http import HttpResponse
class HostValidationMiddleware(object):
def process_view(self, request, view_func, *args, **kwargs):
host = request.get_host()
is_host_valid = # Perform host validation
if is_host_valid:
# Django will continue as usual
return None
else:
response = HttpResponse
response.status_code = 403
return response
If you need more information, comment below and I will try to help.

The setting should be loaded once on startup - generally those settings don't change in a production server setup. You can allow multiple hosts, or allow multiple subdomains.
*.example.com would work, or you can pass a list of domains if I remember correctly.

Related

How to restrict django endpoint to localnetwork?

In my project, I use many FastAPI microservices to conduct the specified tasks and a Django app to handle user actions, connect with the front-end, etc.
In Django database I store information about the file paths that a microservice should obtain. I want to provide it to a microservice and this microservice only. So I want to restrict access to the endpoint to a specified port on the local network.
Other endpoints should be normally available for the web app. Using just Django cors headers will not work since I want access to most of the endpoints normally and restrict it to localhost for only a tiny subset.
CORS_ORIGIN_WHITELIST = [
"http://my_frontend_app" # most of the endpoints available from the front end
"http://localhost:9877", #the microservice that I want to provide with the path
]
Another solution might be to use from corsheaders.signals import check_request_enabled, but in the use cases that I have seen, it was usually used to broaden the access , not to restrict it (front-end should not have access to the subset of endpoints). I’m not sure whether it is a good solution.
# myapp/handlers.py
from corsheaders.signals import check_request_enabled
def cors_allow_particular_urls(sender, request, **kwargs):
return request.path.startswith('/public-api/')
check_request_enabled.connect(cors_allow_mysites)
Is there any way to create a “local cors”, e.g., in the form of a decorator? It would look somehow like:
#local_cors([“localhost:9877”])
#decorators.action(detail=False, methods=["post"])
def get_data(self, request):
return response.Response(status=200)
where localhost:9877 is the address of a microservice
Is such a solution good enough?
def get_data(self, request):
request_host = request.get_host()
data = request.data
if request_host != MAP_UPLOAD_HOST:
# we don't show the endpoint to the outside return
response.Response(status=404)
return response.Response(status=200)
Checking the Host header with get_host() may offer sufficient protection, depending on your server setup.
get_host() will tell you the value of the Host header in the request, which is data provided by the client so could be manipulated in any way. The Host header is an integral part of HTTP 1.1 in allowing multiple domains to be hosted at a single address so you might be able to depend on your server rejecting requests that aren't actually arriving from localhost with a matching header, but it's difficult to be certain.
It would likely be more reliable to check the client's network address and reject requests from all clients except those that are specifically allowed.

CORS on One Endpoint Django

I am new to Django and I have 2 endpoints in a REST API:
api.myapp.com/PUBLIC/
api.myapp.com/PRIVATE/
As suggested by the names, the /PUBLIC endpoint is open to anyone. However, I want the /PRIVATE endpoint to only accept calls from myapp.com (my frontend). How can I do this? Thanks!
Without knowing how you apps and servers are setup, I think you can solve this problem with the django-cors-headers package. I skimmed the entire read me, and the signals at the bottom looks like a solution to your problem. This part here:
A common use case for the signal is to allow all origins to access a
subset of URL's, whilst allowing a normal set of origins to access all
URL's. This isn't possible using just the normal configuration, but it
can be achieved with a signal handler.
First set CORS_ALLOWED_ORIGINS to the list of trusted origins that are
allowed to access every URL, and then add a handler to
check_request_enabled to allow CORS regardless of the origin for the
unrestricted URL's. For example:
# myapp/handlers.py from corsheaders.signals import check_request_enabled
def cors_allow_api_to_everyone(sender, request, **kwargs):
return request.path.startswith('/PUBLIC/')
check_request_enabled.connect(cors_allow_api_to_everyone)
So for most cases django-cors-headers are set as an option for the entire project, but there seems to be a way here for you to allow a subset of your api (/PUBLIC in your case) to be allowed for everyone, but the rest is private.
So your config would be
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
]
That allows myapp.com to reach everything.
cors_allow_api_to_everyone is a function checking for a truth value.
If it is true, the request is allowed.
check_request_enabled.connect(cors_allow_api_to_everyone) connects your truth-check-function to the django-cors-headers signal.

Invalid HTTP_HOST header: The domain name provided is not valid -- requests to dockerized django app using container name

Within an app composed of multiple microservices run via docker-compose, I need a way to make a request from one container (app via flask & requests), directly to the other (chart/django):
This is a simplified version of what I'm attempting.
routes.py:
#APP.route('/post_data', methods=['POST'])
def post_data():
post_data = request.get_json()
response = requests.post('http://chart_app_1:8080/json/', data=post_data)
return response.text
The response I get is an error message:
django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: 'chart_app_1:8080'. The domain name provided is not valid according to RFC 1034/1035
I'm able to make exactly this kind of request to other containers running flask apps, with no problems. I don't have any control over whether or not we use Django for this particular microservice though.
It seems that this might be because the hostname has an underscore in it: see this post. Is there a way around this? It seems like it must be possible to make a simple request between containers.
In the docker-compose file, modify the service name for avoiding underscores in the Django service.
That's the only way that I know for avoiding the error as it's not a Docker limitation.

Proper URL routing configuration for Flask with proxy pass

I'm struggling to find the proper way to setup my flask routes when moving my app to being hosted in a subdirectory. I've read a bunch of great answers on this subject, most notably this answer on adding a prefix to all routes. However, I think my situation is slightly different. I want to ONLY prefix the URLs I generate with url_for, not respond to those URLs. Is there a way to do this?
For example, assume my flask app is hosted at http://www.acme.com/tools/. The main acme.com domain is setup with a proxy pass to pass all requests from /tools/* to my app. So, my app only sees requests like /, /product_1/ even though the full URL is http://www.acme.com/tools/product_/, etc. So, my app.route rules can use / as the base route. The problem is I use url_for everywhere and I want the urls generated to be the full url like /tools/product_1, not /product_1. Is there a way to achieve this?
I've tried using blueprints but those will make the #app.route and url_for respond to /tools/xyz. Is there a more simple solution to this issue or is this a non-standard way to handle this issue?
I would take a look at this snippet: http://flask.pocoo.org/snippets/35/
I'm not sure I love the idea of modifying your WSGI environment as the only solution to this problem. You can always wrap url_for. (note that this won't work for _external=True)
def my_url_for(*args, **kwargs):
return '/tools' + url_for(*args, **kwargs)
Be sure to set your APPLICATION_ROOT to /tools so that cookies are only accessible from the app.
I found a different way to handle this without having to provide a wrapper for url_for. I'm using a custom routing rule to append the prefix. This means none of the app.route decorator calls have to be changed and all calls to url_for will automatically get the prefix added. Are there any caveats I'm missing by overriding the url_rule_class?
Add the following to your __init__.py after setting up your app.
from werkzeug.routing import Rule
class PrefixRule(Rule):
def build(self, *args, **kwargs):
domain_part, url = super(PrefixRule, self).build(*args, **kwargs)
return domain_part, u'%s%s' % (app.config['PREFIX'], url)
app.url_rule_class = PrefixRule

Django as reverse proxy

My client-server application is mainly based on special purpose http server that communicates with client in an Ajax like fashion, ie. the client GUI is refreshed upon asynchronous http request/response cycles.
Evolvability of the special purpose http server is limited and as the application grows, more and more standard features are needed which are provided by Django for instance.
Hence, I would like to add a Django application as a facade/reverse-proxy in order to hide the non-standard special purpose server and be able to gain from Django. I would like to have the Django app as a gateway and not use http-redirect for security reasons and to hide complexity.
However, my concern is that tunneling the traffic through Django on the serer might spoil performance. Is this a valid concern?
Would there be an alternative solution to the problem?
Usually in production, you are hosting Django behind a web container like Apache httpd or nginx. These have modules designed for proxying requests (e.g. proxy_pass for a location in nginx). They give you some extras out of the box like caching if you need it. Compared with proxying through a Django application's request pipeline this may save you development time while delivering better performance. However, you sacrifice the power to completely manipulate the request or proxied response when you use a solution like this.
For local testing with ./manage.py runserver, I add a url pattern via urls.py in an if settings.DEBUG: ... section. Here's the view function code I use, which supports GET, PUT, and POST using the requests Python library: https://gist.github.com/JustinTArthur/5710254
I went ahead and built a simple prototype. It was relatively simple, I just had to set up a view that maps all URLs I want to redirect. The view function looks something like this:
def redirect(request):
url = "http://%s%s" % (server, request.path)
# add get parameters
if request.GET:
url += '?' + urlencode(request.GET)
# add headers of the incoming request
# see https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpRequest.META for details about the request.META dict
def convert(s):
s = s.replace('HTTP_','',1)
s = s.replace('_','-')
return s
request_headers = dict((convert(k),v) for k,v in request.META.iteritems() if k.startswith('HTTP_'))
# add content-type and and content-length
request_headers['CONTENT-TYPE'] = request.META.get('CONTENT_TYPE', '')
request_headers['CONTENT-LENGTH'] = request.META.get('CONTENT_LENGTH', '')
# get original request payload
if request.method == "GET":
data = None
else:
data = request.raw_post_data
downstream_request = urllib2.Request(url, data, headers=request_headers)
page = urllib2.urlopen(downstream_request)
response = django.http.HttpResponse(page)
return response
So it is actually quite simple and the performance is good enough, in particular if the redirect goes to the loopback interface on the same host.