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.
Related
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.
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.
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.
I want to create a localhost-only API in Django and I'm trying to find a way to restrict the access to a view only from the server itself (localhost)? I've tried using:
'HTTP_HOST',
'HTTP_X_FORWARDED_FOR',
'REMOTE_ADDR',
'SERVER_ADDR'
but with no luck.
Is there any other way?
You could configure your webserver (Apache, Nginx etc) to bind only to localhost.
This would work well if you want to restrict access to all views, but if you want to allow access to some views from remote users, then you'd have to configure a second Django project.
The problem is a bit more complex than just checking a variable. To identify the client IP address, you'll need
request.META['REMOTE_ADDR'] -- The IP address of the client.
and then to compare it with the request.get_host(). But you might take into account that the server might be started on 0.0.0.0:80, so then you'll probably need to do:
import socket
socket.gethostbyaddr(request.META['REMOTE_ADDR'])
and to compare this with let's say
socket.gethostbyaddr("127.0.0.1")
But you'll need to process lots of edge-cases with these headers and values.
A much simpler approach could be to have a reverse proxy in front of your app, that sends let's say some custom_header like X_SOURCE=internet. Then you can setup the traffic from internet to goes through the proxy, while the local traffic(in your local network) to go directly to the web server. So then if you want to have access to a specific view only from your local network, just check this header:
if 'X_SOURCE' in request.META:
# request is coming from internet, and not local network....
else:
# presumably we have a local request...
But again - this is the 'firewall approach', and it will require a some more setup, and to be sure that there is no possible access to the app from outside, that doesn't go through the 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.