Apache VirtualHost conditional based on an environment variable - apache

I'm trying to include a config file inside an Apache 2.4 <VirtualHost> based on the presence of an environment variable.
Inside the VirtualHost declaration, I set the VIEWMODE environment variable as such:
Define virtualhost_config "${virtualhost_path}/conf/virtualhost.conf"
<VirtualHost *:80>
SetEnv VIEWMODE demo
Include "${virtualhost_config}"
</VirtualHost>
Inside the included config file, I now have this conditional inside the <Directory> block:
<If "env('VIEWMODE') == 'demo'">
RewriteRule (.*) http://www.apple.com/ [L,R=302]
</If>
However, I can't seem to get this to work. The conditional RewriteRule is ignored.
What am I missing?

See the documentation for SetEnv:
The internal environment variables set by this directive are set after most early request processing directives are run, such as access control and URI-to-filename mapping. If the environment variable you're setting is meant as input into this early phase of processing such as the RewriteRule directive, you should instead set the environment variable with SetEnvIf.
And the note about environment variables within the functions section of the Apache Expresions documentation is also of interest:
Environment variable ordering
When environment variables are looked up within an <If> condition, it's important to consider how extremely early in request processing that this resolution occurs. As a guideline, any directive defined outside of virtual host context (directory, location, htaccess) is not likely to have yet had a chance to execute. SetEnvIf in virtual host scope is one directive that runs prior to this resolution
When reqenv is used outside of <If>, the resolution will generally occur later, but the exact timing depends on the directive the expression has been used within.
So you need to use SetEnvIf because SetEnv is not processed soon enough, and that fixed it for me when I tested here. Something like:
Define virtualhost_config "${virtualhost_path}/conf/virtualhost.conf"
<VirtualHost *:80>
SetEnvIf Request_URI ^ VIEWMODE=demo
Include "${virtualhost_config}"
</VirtualHost>

I solved this by setting an Apache variable, which then fed both the SetEnv directive as well as the <If> block. Unfortunately, the <If> block itself seemed to cause issues with the processing order of directives inside it (e.g. ServerAlias not allowed here), but <IfDefine> did not have this problem (using <IfDefine> only worked for me because VIEWMODE was binary). The final solution looked something like this:
Define environment production
Define viewmode demo
<VirtualHost *:80>
SetEnv ENVIRONMENT ${environment}
<IfDefine viewmode>
SetEnv VIEWMODE ${viewmode}
Include "${virtualhost_path}/conf/demo-configuration.conf"
</IfDefine>
</VirtualHost>
UnDefine environment
UnDefine viewmode
An important caveat is that Apache variables are global, so if the same variables might be used in subsequent Virtual Hosts, make sure to UnDefine them at the end of each config.

Related

Set Apache directive based on external environment variable with default

I would like to do something like this with my Apache configuration:
<VirtualHost *:443>
DocumentRoot /var/www
TimeOut ${APACHE_TIME_OUT}
...
</VirtualHost>
where APACHE_TIME_OUT is an environment variable set in the OS before Apache starts. However, I would like Apache to use a suitable default if the environment variable APACHE_TIME_OUT is not defined.
I tried this without success:
<If "-n env('APACHE_TIME_OUT')" >
TimeOut ${APACHE_TIME_OUT}
</If>
The above generated the complaint "Config variable ${APACHE_TIME_OUT} is not defined".
This could be solved by always being sure that APACHE_TIME_OUT is defined before starting Apache, but I want to handle the case where APACHE_TIME_OUT might not be defined.

Can you use Apache's <If> to conditionally set the Document Root (without using mod_rewrite)?

I need to set Apache's DocumentRoot directive inside a <VirtualHost> according to whether a particular remote IP address is calling it. I could use mod_rewrite for this but once I've "made the switch" I subsequently have a very complicated .htaccess file in the two destination folders themselves. I figure I need to do this inside httpd.conf. Is there a way to conditionally switch the entire Virtual Host directory based on my IP?
I am close with the following code, but it doesn't change based on my IP.
<VirtualHost _default_:443>
<If "-R '1.2.3.4'">
Define mydocumentroot "/var/www/just_for_me"
</If>
<Else>
Define mydocumentroot "/var/www/everyone_else"
</Else>
DocumentRoot ${mydocumentroot}
</VirtualHost>
The DocumentRoot variable correctly gets set to the mydocumentroot variable, but this always evaluates to the <Else> clause, no matter what I try.
Thanks!
After endless experiments and research it seems that doing any kind of switching in httpd.conf or .htaccess is slow and unnecessarily taxing for the server. I ended up working it out using a combination of mod_rewrite and soft links.

