Issues with Require IP on Apache 2.4 and Ubuntu 20.04 server

I have setup a subdomain (staging.callkneehill.ca) on my server and have created a .htaccess file with the following parameters:

<RequireAny>
  Require ip 70.65.194.109
</RequireAny>
ErrorDocument 403 "Restricted Access"

When I try accessing the subdomain, the 403 error is being displayed, even though my IP address matches the required value.

If I comment out all of the code in the .htaccess, the index.html loads.

Here is the virtual host configuration for the subdomain:

<VirtualHost *:80>
  ServerName staging.callkneehill.ca
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/staging/
  ErrorLog ${APACHE_LOG_DIR}/staging-error.log
  CustomLog ${APACHE_LOG_DIR}/staging-access.log combined
  RewriteEngine on
  RewriteCond %{SERVER_NAME} =staging.callkneehill.ca
  RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<Directory /var/www/staging/>
  Options Indexes FollowSymLinks
  AllowOverride All
  Require all granted
</Directory>
<FilesMatch \.php$>
  # SetHandler "proxy:unix:/run/php/php7.4-callkneehill-fpm.sock|fcgi://localhost"
</FilesMatch>

The .htaccess file on the root domain, which uses WordPress functions correctly. I have compared the virtual host configuration files and both look to have the same general parameters.

I have also tested using IP blocking for Apache 2.2, but the same issue occurs.

I have reloaded and restarted Apache each time I have updated the configuration file.

Can anyone highlight what I am missing?

Update

Staging access log output

172.68.189.103 - - [18/May/2021:13:57:15 +0000] "GET / HTTP/1.1" 403     5454 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0"
172.68.143.156 - - [18/May/2021:13:57:15 +0000] "GET /favicon.ico HTTP/1.1" 403 5454 "https://staging.callkneehill.ca/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0"
172.68.189.229 - - [18/May/2021:14:02:11 +0000] "GET / HTTP/1.1" 301 598 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15"
172.68.132.65 - - [18/May/2021:14:02:11 +0000] "GET / HTTP/1.1" 403 5454 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15"
172.68.143.156 - - [18/May/2021:14:02:11 +0000] "GET /favicon.ico HTTP/1.1" 403 5454 "https://staging.callkneehill.ca/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15"
162.158.255.248 - - [18/May/2021:14:03:07 +0000] "GET / HTTP/1.1" 403 5454 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1"

I looks like my IP address is variable. Would this from using Cloudflare's 1.1.1.1 as my DNS on my MacBook Pro?

The same issue affects my iPhone on the same local network with a VPN enabled/disabled.


Solution 1:

Would this from using Cloudflare's 1.1.1.1 as my DNS on my MacBook Pro

No. (But it would if your domain is pointing to Cloudflare's DNS.)

The IP addresses listed in your "update" are all Cloudflare IPs. This would suggest you are using Cloudflare CDN (ie. the NAMESERVERS on the domain point to Cloudflare) - which acts as a reverse proxy in front of your origin server. In other words, all requests to your origin server come from Cloudflare.

The actual client IP address is passed on to your origin server in various HTTP request headers (depending on how Cloudflare is configured). The defacto-standard header is X-Forwarded-For - but note that this can contain multiple IPs (comma separated) depending on whether the request has passed through multiple proxies. And Cloudflare appends the client IP address, not prefixes it (which goes against the convention). Other Cloudflare-specific (preferred) headers are CF-Connecting-IP and True-Client-IP which contain the single client IP address.

Reference:

  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
  • https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
  • https://support.cloudflare.com/hc/en-us/articles/206776727-Understanding-the-True-Client-IP-Header
<RequireAny>
  Require ip 70.65.194.109
</RequireAny>
ErrorDocument 403 "Restricted Access"

So, instead of the above, you would need to do something like the following instead in your .htaccess file:

ErrorDocument 403 "Restricted Access"
SetEnvIf CF-Connecting-IP "^70\.65\.194\.109$" ALLOWED_IP=1
Require env ALLOWED_IP

<RequireAny> is the default when omitted, so it is not required here.


Alternative using mod_remoteip

However, the above does not resolve the "issue" of Cloudflare IPs being reported in your logs instead of the actual client IP addresses.

Alternatively, you can enable Apache's mod_remoteip to inform Apache how to retrieve the client IP address (for the sake of Require ip and logging).

For example, once mod_remoteip is enabled then in your server config:

# Header that contains the client IP address
RemoteIPHeader CF-Connecting-IP

As added security, you can specify a list of allowed Cloudflare IPs in an external file:

# List of valid user-agent IPs
RemoteIPInternalProxyList /var/www/cloudflare-ips.lst

You then don't need to modify your existing Require ip 70.65.194.109 directive. But you do still need to modify your custom LogFormat to get the client IP logged. You would need to change %h (remote host/ip) at the start of the format string to %a (client IP as set by mod_remoteip).

For example, if you are using the combined log format (which it looks like you are) then:

LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

Reference:

  • https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html
  • https://httpd.apache.org/docs/current/mod/mod_log_config.html

Aside:

RewriteEngine on
RewriteCond %{SERVER_NAME} =staging.callkneehill.ca
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

The vHost config you've posted (for port 80) simply redirects to HTTPS (port 443) - the config for which you've not included.

But you don't need to check the requested SERVER_NAME here, since it can't be anything other than the staging hostname as defined by the ServerName directive (assuming you have a default vHost defined earlier). In fact, you don't need mod_rewrite at all, a simple mod_alias Redirect directive would be preferable instead:

Redirect 301 / https://staging.callkneehill.ca/