Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django and Elastic Beanstalk URL health checks

I have a Django webapp. It runs inside Docker on Elastic Beanstalk.

I'd like to specify a health check URL for slightly more advanced health checking than "can the ELB establish a TCP connection".

Entirely reasonably, the ELB does this by connecting to the instance over HTTP, using the instance's hostname (e.g. ec2-127-0-0-1.compute-1.amazonaws.com) as the Host header.

Django has ALLOWED_HOSTS which validates the Host header of incoming requests. I set this to my application's external domain via environment variable.

Unsurprisingly and entirely reasonably, Django thus rejects ELB URL health checks due to lack of matching Host.

We don't want to disable ALLOWED_HOSTS because we'd like to be able to trust get_host().

The solutions so far seem to be:

  • Somehow persuade Django to not care about ALLOWED_HOSTS for certain specific paths (i.e. the health check URL)
  • Do something funky like calling the EC2 info API on startup to get the host's FQDN and append it to ALLOWED_HOSTS

Neither of these seem particularly pleasant. Can anyone recommend a better / existing solution?

(For the avoidance of doubt, I believe this problem to be identical to the scenario of "Disabled ALLOWED_HOSTS, fronting HTTPD that filters on host" - I want the health check to hit Django, not a fronting HTTPD)

like image 542
Kristian Glass Avatar asked Mar 01 '16 14:03

Kristian Glass


People also ask

How does Elastic Beanstalk check health?

The health agent monitors web server logs and system metrics and relays them to the Elastic Beanstalk service. Elastic Beanstalk analyzes these metrics and data from Elastic Load Balancing and Amazon EC2 Auto Scaling to provide an overall picture of an environment's health.

How do I enable enhanced monitoring in Elastic Beanstalk?

In the navigation pane, choose Configuration. In the Monitoring configuration category, choose Edit. Under Health reporting, for System, choose Enhanced.

When should you not use Beanstalk?

Elastic Beanstalk isn't great if you need a lot of environment variables. The simple reason is that Elastic Beanstalk has a hard limit of 4KB to store all key-value pairs. The environment had accumulated 74 environment variables — a few of them had exceedingly verbose names.


1 Answers

If the ELB health check is sending its request with a host header containing the elastic beanstalk domain (*.elasticbeanstalk.com, or an EC2 domain *.amazonaws.com) then the standard ALLOWED_HOSTS can still be used with a wildcard entry of '.amazonaws.com' or '.elasticbeanstalk.com'.

In my case I received standard ipv4 addresses as the health check hosts, so a different solution was needed. If you can't predict the host at all, and it might be safer to assume you can't, you would need to take a route such as one of the following.

You can use Apache to handle approved hosts instead of propagating ambiguous requests to Django. Since the host header is intended to be the hostname of the server receiving the request, this solution changes the header of valid requests to use the expected site hostname. With elastic beanstalk you'll need to configure Apache using .ebextensions as described here. Under the .ebextensions directory in your project root, add the following to a .config file.

files:
  "/etc/httpd/conf.d/eb_healthcheck.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
        <If "req('User-Agent') == 'ELB-HealthChecker/1.0' && %{REQUEST_URI} == '/status/'">
            RequestHeader set Host "example.com"
        </If>

Replacing /status/ with your health check URL and example.com with your site's appropriate domain. This tells Apache to check all incoming requests and change the host headers on requests with the appropriate health check user agent that are requesting the appropriate health check URL.

If you would really prefer not to configure Apache, you could write a custom middleware to authenticate health checks. The middleware would have to override Django's CommonMiddleware which calls HttpRequest's get_host() method that validates the request's host. You could do something like this

from django.middleware.common import CommonMiddleware


class CommonOverrideMiddleware(CommonMiddleware):
    def process_request(self, request):
        if not('HTTP_USER_AGENT' in request.META and request.META['HTTP_USER_AGENT'] == 'ELB-HealthChecker/1.0' and request.get_full_path() == '/status/'):
            return super().process_request(request)

Which just allows any health check requests to skip the host validation. You'd then replace django.middleware.common.CommonMiddleware with path.CommonOverrideMiddleware in your settings.py.

I would recommend using the Apache configuration approach to avoid any details in the middleware, and to completely isolate Django from host issues.

like image 196
yummies Avatar answered Sep 28 '22 11:09

yummies