[Django]-Block invalid HTTP_HOST headers in Apache in AWS ElasticBeanstalk

3👍

Since I haven’t found any better approach using YAML files, I am using a Python script to modify the original Apache wsgi.conf file in the most agnostic way I can think of (no matters if Amazon modifies it in the future).

I share it here in case anyone can find it useful. With this approach, you don’t have to hardcode the hosts whitelist anywhere in code.

  1. First add a new command in .ebextensions/deploy.config
container_commands:
    01__apache_block_invalid_hosts:
        command: python .ebextensions/apache_block_invalid_hosts.py

Notice that you can consider adding leader_only: true if you are not planning to change the DJANGO_ALLOWED_HOSTS environment variable at any time (explained later) and if you understand the implications.

  1. Create the python script in .ebextensions/apache_block_invalid_hosts.py
from enum import Enum, auto
import os


NEW_AUTH_DIRECTIVE = """
  Require expr "%{{HTTP_HOST}} in {{{hosts}}}"
  Options
"""

class Step(Enum):
    BEFORE_AUTH = auto()
    INSIDE_AUTH = auto()
    AFTER_AUTH = auto()

step = Step.BEFORE_AUTH
with open('/etc/httpd/conf.d/wsgi.conf', 'r') as file_in, open('../wsgi.conf', 'w') as file_out:
    for line in file_in.readlines():
        if step == Step.BEFORE_AUTH:
            file_out.write(line)
            if "<Directory /opt/python/current/app/>" in line:
                hosts = ", ".join([f"'{i}'" for i in os.environ['DJANGO_ALLOWED_HOSTS'].split('__')])
                file_out.write(NEW_AUTH_DIRECTIVE.format(hosts=hosts))
                step = Step.INSIDE_AUTH
        elif step == Step.INSIDE_AUTH:
            if "</Directory>" in line:
                file_out.write(line)
                step = Step.AFTER_AUTH
        elif step == Step.AFTER_AUTH:
            file_out.write(line)

Notice that the output path of the edited file is ../wsgi.conf and not /etc/httpd/conf.d/wsgi.conf. Trust me on this, it works.

  1. Define an environment variable with all the whitelisted hosts in the AWS EB configuration website:
DJANGO_ALLOWED_HOSTS         whatever.com__www.whatever.com__whatever.us-east-1.elasticbeanstalk.com

Notice that I am using the __ separator on purpose, instead of a comma. This is because I sometimes create/clone environments directly from my command line using eb clone while providing changes in the values of environment variables. If you do that, you can’t include commas inside values, and there is no way to escape them.

  1. Make use of the same envorinment variable from your Django settings file (I use the django-environ library to read enviroment variables from the system):
import environ
env = environ.Env()
env.read_env()
ALLOWED_HOSTS = env('DJANGO_ALLOWED_HOSTS', default='*').split('__')

0👍

Apache looks at the Host HTTP request header when it decides on the name based routing: https://httpd.apache.org/docs/2.4/vhosts/name-based.html

Just have a default virtual host serving static 404 page and allow to route to Django only when proper Host HTTP request header is specified.

It is also documented in Django: https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/#allowed-hosts

Leave a comment