Convince Apache of the original client protocol
Asked Answered
F

2

13

So I have a relatively straight forward server stack utilizing a SSL offloading and an HTTP load balancer. The setup looks something like this:

(client) -> (SSL offload - stud) -> (balancer - haproxy) -> (http server - apache)

My question isn't about plugging all this up. It works great, and I'm honestly blown away at how straight forward it has been to set this up. I'll also add that HTTP clients connect directly to haproxy, thereby bypassing the SSL offloaded. And for the record, there are redundant partners for each of the above pieces.

The problem is somewhat abstract. I'll start with a demonstration. The client makes a request through the setup to https://myserver.tld/scp (some headers removed to keep things clear)

GET /scp HTTP/1.1
Host: myserver.tld
(headers added by haproxy)
X-Forwarded-For: [original::client:ip]
X-Forwarded-Proto: https

And the server responds with

HTTP/1.1 301 Moved Permanently
Date: Wed, 03 Jul 2013 03:16:25 GMT
Server: Apache/2.2.15 (CentOS)
Location: http://myserver.tld/scp/
Content-Length: 344
Content-Type: text/html; charset=iso-8859-1

So Apache mod_dir is sending a redirect to the same URL with a trailing slash. It's proper to do so. That isn't the issue. The issue is that the HTTPS protocol was lost. Again, I think Apache is correct to redirect to an HTTP URL, after all, the connection it received from the aforementioned stack is a regular HTTP connection.

So from the perspective of Apache, the client requested a regular HTTP connection. The HTTPS flag is off, along with all other SSL information, because the SSL part of the session is handled by stud.

Although (inside Apache) I have a valid X-Forwarded-Proto header, I cannot find a way to tell Apache that the original client connection was HTTPS and that the directory slash redirect by mod_dir should use the https:// protocol. What am I missing?

The only thing I've come up with is to rewrite the Location header inside haproxy for the HTTPS forwarded connections to replace http:// with https://, but I really don't think that approach is very elegant. I would prefer for Apache (and (don't hurt me) PHP further down the chain) to be informed and treat the connection like a normal HTTPS connection.

Please help!

PS - I've heard it said before if you have to ask, then you're doing it wrong. Perhaps that's the root issue here, but this seems like such a simple dilemma and I feel like the only one ever to have arrived here.

Forbidden answered 3/7, 2013 at 3:48 Comment(0)
K
10

You can do this by including the protocol in the ServerName directive:

ServerName https://my-server-name

According to the Apache docs:

Sometimes, the server runs behind a device that processes SSL, such as a reverse proxy, load balancer or SSL offload appliance. When this is the case, specify the https:// scheme and the port number to which the clients connect in the ServerName directive to make sure that the server generates the correct self-referential URLs.

Kelantan answered 16/4, 2014 at 22:28 Comment(2)
This does not work if you want to have multiple entry points to the application where one is https:// and the other is http://. mod_dir will always use the scheme specified in the ServerName directive. There are situations where the production URL will have https:// but internal urls (for testing, etc) may have http, especially in SSL/TLS offloading situations where you want to directly test/monitor the Apache instances. Now, it may not matter if the 'non-canonical' URL does not generate redirects correctly, but it's messy :/Mosaic
@Mosaic In that case, you can create separate VirtualHosts for the http and https sites, with identical settings except for the port and ServerName.Kelantan
T
9
  1. The directory slash redirect: What you want is mod_rewrite.

    RewriteEngine On
    RewriteCond %{HTTP:X-Forwarded-Proto} =https
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(.+[^/])$          https://www.example.com/$1/  [R=301,L,QSA]
    

    If the header is set to https and the requested filename is a directory (-d), then rewrite that (replace example.com with your own domain).

  2. As for making PHP treat the connection like a normal HTTPS connection, set the environment variable HTTPS to on:

    SetEnvIf X-Forwarded-Proto https HTTPS=on
    
Thadeus answered 26/11, 2013 at 12:55 Comment(2)
I had to adjust suggestion 1 to get it to work properly for my use. First, I switched the second RewriteCond to: RewriteCond %{LA-U:REQUEST_FILENAME} -d To get the matching working properly (based on Apache's docs, in particular the note about "URL-based look-ahead". I also adjusted the rewrite itself to dynamically include the hostname, and avoid a duplicate '/': RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [R=301,L,QSA] The $1 var itself provides the initial slash, so it's not needed in the rewrite URL.Pleasant
RewriteOptions AllowNoSlash is needed with apache 2.4.Yaroslavl

© 2022 - 2024 — McMap. All rights reserved.