KVISH
KVISH

Reputation: 13208

Django ALLOWED_HOSTS for Amazon ELB

I'm using Django and I have the ALLOWED_HOSTS setting to include my EC2's private IP as per below:

import requests
EC2_PRIVATE_IP = None
try:
    EC2_PRIVATE_IP = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4', timeout=0.01).text
except requests.exceptions.RequestException:
    pass
if EC2_PRIVATE_IP and not DEBUG:
    ALLOWED_HOSTS.append(EC2_PRIVATE_IP)

Problem is that the above does not take into consideration the ELB's that forward the request to my EC2 instances. Is there a way to make that work programmatically? Can I request the public IP address or have setting to check the DNS instead?

I'm seeing this issue with the ELB's public IP address.

Upvotes: 20

Views: 9322

Answers (3)

tklodd
tklodd

Reputation: 1078

Fetch the Private Ip

I have also recently had this problem. The current answer to this is roughly what the OP described, but it also involves fetching an access token beforehand and using that in the request. My setup is on Elastic Beanstalk behind a load balancer, and the health checker requests the private ip of the EC2 instance and fails if the private ip is not in ALLOWED_HOSTS. When the private ip is fetched and added, then it works. The EB health checker appears to want the private ips of the instances, not of the load balancer.

I got my answer from an answer on this question. I have modified it a bit to cache the resultant ip in a temporary file for a while so that the site does not need to make the request every time someone loads a page.

import requests, tempfile, datetime, os
private_ip = None
private_ip_filepath = "{dir}/private-ip.txt".format(dir=tempfile.gettempdir().rstrip('/'))
if os.path.exists(private_ip_filepath) and os.access(private_ip_filepath, os.R_OK):
    # Check if expired.
    if datetime.datetime.now() - datetime.datetime.fromtimestamp(os.path.getmtime(private_ip_filepath)) < datetime.timedelta(seconds=3600):
        # Open file if not expired.
        try:
            with open(private_ip_filepath, "r") as f:
                private_ip = f.read().strip()
        except:
            pass
    elif os.access(private_ip_filepath, os.W_OK):
        # Remove file if expired.
        try:
            os.remove(private_ip_filepath)
        except:
            pass
if not private_ip:
    if STAGE == 'local':
        private_ip = "127.0.0.1" # Here for debugging the caching system.
    else:
        # https://stackoverflow.com/questions/47277541/how-to-dynamically-add-ec2-ip-addresses-to-django-allowed-hosts
        try:
            IMDSv2_token = requests.put('http://169.254.169.254/latest/api/token', headers={
                'X-aws-ec2-metadata-token-ttl-seconds': '3600',
            }).text
            private_ip = requests.get('http://169.254.169.254/latest/meta-data/local-ipv4', timeout=0.01, headers={
                'X-aws-ec2-metadata-token': IMDSv2_token
            }).text
        except requests.exceptions.RequestException:
            pass
    if private_ip and os.access(private_ip_filepath, os.W_OK):
        try:
            with open(private_ip_filepath, "w") as f:
                f.write(private_ip)
        except:
            pass
if private_ip:
    ALLOWED_HOSTS.append(private_ip)

The currently accepted answer here of writing middleware to always return a healthy response defeats the purpose of the health-checker. The other answer of ignoring the Host header that came from the client and replacing it with a valid one defeats the purpose of ALLOWED_HOSTS.

Upvotes: 0

Vaibhav Shelke
Vaibhav Shelke

Reputation: 374

Another simple solution would be to write a custom MIDDLEWARE which will give the response to ELB before the ALLOWED_HOSTS is checked. So now you don't have to load ALLOWED_HOSTS dynamically.

The middleware can be as simple as:

project/app/middleware.py

from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin

class HealthCheckMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # add any custom service checks here and return status
        if request.META["PATH_INFO"] == "/ping/":
            return HttpResponse("pong")

settings.py

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'app.middleware.HealthCheckMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    ...
]

Django Middleware reference https://docs.djangoproject.com/en/dev/topics/http/middleware/

Upvotes: 14

Shameem
Shameem

Reputation: 95

Fetching AWS internal IPs and adding to the ALLOWED_HOST is not the best solution. Since this Fetching will happen only on application reload. ELB IPs can change anytime.

Instead of this, We can set actual host header in the nginx, if this the request is coming from an IP.

Credit goes to: https://www.xormedia.com/django-allowed-hosts-and-amazon-elastic-load-balancer/

Upvotes: 2

Related Questions