Apache ProxyPass error

I have to redirect all apache requests on 80 to tomcat on 8080, except one path.
So, if a receive http://example.com/anything --> tomcat:8080.
But, if the url is that: http://example.com/site --> apache should serve and no redirect is needed.
Currently, there is a folder named site inside /var/www/html/.
This is my current configuration file:
site.conf (this file contains only the following and is inside the conf.d folder)
<LocationMatch "/*">
Allow from all
ProxyPass /site !
ProxyPass http://127.0.0.1:8080
ProxyPassReverse http://127.0.0.1:8080
</LocationMatch>
I think this is a simple thing to accomplish with apache, but I have tried everything that I could find and I am still getting the error:
ProxyPass|ProxyPassMatch can not have a path when defined in a location.
The thing is that the root website is running on tomcat, but the other runs on apache (the one that I called site in this question).
If anyone can help, I appreciate.
Thanks!
Update 1 - 09/06/2017
I get it to work if I remove the LocationMatch and put the ProxyPass
direct in the .conf file:
ProxyPass /site !
ProxyPassReverse /site !
ProxyPass / http://127.0.0.1:8080
ProxyPassReverse / http://127.0.0.1:8080
But, I would like to know, why is that? What is the impact of putting this directives outside the LocationMatch tag? And, most important, why I cannot accomplish the same result using the LocationMatch?
I think the error is pretty clear:
ProxyPass|ProxyPassMatch can not have a path when defined in a location.
According to the documentation, inside a context block like Location or LocationBlock the ProxyPass directive does not accept a path:
When used inside a <Location> section, the first argument is omitted and the local directory is obtained from the <Location>. The same will occur inside a <LocationMatch> section; however, ProxyPass does not interpret the regexp as such, so it is necessary to use ProxyPassMatch in this situation instead.
You're getting the error because you were trying to use a path:
ProxyPass /site !
You could try to resolve this in theory by using multiple <Location> sections, like this:
<Location />
ProxyPass http://backend/
</Location>
<Location /site>
ProxyPass !
</Location>
The ordering of these sections is important.
Your solution of using ProxyPass directives outside of a LocationMatch block is probably the simplest solution.
As a side note, your LocationMatch directive is incorrect. The argument to LocationMatch is a regular expression, and /* would only match URLs consisting only of / characters. That is, it would match / or // or /////////, etc. I think you really meant /.*. The * in a regular expression means "the previous character, zero or more times".

Apache: <Directory> directive with no path specified

I have a local server environment running on my OS X machine using MAMP, and I am currently setting up a few virtual hosts. I found that, in order for one of my virtual hosts to recognize my site's .htaccess file, I needed to add a <Directory> directive within the <VirtualHost> directive.
Here is an example of what I am using to configure one of my virtual hosts:
<VirtualHost *>
DocumentRoot "/path/to/mysite"
ServerName mysite.local
<Directory "/path/to/mysite">
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>
</VirtualHost>
Now, I would like to avoid redundancy by removing the path from that <Directory> directive. I tried doing so and it seems to work, although I am not familiar with Apache enough to know the potential consequences of doing this. Furthermore, I could not find an explanation of this case in the Apache Documentation.
This is what I would like to do:
<VirtualHost *>
DocumentRoot "/path/to/mysite"
ServerName mysite.local
<Directory>
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>
</VirtualHost>
Therefore, my question is this: what happens when I omit the path from the <Directory> directive (in this case)?
Directory is a directive targeting a file system directory, and should always have a path (waildcards allowed).
Using some instructions targetd to no directory may be discarded silently, or maybe with an error (you could test it), but is would certainly be useless.
Why would you write some configuration instructions targeted to no directory at all?
But there's maybe more things in your question.
Ususally a Virtualhost should contains at least one Directory instruction, the DocumentRoot directory, and usually we also add one targeting the root of the filesystem, this way:
<Directory />
(instructions to deny access and avoid .htaccess reads)
</Directory>
<Directory /my/document/root/>
(instructions to allow access and still avoid .htaccess reads or to allow them if you are lazy and feel over confident that .htaccess are useful in any way and you do not need speed)
</Directory>
And you could add more, especially to avoid usage of .htaccess files as such file stored in /path/to/somewhere is the same thing as a <Directory /path/to/somewhere> section, but slower.
Now you do not like to repeat yourself in your VH configuration, so you coud maybe use .htaccess files or global level instructions for things that are usually set there. And you could also use another magical keyword, which is Location.
You can use a lot of Apache instruction in a <Location /foo> directive. Locations works on url, not on filesystem paths.
So <Location /> is your documentRoot, and <Location /foo> is maybe the subdirectory 'foo' under you DocumentRoot. This could help you write things in your VH without reusing the documentroot prefix everywhere. But be careful, urls are not file-system paths. Any protection applied on the url level may be weaker or stronger compared to a protection applied on a directory level. It depends on your alias, the way you use urls, etc.
Update:
If you use the new Apache 2.4 version you can now use mod_macro or even easier, built-in variables support:
Define docroot "/path/to/mysite"
DocumentRoot ${docroot}
<Directory ${docroot}>
(...)

Single htaccess file with distinction between production and development

I am currently using two .htaccess files for my website: one is the development version, and the other one is the production version. The difference is in absolute and some relative paths that are used for RedirectMatch, RewriteRule, and ErrorDocument directives, and that differ between development and production. I am using a git hook to remove the dev version and rename the production version, but this still means I have to maintain two versions of basically the same file. Also, I am encountering similar problems with other sites I manage, so this all means more work. I rather have one single file and some sort of condition in the .htaccess file that allows me to say if production -> use these, else -> use these. Can I do such thing in an .htaccess file?
I have found this:
.htaccess between developemt, staging, and production
But this is for rewrite rules and would not fix the different ErrorDocument paths.
If your servers are running Apache 2.3 or higher, you can use an <if expr> directive like this:
<if "%{HTTP_HOST} == 'dev.example.com'">
# dev directives
</if>
<if "%{HTTP_HOST} == 'www.example.com'">
# prod directives
</if>
Earlier versions of Apache will throw a 500 Server Error if you use <if>, be sure to check the Apache version before trying this.
I ran into an issue where I needed a similar solution but could not use <if expr> directive because I was running Apache 2.2
An alternative is to put password protect instruction in your apache config (vhost-conf in my case) file within the tag like below. Note that you still need a .hpasswd file
ServerAdmin xxx#lami.me
DocumentRoot "/doc/root/html"
ServerName dev.lami.me
ServerAlias dev.lami.me localhost
ErrorLog "/doc/root/logs/error_log"
CustomLog "/doc/root/logs/access_log" common
<Directory "/doc/root/html">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
AuthName "Restricted Area"
AuthType Basic
AuthUserFile /doc/root/.htpasswd
AuthGroupFile /dev/null
require valid-user
</Directory>
I rather have one single file and some sort of condition in the .htaccess file that allows me to say if production -> use these, else -> use these. Can I do such thing in an .htaccess file?
No. Sorry.
However you may construct your .htaccess to work on both with a bit of care. And I have done this for my test and prod systems.
IMO, the easiest way to do this is to make your development site mirror your production site in some key aspects and this is what I do. For example if your prod domain is mydomain.com and your test environment is on localhost, the add an alias of mydomain.home for localhost in your etc/hosts file. You can now add a vhost for mydomain.home and mirror the document roots and environment setting of prod in this. If you need to refer to the HTTP_HOST, you can now use mydomain.(com|home) and use %1 to distinguish which.
This assumes that you are running your dev env on a LAMP system. If not it's pretty easy to set up a VM to do this (as I describe here)
You can use RewriteRules instead of RedirectMatch directives and with Rewriterules you can use the [C],[OR],[E] and [S] flags to implement branch-style ladder-logic which can effectively mirror if/then/else constructs.
The main gotcha is DocumentRoot setting if you production is on a shared hosting environment and with impacts local ErrorDocument paths, but without knowing you environments, it is difficult to give more specifics