Secure to secure proxy with Apache 2.4 and separate hosts - apache

I am trying to proxy a SCORM entry point on my LMS to the content on another LMS. The manifest resource href looks like:
https://example.com/training/rest/of/the/content/link?key=value
And in my httpd.conf, I have the following:
SSLProxyEngine on
RewriteRule ^proxy:.* - [F]
ProxyPass /training/ https://theirsite.com/
ProxyPassReverse /training/ https://theirsite.com/
<Location /training/>
SetOutputFilter proxy-html
Header add referer https://theirsite.com/
Header set Accept-Ranges none
RequestHeader set referer https://theirsite.com/
RequestHeader unset Accept-Encoding
ProxyHTMLEnable on
ProxyHTMLExtended on
ProxyHTMLURLMap / ppt/
ProxyHTMLURLMap https://example.com/ ppt/
ProxyHTMLURLMap https://example.com/training/ ppt/
ProxyHTMLURLMap ppt/ https://example.com/training/
</Location>
The problems I am having are this:
The value in my manifest href is correctly hitting the content host (so my proxy values are correct), but are not returning to https://example.com/training/, they are returning to https://example.com, which leads to my next problem.
I am having to map all of the URLs when the body returns to me, which is not a problem, except the client host is actually using a third-party for a particular JS library, and mapping / to a standardized value so I can then re-standardize the entire body back to https://example.com/training/, is breaking that particular link.
I have tried various rewrites and substitutions, but I feel I may be missing a key component on this proxying business :/

Related

Apache LocationMatch matching urls starting with...

I'm using apache to redirect AJAX request to server backend in my AJAX app.
Everything that starts with /service/ should go to service backend:
<LocationMatch "/service">
ProxyPass http://backend:8080/service Keepalive=On
Header set Cache-Control "no-cache, no-store, must-revalidate"
</LocationMatch>
Everything that starts with /auth goes to authentication server:
<LocationMatch "/auth">
ProxyPass http://keycloak:8090/auth/ Keepalive=On
</LocationMatch>
I was happy with my apparently working solution, unless the auth channel was added to backend, and them I've noticed, that requests to /service/auth/info are not consumed by backend, but land in authentication server.
Apparently I have some understanding problem. How should I match URLs that start with given string, and not contain it somewhere in the middle?
Use the caret (^) to indicate the beginning of the string:
<LocationMatch "^/service">

virtual host not working when cloudflare is enabled

