Tuesday, 18 October 2022

X-Forwarded-Proto header and django server enforce HSTS when being reverse proxied to , including setting up django to trust proxy server

https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-SECURE_HSTS_SECONDS

https://stackoverflow.com/questions/56572638/hsts-and-https-settings-invalid

https://stackoverflow.com/questions/41483068/djangos-httpresponseredirect-is-http-instead-of-https/41488430#41488430


 If web browser / load-balancer reverse proxy to django server(or dev server), if proxy server establishes connection with django server via https, then it does not matter what client request is (http/https), the connection between django and load-balancer will always be secure, django is_secure() checks client request via https:// or connection will always return true. If load-balancer and django server connection is not https, (which is by default), then is_scure() will always return False, causing  SECURE_HSTS_SECONDS in settings.py in django to enforce HSTS connection wont work.

https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-SECURE_PROXY_SSL_HEADER


when SECURE_HSTS_SECONDS is sucessfully set up in django , django server will respond request with 

Response Headers:

Strict-Transport-Security:max-age=60; includeSubDomains; preload



This will enforce browser to access the current domain through https for the next minuet(60 seconds)

To fix the issue 

1) In load-balancer, web browser, need to have  (https://stackoverflow.com/questions/41483068/djangos-httpresponseredirect-is-http-instead-of-https/41488430#41488430)

X-Forwarded-Proto browser header on when reverse proxy request to django dev server. 

(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto#:~:text=The%20X%2DForwarded%2DProto%20(,your%20proxy%20or%20load%20balancer.)

The X-Forwarded-Proto (XFP) header is a de-facto standard header for identifying the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer.


To configure this for nginx (https://stackoverflow.com/questions/41483068/djangos-httpresponseredirect-is-http-instead-of-https/41488430#41488430) :

proxy_redirect off;

proxy_set_header X-Forwarded-Proto $scheme;


To configure this for apache2 2.4 :

https://webmasters.stackexchange.com/questions/97005/setting-x-forwarded-proto-under-apache-2-4

https://serverfault.com/questions/257616/requestheader-with-apache-environment-variable

        RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}

        RequestHeader set X-Forwarded-SSL expr=%{HTTPS}

If it doesn't work, you may need to install and enable the module mod_headers.


For docker-compose httpd,  mode_headers, ensure http.conf has 

LoadModule headers_module modules/mod_headers.so


For RequesetHeader for specific site, go to sites/your-site-ssl.conf (this is specified in httpd.conf like 

# To Load Customer VirtualHost Configuration files

IncludeOptional conf/sites/*.conf)


add 

<VirtualHost _default_:443>


        # Notify API server the client request being proxy reveresed is http / https (https to trigger hsts in API server)

        RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}

        RequestHeader set X-Forwarded-SSL expr=%{HTTPS}

        # Proxy reverse rules

ProxyPreserveHost On

# ProxyPass / http://django_ip:8000/

    # ProxyPassReverse / http://django_ip:8000/

        # Fortidemo API endpoint

ProxyPassMatch .*/saml/.* http://django_ip:8000

    ProxyPassReverse .*/saml/.* http://django_ip:8000

        ProxyPassMatch .*/api/.* http://django_ip:8000

    ProxyPassReverse .*/api/.* http://django_ip:8000

        # Django css end point

        ProxyPassMatch .*/static/.* http://django_ip:8000

    ProxyPassReverse .*/static/.* http://django_ip:8000

</VirtualHost>


2) In django settings.py

add the following : 


# httpd has x-forwarded proto to indicate client http/https being forwarded, this indcates django only accept https

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Increase its value after test is success

# After everything goes well , good default value is to set it to a month ( 2592000): 

https://learndjango.com/tutorials/django-best-practices-security#:~:text=The%20SECURE_HSTS_SECONDS%20setting%20is%20set,month%2C%202%2C592%2C000%20seconds%2C%20instead.

SECURE_HSTS_SECONDS = 60

SECURE_HSTS_INCLUDE_SUBDOMAINS = True

SECURE_HSTS_PRELOAD = True


SECURE_PROXY_SSL_HEADER 

is (https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-SECURE_HSTS_SECONDS)

A tuple representing an HTTP header/value combination that signifies a request is secure. This controls the behavior of the request object’s is_secure() method.


By default, is_secure() determines if a request is secure by confirming that a requested URL uses https://. This method is important for Django’s CSRF protection, and it may be used by your own code or third-party apps.


If your Django app is behind a proxy, though, the proxy may be “swallowing” whether the original request uses HTTPS or not. If there is a non-HTTPS connection between the proxy and Django then is_secure() would always return False – even for requests that were made via HTTPS by the end user. In contrast, if there is an HTTPS connection between the proxy and Django then is_secure() would always return True – even for requests that were made originally via HTTP.


In this situation, configure your proxy to set a custom HTTP header that tells Django whether the request came in via HTTPS, and set SECURE_PROXY_SSL_HEADER so that Django knows what header to look for.


Set a tuple with two elements – the name of the header to look for and the required value. For example:


SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

This tells Django to trust the X-Forwarded-Proto header that comes from our proxy and that the request is guaranteed to be secure (i.e., it originally came in via HTTPS) when:


the header value is 'https', or

its initial, leftmost value is 'https' in the case of a comma-separated list of protocols (e.g. 'https,http,http'



T

No comments:

Post a Comment