Apache `SetEnvIf` and duplicated headers - apache

We are using this kind of configuration to grant access to one of our sites
<LocationMatch "/*">
Order deny,allow
Deny from all
Allow from 127.0.0.0/8
SetEnvIf X-Forwarded-For "(,| |^)192\.168\." WhiteIP
SetEnvIf X-Forwarded-For "(,| |^)172\.(1[6-9]|2\d|3[0-1])\." WhiteIP
SetEnvIf X-Forwarded-For "(,| |^)10\." WhiteIP
Allow from env=WhiteIP
</LocationMatch>
Indeed, there is another reverse proxy in front of this Apache server so all clients will have the header.
Problem is sometimes client have others proxies on their side and the X-Forwarded-For Header wil be either duplicated or concatenated. We handle the concatenation correctly with the (,| |^) regexp trick, but the problem is that Apache seems to run the SetEnvIf only against the first occurrence of the Header.
Documentation is unclear about this behavior. Any idea on how to handle this kind of case ? (note: we cannot control how our reverse proxy works, only Apache) Could that be qualified as a bug ? I couldn't find the right way to ask google about this and found no result. I've also tried digging into the mod_setenvif's code but that's out of my league.
Precision : CentOS 6, Apache 2.2.15 latest patch version

If SetEnvIf sees regex-like characters in the first argument, it will go into a mode where it iterates over all headers that match the regex until there's a match.
You could use this by specifying ^X-Forwarded-For$ which would iterate over the multiple occurrences of this 1 header.
This is a workaround for the longstanding behavior of how many modules treat multiple occurrences. This should be documented better as a module-specific solution.

Related

Apache forward proxy, only allow proxy to specific hosts, deny everything else

I've set up a forward proxy using Apache 2.4. I want to allow proxy users access to seven different domains and deny everything else. I've configured the access to the seven domain as follows:
# Allow proxy only to these test sites
<ProxyMatch ^https\:.{1,20}\.?test\.(com|io|zone)>
Include /etc/apache2/sites-available/password-protect.conf
</ProxyMatch>
<ProxyMatch ^https\:.{1,20}\.?testhost\.(cloud|com|io)>
Include /etc/apache2/sites-available/password-protect.conf
</ProxyMatch>
<ProxyMatch ^https\:.{1,20}\.?test-content\.com>
Include /etc/apache2/sites-available/password-protect.conf
</ProxyMatch>
I've temporarily included password protection in each of the ProxyMatch directives so I can verify the regex is being matched. That part works properly. The problem I have is I cannot figure out a way to deny everything else. I've added a ProxyMatch tag to deny * and it does deny everything. Apache doesn't just find the first matching ProxyMatch and then stop. The last deny all matches everything.
I think I can create a complex regex where I take the three ProxyMatch regex expressions, add a NOT operator to each and "or" them all together. That seems like a huge mess. If I add more regex matches later, my "catch all" ProxyMatch will be unmaintainable. Just want to check to see if there is a better way to create a catch-all deny for any proxy requests that do not match the three ProxyMatch expressions above. I've already looked at all the proxy directives in the mod_proxy documentation. Don't see any way to do this without being messy.

Apache ProxyPass not forwarding original http headers to workers

