Diogo Freire

Fixing the damn ALLOWED_HOSTS issue with Amazon Elastic Beanstalk and Django

I’ve been banging my head against this one for weeks and finally got on top of it.

Django has a mechanism to prevent HTTP Host header attacks and is best practice to set a ALLOWED_HOSTS variable in your settings with the known hosts and URLs your application you’ll run in.

Trick is, if you’re using Amazon Beanstalk it sometimes will deploy your application in a random host with an IP like 3.12.70.128 (example), and that is a random number, which means you can’t list that in your ALLOWED_HOSTS setting. That means a whole bunch of noise and if you like me want to keep track of your production issues in Sentry, your quota will evaporate really quickly as health checks can happen thousands of times a day.

There were a few solutions suggested in StackOverflow but all of them missed the catch where it looks like in some cases the machine seems to have a hostname but queries the application via the IP. (I don’t fully understand what happens, any education is welcome). I finally managed to del

Steps

  • Login to the EB EC2 instance.

  • Make a copy of the /etc/nginx/nginx.conf file. ABSOLUTELY DO THIS - I recommend putting that in your application code should the future require you to roll back.

  • Create a directive to replace calls from IP with a custom hostname that you can add to ALLOWED_HOSTS.

  • Copy the contents of /etc/nginx/conf.d/elasticbeanstalk/00_application.conf and put them in the server context of the above.

  • Update the proxy_set_header in the location directive to $my_host.

  • Comment out or remove the directive include conf.d/elasticbeanstalk/*.conf; from your nginx.conf file.

What does it look like

In the root of your application create a file under .platform/nginx/nginx.conf

You might have to adapt the code below depending if you have any specifics fo your setup, so check the comments to what actually does the magic.

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    200000;

events {
    worker_connections  1024;
}

http {
    server_tokens off;
    
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    include       conf.d/*.conf;

    map $http_upgrade $connection_upgrade {
        default     "upgrade";
    }

    server {
        listen        80 default_server;
        access_log    /var/log/nginx/access.log main;

        client_header_timeout 60;
        client_body_timeout   60;
        keepalive_timeout     60;
        gzip                  off;
        gzip_comp_level       4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # ELB MAGIC HERE - if we detect an IP, set host to a custom domain
        set $my_host $host;
        if ($host ~ "\d+\.\d+\.\d+\.\d+") {
            set $my_host "elb.mydomain.com"; # ADD THIS DOMAIN TO ALLOWED HOSTS
        }

        location / {
            proxy_pass          http://127.0.0.1:8000 ;
            proxy_http_version  1.1;

            proxy_set_header    Connection          $connection_upgrade;
            proxy_set_header    Upgrade             $http_upgrade;
            # THE BELOW WILL SET THE DOMAIN TO YOUR CUSTOM DOMAIN ABOVE IF IT'S FROM AN IP
            proxy_set_header    Host                $my_host;
            proxy_set_header    X-Real-IP           $remote_addr;
            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        } 

        location /static {
            alias /var/app/current/static;
            access_log off;
        }       

        # THE EXTRA FILES FROM ELB CONTAIN THE location / DIRECTIVE BELOW
        # P.S. I don't love this
        # Include the Elastic Beanstalk generated locations
        # include conf.d/elasticbeanstalk/*.conf;
    }
}

References:

built with btw btw logo