Before cloudflare, I was able to access the phpMyAdmin link such as
example.com/ctrl/pmasetup
After CloudFlare, I get a 403 (coming from my own apache server, not CloudFlare's error page)
Forbidden
You don't have permission to access /ctrl/pmasetup on this server.
I feel like I need to add the CloudFlare's reverse proxy somewhere on the server, which I did in /etc/hosts which now looks like:
127.0.0.1 localhost localhost.localdomain
104.25.68.32 example.com <- This is the ip of my site when CloudFlare is enabled on it
I also added it in httpd.conf in the virtual host config:
<VirtualHost *:80>
ServerAdmin support#example.com
DocumentRoot /var/www/example.com
ServerName example.com
ErrorLog logs/example.com-error_log
CustomLog logs/example.com-access_log combinedio
Alias /ctrl /var/www/ctrl
<Location /ctrl>
<RequireAny>
Require all denied
Require ip {my ip}
Require ip 104.25.68.32 <- CloudFlare IP, again
</RequireAny>
</Location>
#Header always set Access-Control-Allow-Origin "http://example.com"
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "*"
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</VirtualHost>
Edit: Even when commenting the entire RequireAny block, I still get a 403 when CloudFlare is enabled.
Edit 2: if inside the location tag I ONLY have Require all granted, then it works. So, how can I disallow everybody BUT the few IP's I put initially?
Edit 3: It most likely is because CloudFlare changes your IP as a visitor, then my apache config doesn't recognize it. Any idea?
EDITED -- See below;
I believe you should allow cloudflare's ip ranges to access the site. After all, the request comes from cloudflare not the client.
CloudFlare's ip ranges can be found on their website at:
https://www.cloudflare.com/ips/
They also provide an easy "text/plain" format following both theses urls:
CloudFlare IPv4 Ranges & CloudFlare IPv6 Ranges
There's also a little tool i know of, for nginx tho, available at:
https://www.8ball.me/nginx/ngx-cfips.zip
EDIT -- I've created a new version since this post. Today actually (2017.05.31). The url is still valid. It's got a --help function detailing how to setup as a cronjob and include into nginx. Same CloudFlare urls are used.
When there is an option denying I believe you need to use RequireAll instead of RequireAny (since RequireAny is the default behaviour in 2.4 so there is no need to specify it like you do). Try it.

How to server static files + proxy context

I am wondering how to configure my httpd server to serves the following pages:
My need is to serve static content located in my /var/www/static when url is /context/static and to proxy the remaining to a tomcat server
In this order:
/context/static/* --> files served by httpd
/context/* --> resources served by tomcat
I have tried to rewrite /context/static/* to a folder pointing to my /var/www/static and added the ProxyPath directive for the remaining but I can't get it working.
What are the best practices and how to achieve that ?
Thanks in advance
Well, in fact it is quiet easy...
Having such folders configured:
/var/www/static/
|- css/*
|- js/*
\ medias/*
The following httpd configuration will redirect static/* to the /var/www and the rest will be proxied
# first rewrite for statics
RewriteEngine On
RewriteRule ^/context/static/(.+)$ /static/$1
# then proxy remaining...
ProxyPass /context http://127.0.0.1:8080/context
ProxyPassReverse /context http://127.0.0.1:8080/context
I've found the following approach that works and is quite general. (4/12/2018)
Location/Proxypass expressions always take priority over any other location block, so you have to Exclude the paths that you don't want to be proxied. the "?!" does that in the regex. Since static content is, um, static, it is not so bad to require that the apache configuration be updated if another directory is needed to be served directly for a different media type.
The following was taken from a server that was proxying a Python Flask application.
<LocationMatch "^/(?!js|css|media)" >
ProxyPass http://127.0.0.1:5000
ProxyPassReverse http://127.0.0.1:5000
</LocationMatch>
<Location "/">
Require all granted
</Location>
Both of the existing answers rely on Regular Expressions. While they work, it is possible to do this without such complicated constructs. ProxyPass can take "!" as a second parameter, in which case it doesn't proxy the matching URL. For example
ProxyPass /context/static/ !
ProxyPass /context http://127.0.0.1:8080/context
ProxyPassReverse /context http://127.0.0.1:8080/context
or, with multiple exclusions,
ProxyPass /js !
ProxyPass /css !
ProxyPass /media !
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
These exceptions need to come before the more general rule in order to take precedence.
Thanks to freenode user "thumbs" in #httpd.

Detect apache reverse proxy programmatically

I want to proxy a website – let’s call it “APP” - through Apache 2.4 using two different reverse proxies with different host names (virtual hosts). Let’s call those proxies “Alfa” and “Beta”. I want Alfa to be the “public proxy” which will show the normal version of the website. The Beta proxy will limit public access to certain client IPs, but here I want to show – on top of each page of the website – some type of sensitive information. Let’s call those portions of sensitive information “SENS”.
Here’s my current apache config.
Listen 443
NameVirtualHost *:443
SSLStrictSNIVHostCheck off
## Virtual host for the Alfa Proxy
<VirtualHost *:443>
ServerName alfa.mysite.org
RewriteEngine On
<Location /app/>
ProxyPass http://x.x.x.x:8080/app/
ProxyPassReverse http://x.x.x.x:8080/app/
</Location>
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile conf/alfa.crt
SSLCertificateKeyFile conf/alfa.key
</VirtualHost>
## Virtual host for the Beta Proxy
<VirtualHost *:443>
ServerName beta.mysite.org
RewriteEngine On
<Location /app/>
Require ip 192.168.0
ProxyPass http://x.x.x.x:8080/app/
ProxyPassReverse http://x.x.x.x:8080/app/
</Location>
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile conf/beta.crt
SSLCertificateKeyFile conf/beta.key
</VirtualHost>
In order to show SENS only to the users of Beta, I need to programmatically detect which proxy requests come from. Now, I’ve looked into the use of Reverse Proxy Request Headers such as “X-Forwarded-Server”. Let’s say I define some kind of security filter in APP (e.g Spring Filter) that allows SENS to be rendered on page only if X-Forwarded-Server equals “beta.mysite.org”. This should work just fine.
But my question is this: Can I be sure not some kind of tampering with the proxy headers occur that would allow users of the alfa.mysite.org actually view the SENS-portions of my website?
If so, are there any other ways of doing this a “secure manner”.
Having two different versions of APP or deploying APP on two different containers is something I want to avoid here.
Any comments or suggestions are appreciated.
According to the Apache documentation, the x-forwarded-server header can be a comma separated list when multiple proxies are used. So I wouldn't consider it safe from a security point of view.
Under the assumption that your backend server is not directly access, you could try the following.
Set your own HTTP Header which value changes depending on which VirtualHost it passes.
You only have to check then for the existence of the header in the backend.
ServerName alfa.mysite.org
RewriteEngine On
<Location /app/>
#Set - The request header is set, replacing any previous header with this name
RequestHeader set MyCustomHeader "remote"
</Location>
ServerName beta.mysite.org
RewriteEngine On
<Location /app/>
Require ip 192.168.0
#The request header is set, replacing any previous header with this name
RequestHeader set MyCustomHeader "local"
..
</Location>

AJP proxy that maps internal servlet name to a different external name

Using apache2 I want to set up an AJP proxy for a Tomcat server that maps an internal servlet URL to a completely different URL externally. Currently I am using the following configurations:
Apache2 configuration:
<IfModule mod_proxy.c>
ProxyPreserveHost on
ProxyPass /external_name ajp://192.168.1.30:8009/servlet_name
ProxyPassReverse /external_name ajp://192.168.1.30:8009/servlet_name
</IfModule>
Note that external_name and servlet_name are different.
Tomcat 6 configuration:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
This however does not work. Apache seems to forward http requests to Tomcat.
However the URLs and redirects returned by Tomcat are still using the original servlet_name and Apache does not map them to external_name.
Is this possible at all with AJP? If not can it be done using a plain http proxy instead?
Mapping different names between Apache and Tomcat can be quite tricky and depends much on how the web application builds its urls for the response.
Basically your setup is correct, but if your application uses its own servlet_name for redirects and urls ProxyPassReverse won't map them.
If you need this kind of setup have a look at mod_proxy_html (Apache 3rd party module) which will parse and rewrite also the contents, not only the url and response headers as mod_proxy.
( A late answer, but I just ran into this problem myself. )
It appears that ProxyPassReverse using ajp: doesn't work because the headers returned from a redirect don't have an ajp: URL in Location:, they have a http: URL. ProxyPassReverse just causes a rewrite of matching headers, and
that string doesn't match what's being returned.
This should work (provided the Location: field uses that numerical address
and not a host name.)
ProxyPreserveHost on
ProxyPass /external_name ajp://192.168.1.30:8009/servlet_name
ProxyPassReverse /external_name http://192.168.1.30/servlet_name
( You can use 'curl -I' to inspect the redirect headers and debug. )
See this note, or a more involved solution here using mod_proxy_html
for rewriting the URLs in web pages as well.
Additionally to the answer from Steven D. Majewski there is one more problem. If the target application uses the request host name to create a redirect (302 Moved Temporarily), it won't work with multiple host names. One must create multiple configurations for every name, like this:
ProxyPassReverse /external_name http://server.com/servlet_name
ProxyPassReverse /external_name http://server.org/servlet_name
ProxyPassReverse /external_name http://server.co.uk/servlet_name
Actually the ProxyPreserveHost on must solve this issue and replace the HOST header in the incoming requests with the address or IP specified in ProxyPass. Unfortunately it seems to be the ProxyPreserveHost doesn't work with ajp connectors. The tomcat in my configuration still received the host name got from browser instead replacing it with 192.168.1.30. As result the browser based redirects still didn't work for every name.
Following configuration didn't work as well :-(
# NOT WORKING !!!
ProxyPassReverse /external_name http://%{HTTP_HOST}/servlet_name
The workaround was using http instead of ajp.
ProxyPreserveHost on
ProxyPass /external_name ajp://192.168.1.30:8009/servlet_name
ProxyPassReverse /external_name http://192.168.1.30/servlet_name
Did somebody investigate it deeply?
For me, this seemed to cause problems:
ProxyPreserveHost on
ProxyPass /external_name ajp://192.168.1.30:8009/servlet_name
ProxyPassReverse /external_name http://192.168.1.30/servlet_name
While this seemed to work:
ProxyPreserveHost on
ProxyPass /external_name ajp://192.168.1.30:8009/servlet_name
ProxyPassReverse /external_name ajp://192.168.1.30:8009/servlet_name
I don't know why but it just did.