I am sending a request to httpd with a setHeader, named.
I see that header if I do not use ModProxy, using getRequestHeader with the header name.
However I need the header information in the ProxyPass workers.
How can I get Apache to forward the headers in ProxyPass.
I have searched for an answer for this for quite a while but I cannot find something that works.
My ProxyPass directive:
<IfModule mod_proxy_fcgi.c>
#No PATH_INFO with mod_proxy_fcgi unless this is set
SetEnvIf Request_URI . proxy-fcgi-pathinfo=1
ProxyPass "/gas30/" "fcgi://192.168.1.5:6394/"
Alias "/gas30" "/opt/fourjs30/gst/gas/bin/fastcgidispatch enablereuse=on"
ProxyPass "/gas31/" "fcgi://192.168.1.5:6395/"
Alias "/gas31" "/opt/fourjs31/gst/gas/bin/fastcgidispatch enablereuse=on"
</IfModule>
I am using Apache/2.4.6 (CentOS 7)
Thanks in advance
Johan
I use tcpdump to display the header received on Listen port 6380
Just to make sure it is there.
http_auth: Y2FuY3VuQG1ic2JiYW5rLmNvbScgJzExMTExMTExJyAnJDJhJDEyJDJwMlFOMVNZVURldUlpcWw2R3ZWMk83Mi45QUtXSlROcUlnWDRFQVUvZnJjU01SZnJNQXVL
That is the correct header.
I have tried a lot of things to forward that to port 6395.
I am totally stuck on that though.
The last thing I tried was to hard code it under Location.
But I do not see it in tcpdump
<Location /gas31>
Header add http_auth "Y2FuY3VuQG1ic2JiYW5rLmNvbScgJzExMTExMTExJyAnJDJhJDEyJDJwMlFOMVNZVURldUlpcWw2R3ZWMk83Mi45QUtXSlROcUlnWDRFQVUvZnJjU01SZnJNQXVL"
RequestHeader set http_auth "Y2FuY3VuQG1ic2JiYW5rLmNvbScgJzExMTExMTExJyAnJDJhJDEyJDJwMlFOMVNZVURldUlpcWw2R3ZWMk83Mi45QUtXSlROcUlnWDRFQVUvZnJjU01SZnJNQXVL"
Order Deny,Allow
Deny from all
Allow from all
</Location>
I have come to the end of the line with this one, I tried everything and I cannot get the request header to pass through.
.
I can either put my header in the JSON messages I send or move to nginx.
Since I have been using httpd for years I will take the first option.
I saw this post but I cannot add a comment due to reputation.
Is this relevant ?
If it is where do I put these instructions ?
It seems this line preserves the Header for reasons I don't quite understand:
SetEnvIf HTTP_MY_HEADER "(.*)" MY_HEADER=$0
The reason I don't understand this is that I am setting an Env var here, not a header -- are Env vars automatically turned into headers?
I though I might have to do this also, but was unnecessary:
RequestHeader set HTTP_MY_HEADER "${MY_HEADER}e"
I suppose this is an answer as "it works", although I would love to know why...

Get mod_proxy to pass a custom header to backend

I have a Python backend that is being reverse proxied by Apache/mod_proxy using fcgi (httpd 2.4 on rhel7).
I have a client that sets a custom header in the request, however mod_proxy does not appear to be sending that header on to the backend.
I know something similar exists for the host as ProxyPreserveHost - I would like to know how to do something similar for a custom header.
Can I do that with mod_proxy, or will I need to fall back on to mod_rewrite in some way?
TIA
It seems this line preserves the Header for reasons I don't quite understand:
SetEnvIf HTTP_MY_HEADER "(.*)" MY_HEADER=$0
The reason I don't understand this is that I am setting an Env var here, not a header -- are Env vars automatically turned into headers?
I though I might have to do this also, but was unnecessary:
RequestHeader set HTTP_MY_HEADER "${MY_HEADER}e"
I suppose this is an answer as "it works", although I would love to know why...

.htaccess - how to set headers dynamically per domain?

I'm trying to get CORS functioning with multiple domains.
Header add Access-Control-Allow-Origin "http://localhost, http://multiplay.io"
However, it seems that most browsers only support one domain. I've been told that the solution is to set the header per incoming domain.
How do you do this using the .htaccess file?
If it's only two values you wish to alternate between, you can use SetEnvIf to differentiate between the two.
SetEnvIf Referer "^http://localhost/" is_localhost
Header add Access-Control-Allow-Origin http://localhost env=is_localhost
Header add Access-Control-Allow-Origin http://multiplay.io env!=is_localhost
There may be a more elegant solution, but something like the above (untested) directives should work.
(Note that it is trivial to forge a Referer header, so be aware of the security implications of forged Referer headers when using Referer headers for pretty much anything.)
Additionally, if you just want to allow all hosts, you can specify * instead of listing multiple hostnames:
Header add Access-Control-Allow-Origin *
But I assume you already knew that and don't want to be that permissive.

