Apache httpd: How can I Deny from all, Allow from subnet, but Deny from IP within that subnet?

I am running CentOS 5.5 with the stock Apache httpd-2.2.3.

I have enabled mod_status at the Location /server-status. I would like to allow access to this single Location in the following way:

  1. Deny from all
  2. Allow from the subnet 192.168.16.0/24
  3. Deny from a the IP 192.168.16.100, which is within the 192.168.16.0/24 subnet.

1 & 2 are easy. However, since I "Allow from 192.168.16.0/24", is it possible to Deny from 192.168.16.100?

I tried to add a Deny statement for 192.168.16.100 but it doesn't work. Here is the relevant config:

<Location /server-status>
    SetHandler server-status
    Order Allow,Deny
    Deny from  all
    Deny from  192.168.16.100 # This does not deny access from 192.168.16.100
    Allow from 192.168.16.0/24
</Location>

Or:

<Location /server-status>
    SetHandler server-status
    Order Allow,Deny
    Deny from  all
    Deny from  192.168.16.100 # This does not deny access from 192.168.16.100
    Allow from 192.168.16.0/24
</Location>

However, this doesn't prevent access to this particular page, as demonstrated in the Access logs:

www.example.org 192.168.16.100 - - [11/Mar/2011:16:01:14 -0800] "GET /server-status HTTP/1.1" 200 9966 "-" "

According to the manual for mod_authz_host:

Allow,Deny

First, all Allow directives are evaluated; at least one must match, or the request is rejected. Next, all Deny directives are evaluated. If any matches, the request is rejected

The IP address matches the Deny directive, so shouldn't the request be rejected?

According to the table on the mod_authz_host page, this IP address should "Match both Allow & Deny", and thus the "Final match controls: Denied" rule should apply.

    Match                       Allow,Deny result                   Deny,Allow result
    Match Allow only            Request allowed                     Request allowed
    Match Deny only             Request denied                      Request denied
    No match                    Default to second directive: Denied Default to second directive: Allowed
    Match both Allow & Deny     Final match controls: Denied        Final match controls: Allowed

Solution 1:

I haven't tested, but I think you are almost there.

<Location /server-status>
    SetHandler server-status
    Order Allow,Deny
    Deny from  192.168.16.100
    Allow from 192.168.16.0/24
</Location>

Deny from all is not needed. In fact it will screw up because everything will match all, and thus denied (and I think Apache is trying to be smart and do something stupid). I have always found Apache's Order, Allow and Deny directives confusing, so always visualize things in a table (taken from the docs):

Match      | Allow,Deny result   | Deny,Allow result
-------------------------------------------------------
Allow only | Allowed             | Allowed
Deny only  | Denied              | Denied
No match   | Default: Denied     | Default: Allowed
Match both | Final match: Denied | Final match: Allowed

With the above settings:

  • Requests from 192.168.16.100 get "Match both" and thus denied.
  • Requests from 192.168.16.12 get "Allow only" and thus allowed.
  • Requests from 123.123.123.123 get "No match" and thus denied.

Solution 2:

I would probably look at also adding IPTables rules for this to deny the single host on port 80, deny from all, and allow the subnet.

You should have no problem setting up a deny rule from a specific address after you have allowed the subnet. Just do it in that order.