Can htaccess read a server environment variable set in Apache? - apache

When you set a server environment variable in your Apache settings, it's possible to read that variable from PHP using built in functions.
But can you read the variable from an htaccess file?
What I'm trying to accomplish in my htaccess is something along the lines of:
<If var=="stuff">
do things here
</if>
<if var=="different stuff">
do different things here
</if>

Yes it is.
You will probably want to use mod_setenvif functionality, so that module will need to be turned on.
Then you simply do something like:
SetEnvIf Request_URI "\.gif$" image_type=gif
Which would set the environmental variable image_type with a value of gif if the requested file name ends with .gif.
You could then access this (like in RewriteCond) like this:
RewriteCond %{ENV:image_type} gif
RewriteRule ^(.*) $.gif
You can read the full documentation here: http://httpd.apache.org/docs/2.2/env.html

As I had a lot of problems setting up a similar thing to recognise my home localhost as distinct to my work set up, here's what I ended up doing.
First and foremost, The <IF ..> directive does not recognise SetEnv. From http://httpd.apache.org/docs/current/env.html : "The SetEnv directive runs late during request processing meaning that directives such as SetEnvIf and RewriteCond will not see the variables set with it."
So, try to use SetEnvIf if you can. I was lazy and just put this in my httpd.conf file. It checks that it's running on localhost - which is obviously is - then sets the variable:
SetEnvIf Server_Addr ^127\.0\.0\.1$ home_variable
Then, elsewhere, in my htaccess file I had this:
# IfVersion needs mod_version - you could do an ifmodule before all this i guess
# we use ifversion because If was introduced in apache 2.4
<IfVersion >= 2.4>
# check if the home variable exists/isn't: empty, "0", "off", "false", or "no"
<If "-T reqenv('home_variable')">
... do home-y stuff
</If>
<Else>
... do work-y stuff
</Else>
</IfVersion>
# fallback
<IfVersion < 2.4>
.. fallback?
</IfVersion>
Alternatively, you could just have one variable for each environment and give it a specific value:
SetEnvIf Server_Addr ^127\.0\.0\.1$ check_location=home
Then the rest would be the same, except that first<If ..> statement:
<If "%{ENV:check_location} == 'home'">....</If>
I've tested both of those between a home environment with 2.4 and a work environment with 2.2, both of which had mod_version enabled (hence not bothering with the ifmod).
(I also haven't tested for more than two environments but Apache 2.4 does give an <ElseIf> so that's also an option).
Doop di do.
(More about the -T operator et al: http://httpd.apache.org/docs/current/expr.html)

Apache 2.4 introduced <If>, <Else>,<ElseIf> blocks in conjunction with a new expression syntax for more powerful programmatic control in .htaccess files, etc.
This is untested but ostensibly works:
<If "%{MY_VAR} == 'my result'">
…
</If>
Source: https://blogs.apache.org/httpd/entry/new_in_httpd_2_4

I gave description and .htaccess and PHP examples here: Set an environment variable in .htaccess and retrieve it in PHP
For just some examples in .htaccess, taken from my modifying the Perishable Press 5G Blacklist/Firewall http://perishablepress.com/5g-blacklist-2013/ to use environment variable reporting:
SetEnv myServerName %{SERVER_NAME}
RewriteCond %{QUERY_STRING} (base64_encode|localhost|mosconfig|open_basedir) [NC,OR]
RewriteCond %{QUERY_STRING} (boot\.ini|echo.*kae|etc/passwd) [NC,OR]
RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC]
RewriteRule .* - [E=badQueryString:%0--%1--%2,F,L]
SetEnvIfNoCase User-Agent ^$ noUserAgent
SetEnvIfNoCase User-Agent (binlar|casper|cmsworldmap|comodo|diavol|dotbot|feedfinder|flicky|ia_archiver|jakarta|kmccrew|nutch|planetwork|purebot|pycurl|skygrid|sucker|turnit|vikspider|zmeu) badUserAgent=$1
<limit GET POST PUT>
Order Allow,Deny
Allow from all
Deny from env=badUserAgent
</limit>
Notice the use of paramters. %0 gives the full string, %1 gives the match from the 1st parenthesized statement, %2 the 2nd. The hyphens are literal display characters, to visually separate (don't think is any way to put spaces in there).

Related

Apache mod_rewrite [E] Redirect prefix with RewriteCond

In an Apache server, I need to disable keepalive if a specific URL is matched.
To do so, I used mod_rewrite to set it then let the script run its course.
RewriteCond %{REQUEST_URI} ^/specific_url
RewriteRule ^ - [E=nokeepalive]
.......
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
Now my problem is that nokeepalive end up being prefixed/renamed to with REDIRECT_ if it matches
From this question : When setting environment variables in Apache RewriteRule directives, what causes the variable name to be prefixed with "REDIRECT_"?
I assumed it was due to the RewriteRule
But it is not prefixed if I remove the RewriteCond (which would disable keepAlive globaly with any URL)
Is there a way to prevent this prefixing with RewriteCond ?
I cannot use SetEnvIf since I use mod_rewrite so I must use RewriteRule.
SetEnv/SetEnviF also won't work as they can not read from variable that were not assigned using SetEnv/SetEnvif.
I tried with SetEnvIf to define only if REDIRECT_nokeepalive exist ( since nokeepalive just have to be defined, regardless of the value ) - but to no avail.
I cannot use SetEnvIf since I use mod_rewrite so I must use RewriteRule.
You could use mod_setenvif with <If>.
<If "%{REQUEST_URI} =~ /regextomatchurl/">
# or use <If "%{REQUEST_URI} == 'urlstring'">
SetEnv nokeepalive 1
</If>
You can remove that rewrite rule.
OR:
You can just add this, it might work, not tested:
SetEnv nokeepalive ${REDIRECT_nokeepalive}
Edit:
Remember to add PassEnv REDIRECT_nokeepalive before SetEnv because SetEnv cannot read env variables from mod_rewrite.
Just to point out:
You have two spaces here, it could cause future problems in other RewriteRule(s):
RewriteRule ^ - [E=nokeepalive]
^^

Apache ACL based on environment variable value

I have a vendor Apache module (PingFederate) that sets environment variables based on a token it receives. I would like to control access to directories based on the value of an environment variable.
For example the module sets variables like this:
[PF_AUTH_SUBJECT] => aaron
[PF_AUTH_GROUPS] => CN=Application.E18.Users,OU=Internal,DC=local,CN=Application.E17.Users,OU=Internal,DC=local
I want to secure a directory so that only users in group CN=Application.E18... can access it. My location direction conf looks like this:
<Location /example_app>
SetEnvIf %{PF_AUTH_GROUPS} ^.*CN=Application.E18.Users.*$ ALLOWED
AuthName "ACL PingFederate restricted"
AuthType PFApacheAgent
Order Deny,Allow
Deny from all
Allow from all
</Location>
This doesn't seem to work. I've tried:
SetEnvIf %{PF_AUTH_GROUPS} ^.*CN=Application.E18.Users.*$ ALLOWED
SetEnvIf %{PF_AUTH_GROUPS} ^.*Application.*$ ALLOWED
SetEnvIf %{PF_AUTH_GROUPS} ^.*A.*$ ALLOWED
The only thing that works is:
SetEnvIf %{PF_AUTH_GROUPS} ^.*$ ALLOWED
That obviously won't work.
https://issues.apache.org/bugzilla/show_bug.cgi?id=25725 somewhat intimates that SetEnvIf won't test environment variables but the docs at http://httpd.apache.org/docs/2.2/mod/mod_setenvif.html mentions "an environment variable in the list of those associated with the request", which this should be.
I've also tried mod_rewrite using this:
RewriteEngine On
<Location /example_app>
RewriteCond %{PF_AUTH_GROUPS} ^.*Application.E18.Users.*$
RewriteRule - [E=ALLOWED:1]
AuthName "ACL PingFederate restricted"
AuthType PFApacheAgent
Order Deny,Allow
Deny from all
Allow from all
</Location>
In all of these instances the ALLOWED environment variable is not set.
You'll need to make mod_rewrite execute 2 times to be able to leverage the headers produced by mod_pf, since the latter executes after the former:
RewriteEngine On
RewriteCond %{ENV:REDIRECT_PASS} !1
RewriteRule .* $1 [L,E=PASS:1]
RewriteCond %{HTTP:PF_AUTH_GROUPS} !^.*ECN.*$
RewriteRule .* $1 [L,R=401]
This is also documented here: https://ping.force.com/Support/PingIdentityArticle?id=kA340000000Gs7bCAC

Apache 2.2 - "Allow from" only if QUERY_STRING matches

I'm trying to limit one of my servers to only one specific request, but after 2 hours of trying couldn't come up with a working solution. Basically I'm looking for something similar to the <If ...> directive, but I only have Apache 2.2 (this is a fact, and I cannot update to 2.4).
I have 4 Servers: frontend[1-3] and backend1. frontend[1-2] are allowed to do anything on backend1, but frontend3 should only be allowed to make 1 specific request. In Apache 2.4 it would look something like this:
<Location />
Order allow,deny
Allow from frontend1
Allow from frontend2
<If "%{QUERY_STRING} =~ /foobar/myfunc/[^/]*$">
Allow from frontend3
</If>
</Location>
How can I do the same in Apache 2.2? I tried using SetEnvIf, but since it hasn't got logical AND it was a mess and didn't work (I have to match the host and the URL, since frontend3 is only allowed to do "myfunc").
Found a way with the help of mod_rewrite:
RewriteEngine On
# Matching the host "frontend3"
RewriteCond %{REMOTE_ADDR} ^1\.2\.3\.4$
# Matching the request "/foobar/myfunc?do=something"
RewriteCond %{QUERY_STRING} ^do=something$
# Setting an environment variable if host & request match
RewriteRule ^/foobar/myfunc$ - [E=allowthis:1,L]
<Location />
Order allow,deny
Allow from frontend1
Allow from frontend2
Allow from env=allowthis
</Location>

Apache 2.2 redirect to SSL *then* do auth (with solution < but is it crap?)

Seems to be the place for apache so here goes :)
Age old problem: how so I redirect HTTP->HTTPS, then and only if HTTPS, do an auth?
Oh - and I'd like most of it in a single snippet that can be Include-ed in multiple <directory> or <location> blocks, so no virtual host level random path based rewrites...
Well, here's what I have that does seem to work:
In the top of a VirtualHost block
# Set ssl_off environment variable
RewriteEngine on
RewriteCond %{HTTPS} =on
RewriteRule ^ - [E=ssl]
In the location or directory block
RewriteEngine on
# Case 1 redirect port 80 SSL
RewriteCond %{HTTPS} !=on
RewriteCond %{SERVER_PORT} =80
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R=301]
AuthType Basic
AuthBasicProvider external
AuthExternal auth_pam
AuthName "My Underpants"
AuthzUnixgroup on
Order Deny,Allow
Deny from all
Allow from env=!ssl
Satisfy any
Require group nice-users
Pluses
All of that bar the Require's can be abstracted out to a snippet file to Include in one line on each location
It fixes forcing SSL and authentication together for each location, so less chance of mistakes
Minuses
Bloody hell, it is hardly intuitive! Might be fragile for all I know...
Is there a better way (not that I've found...)?
Comments would be very welcome on whether that has any serious flaws :)
Aside
Life would be so much easier if Apache had a sensible config syntax with a generic
<If expression> </If>
block that could be used anywhere. It has certain special case blocks such as IfModule, and then you have special case conditionals like RewriteCond (which is very hard to grok if you're not used to it).
Cheers,
Tim
If you're wanting to force the entire site to https, you can use the VirtualHost directives, and then it's quite simple:
<VirtualHost *:80>
ServerName example.com
RedirectMatch (.*) https://example.com$1
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
...
...
...
</VirtualHost>
Tim Watts' solution seems to work best for me but needed a bit of tweaking. Also my situation is slightly different in that I wish to allow certain IP addresses without HTTP auth but this just adds an extra line.
mod_rewrite won't inherit config from the VirtualHost by default.
See: http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriteoptions
I was going to make use of "RewriteOptions inherit" but it seems that this applies the parent rules AFTER the child ones. In any case, I thought of a different solution.
Within my SSL <VirtualHost> I have the line:
SetEnvIf Request_URI "/" using_ssl=yes
This sets the using_ssl environment variable if the request URI contains a forward slash (i.e. all the time.) It's a bit of hack as I'd prefer to use the unconditional SetEnv but apparently:
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.
(source: http://httpd.apache.org/docs/2.2/mod/mod_env.html#setenv)
My config within my container looks like this:
# Require a basic HTTP auth user
AuthName "realm-name-goes-here"
AuthType Basic
AuthUserFile /var/www/etc/htpasswd
Require valid-user
# OR allow from non-SSL (which will be redirected due to mod_rewrite below!)
Order Allow,Deny
Allow from env=!using_ssl
# OR allow from a trusted IP range
# NB: This allows certain IPs without a username & password
Allow from 192.168.0.0/16
Satisfy Any
# Force a redirect to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=permanent]
You probably want to try with just 'R' instead of 'R=permanent' first for testing purposes.
Hope this is useful for others :)
I've tested your solution and it didn't work ...
After a loooong time searching the solution, googling too much and found always the same things which didn't work, I finally did this : I use SSLRequireSSL and an ErrorDocument 403 configured with a static page containing a JavaScript code (thanks to this blog page).
in /etc/apache2/conf.d.opt/redirect_if_not_https.conf :
SSLRequireSSL
ErrorDocument 403 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\
<html><head>\
<title>403 Forbidden</title>\
<script language=\"JavaScript\">\
window.location='https://'+window.location.hostname+window.location.pathname;\
</script>\
</head><body>\
<h1>Forbidden</h1>\
<p>You don't have permission to access that resource using simple HTTP. Please use HTTPS instead.</p>\
</body></html>"
(note that I created /etc/apache2/conf.d.opt/)
And in an app conf, include that file (for example in /etc/apache2/conf.d/trac.conf) :
<LocationMatch "/trac">
# All the classical configurations here
# ...
Include conf.d.opt/redirect_if_not_https.conf
</LocationMatch>

How Can I Have A Conditional .htaccess Block?

This is an Apache question you've probably come across before. I want to have one source package that I can deploy to my workstation, my staging server, and my production server, but for it to load different .htaccess settings based on what the URL was.
Note that I was using a kludge with an IfModule call, but that won't work with our new production server because it shares all the same modules as my staging server.
Note I need to bundle SetEnv with these rewrites. Currently if you use RewriteCond, it only ties to the following RewriteRule, but not the SetEnv underneath.
Instead of using SetEnv, use the environment variable setting capabilities of RewriteRule itself:
RewriteCond %{HTTP_HOST} =foo.com
RewriteRule ^ - [E=VARNAME:foo]
RewriteCond %{HTTP_HOST} =bar.com
RewriteRule ^ - [E=VARNAME:bar]
Although I prefer doing this sort of thing by passing flags to the httpd process at startup and looking for them using IfDefine blocks.
<IfDefine FOO>
SetEnv VARNAME foo
</IfDefine>
<IfDefine BAR>
SetEnv VARNAME bar
</IfDefine>
On Ubuntu Linux, the IfDefine's variable is set in
/etc/apache2/envvars
and is called APACHE_ARGUMENTS. So, at the bottom of that file I had to add:
export APACHE_ARGUMENTS="-D dev"
...and then bounce the server with:
/etc/init.d/apache2 stop
/etc/init.d/apache2 start
On other systems:
However, there's a Debian article on this topic that discusses this here. In that example, the file to edit is /etc/default/apache2 and the variable is called APACHE_DEFINES.
Likewise, on some systems it is a variable named OPTIONS that is set in /etc/sysconfig/httpd.
So, what you really need to do is look for the start section in your apache2ctl file. So, begin by doing a whereis apache2ctl to find where that script is, cat it out and find the start section with the apache2 directive in it, and see if the variable it passes is OPTIONS, APACHE_ARGUMENTS, APACHE_DEFINES, or something else. Then, see which file you need to edit by experimentation with either /etc/sysconfig/httpd, /etc/default/apache2, or /etc/apache2/envvars.
I tried the IfDefine method and it didn't work, even though the defined variable i'd passed into the Apache startup was definitely showing up in phpinfo() on the APACHE_ARGUMENTS line.
I tried another method and it worked perfectly. In your .htaccess file you need something like:
# Is the domain local (i wanted to check two names)?
<If "%{SERVER_NAME} != 'localhost' && %{SERVER_NAME} != 'myproject.localhost'">
# I wanted to password protect the production server only
AuthType Basic
AuthName "Restricted area"
AuthUserFile /app/.htpasswd
require valid-user
</If>
I'm using Apache 2.4. Might not work for earlier versions.
Here is a simple example that should be enough for you to change it to meet your requirements:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP_HOST} !^localhost
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule (.*) http://www.%{HTTP_HOST}/$1 [R=301,L]
</IfModule>