Set REMOTE_ADDR to X-Forwarded-For in apache

In a situation where Apache is sitting behind a reverse proxy (such as Squid), the cgi environment variable REMOTE_ADDR gets the address of the proxy rather than the client.
However, the proxy will set a header called X-Forwarded-For to contain the original IP address of the client so that Apache can see it.
The question is, how do we get Apache to replace REMOTE_ADDR with the value in the X-Forwarded-For header so that all of the web applications will transparently see the correct address?
You can use mod_rpaf for that. http://stderr.net/apache/rpaf/
Currently apache module mod_remoteip is the recommended way to do this; rpaf hasn't been reliably maintained, and can cause problems.
Note that the X-Forwarded-For header may contain a list of IP addresses if the request has traversed more than one proxy. In this case, you usually want the leftmost IP. You can extract this with a SetEnvIf:
SetEnvIf X-Forwarded-For "^(\d{1,3}+\.\d{1,3}+\.\d{1,3}+\.\d{1,3}+).*" XFFCLIENTIP=$1
Note the use of $1 to set the XFFCLIENTIP environment variable to hold the contents of the first group in the regex (in the parentheses).
Then you can use the value of the environment variable to set headers (or use it in Apache log formats so that the logs contain the actual client IP).
In addition to mod_rpaf as mentioned before, it appears that mod_extract_forwarded will perform this function as well.
One advantage to mod_extract_forwarded is that it is available from EPEL for RHEL/CentOS servers whereas mod_rpaf is not.
It appears that neither of these two modules allow you to whitelist an entire subnet of proxy servers, which is why the CloudFlare folks created their own plugin: mod_cloudflare which, it should be noted, is not a general-purpose tool like the other two; it contains a hardcoded list of CloudFlare subnets.
Yes, we can do this.
Just add a auto_prepend_file in your PHP.ini like auto_prepend_file = "c:/prepend.php"
and in this file add this:
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
You need the MOD_REMOTEIP in apache width RemoteIPHeader X-Real-IP.
Cheers,
Guiremach
Since Apache 2.4 there is mod_remoteip built-in module that does this.
Enable mod_remoteip
(e.g. a2enmod remoteip)
Create a list of trusted IP ranges (the IPs from which you accept the remote IP header). You can put them in a file like conf/trusted-ranges.txt
Add this line to the Apache config:
RemoteIPTrustedProxyList conf/trusted-ranges.txt
Change your log file formats to use %a instead of %h for logging the client IP.
For Cloudflare you need to trust all their IP ranges and use a custom header CF-Connecting-IP:
RemoteIPHeader CF-Connecting-IP
You can get Cloudflare ranges like this:
curl https://www.cloudflare.com/ips-v4 > trusted-ranges.txt
curl https://www.cloudflare.com/ips-v6 >> trusted-ranges.txt
Unfortunately,
at the time of this writing, none of the backports and forks at freshports.org, people.apache.org or gist.github.com worked. They were all based on an early alpha version of apache httpd 2.3 which was neither compatible with current versions of 2.2 nor 2.4.
So after hours of wasting time while trying to adjust the backports to create a real working one for httpd 2.2, I decided to move to httpd 2.4. Within httpd 2.4, mod_remoteip works smoothly, even if a load balancer has permanent keepalive connections which it uses to proxy requests from different actual client ip addresses to the backend. I'm not sure if the other modules can handle this situation (changing client ip addresses on each request within the same connection).
Remember that this value can be spoofed. See http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ for a real-life example with Cross-site Scripting consequences.
You can install the module mod_extract_forwarded and set MEFaccept parameter to all.