Nginx/Apache: set HSTS only if X-Forwarded-Proto is https

apache-2.2http-headersnginxssl

I got the following setup:

Internet => nginx[public:80 +443, SSL termination)
=> Varnish[localhost:81] => Apache[localhost:82]

Now some sites should only be reachable via HTTPS and a valid SSL certificate. For these few exceptions I'd like to activate HSTS, either on nginx (preferred, or on Apache).

The problem:

  • On nginx, I'd need some logic à la if Host = foo.tld then set Strict-Transport-Security xxx, but according to http://wiki.nginx.org/IfIsEvil one should not use if in a location
  • On Apache, I'd need something like if X-Forwarded-Proto 443 set Strict-Transport-Security xxx, but I don't seem to be able to construct this with SetEnvIf (Apache 2.2)

Is my logic flawed? Another idea for an approach?

This is the configuration currently active:

nginx

server {
        server_tokens off;

        listen xx.xx.xxx.xxx:80;
        server_name localhost;

        location / {
                proxy_pass http://127.0.0.1:81;
                proxy_set_header X-Real-IP  $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port 80;
                proxy_set_header Host $host;
                add_header X-XSS-Protection "1; mode=block";
        }
}

server {
        server_tokens off;

        listen xx.xx.xxx.xxx:443 ssl;
        server_name localhost;

        ssl on;
        ssl_certificate /etc/ssl/foo.crt;
        ssl_certificate_key /etc/ssl/private/foo.key;

        ssl_session_timeout 10m;

        # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";

        location / {
                proxy_pass http://127.0.0.1:81;
                proxy_set_header X-Real-IP  $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port 443;
                proxy_set_header Host $host;
                add_header X-XSS-Protection "1; mode=block";
        }
}

Varnish

No special configuration.

Apache

<VirtualHost *:82>
[...] nothing special
</VirtualHost>

Best Answer

You could have multiple server blocks. So just add new server block for domains that need HSTS.

server {
    listen xx.xx.xxx.xxx:443 ssl default_server;

    # all ssl stuff
    # and other directives
}

server {
    listen xx.xx.xxx.xxx:443 ssl;
    server_name example.com other.example.com;

    # all ssl stuff
    # and other directives with HSTS enabled
}

Here first block will handle all https connections except example.com and other.example.com.

And you don't need ssl on directive if you have ssl flag in listen.

EDIT

There is another solution with only one server block:

map $scheme:$host $hsts_header {
    default "";
    https:example.com "max-age=31536000";
    https:other.example.com "max-age=31536000";
}

server {
    server_tokens off;

    listen xx.xx.xxx.xxx:80;
    listen xx.xx.xxx.xxx:443 ssl;

    ssl_certificate /etc/ssl/foo.crt;
    ssl_certificate_key /etc/ssl/private/foo.key;

    ssl_session_timeout 10m;

    # ... other ssl stuff

    location / {
        proxy_pass http://127.0.0.1:81;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header Host $host;
        add_header X-XSS-Protection "1; mode=block";
        add_header Strict-Transport-Security $hsts_header;
    }
}

We use map to define HSTS header value and use the fact, than nginx will not add header with empty value.