Control server access: Prompt for credentials (basic auth) on approved networks, allow access to itself, and deny everyone else

Keyboard buttons spread around an open lock.
I dropped my password.

To get a site feature from development to production, it goes through four servers:

  1. Development
  2. Quality Assurance (QA)
  3. Staging
  4. Production

Except for the development server, which is localhost, there are times people, other than developers, need access to view a particular site on the QA or staging server. In addition, sometimes you need to test your site externally, whether it’d be a mobile device not connected to the network or an external, automated service. This means that the aforementioned servers need to be publicly accessible, but without being accessible to just anyone.

1. Using basic authentication

The first thing that comes to mind is basic authentication. It’s quick and easy to setup, and it solves the authentication problem, albeit not being the securest option. To implement this, you’d put this in your virtual host file:

<Directory />
  AuthType Basic
  AuthName "Restricted Area"
  AuthBasicProvider file
  AuthUserFile /var/www/htpasswd/users
  AuthGroupFile /var/www/htpasswd/groups
  Require group groupname1
</Directory>

2. Setting up permitted users

The user file can be created with something like this:

[root@vps ~]# htpasswd -mbc /var/www/htpasswd/users username1 password1

If you open the users file, you’d see something like this:

username1:$apr1$yP7cevF2$xADK1ANX1XAkqX6uUn.Jj/
username2:$apr1$hfg6JwM7$/mqaQz7aeHryib4EvtxIc.

3. Placing users into groups

You’ll want to assign users to groups, which allows you to approve multiple users without having to reference them individually over and over. You can do this by creating a file with the following contents:

groupname1: username1 username2

Group name followed by space-separated usernames.

4. Dealing with requests to itself

The biggest issue with basic authentication, in my opinion, is that requests to the site originating from the site or server, like a cron job making a cURL request, for example, will now fail unless you also pass a set of credentials in the request, and depending on your application, this may be a huge inconvenience.

[root@vps ~]# /usr/bin/curl "http://qa.domain.com"
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Authorization Required</title>
</head><body>
<h1>Authorization Required</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
</body></html>

So to get this to work, you’d add credentials to the request:

[root@vps ~]# /usr/bin/curl "http://qa.domain.com" --user username1:password1

5. Whitelisting the host

To get around having to provide credentials, you could extend your authentication to either provide credentials or be whitelisted. That would look something like this:

<Directory />
  AuthType Basic
  AuthName "Restricted Area"
  AuthBasicProvider file
  AuthUserFile /var/www/htpasswd/users
  AuthGroupFile /var/www/htpasswd/groups
  Require group groupname1
  Order Allow,Deny
  Allow from 111.111.111.111
  Satisfy any
</Directory>
  • Order: Whether to allow or deny access by default (using deny here).
  • Allow: Which host can access the site.
  • Satisfy: Whether to require credentials and coming from a whitelisted host, or whether one of the two is sufficient.

6. Moving authentication configuration into an include file

I like things easy and global, which means I’ll most likely put this in an include file so that I can force authentication for an array of sites using different virtual host files, but then only have to update the list of groups and IP address in one file. I’d then include a default authentication configuration in the virtual host files that need it:

Include vhosts.d/includes/basic_authentication_master

7. Permitting requests to itself, but requiring valid host and credentials from users

Now, since basic authentication is so simple and insecure, I thought about adding an additional layer. What I was looking for was something like: if the request comes from the server, allow it, otherwise ensure request comes from a whitelisted host, and then still ask for credentials.

Unfortunately, I couldn’t think of a way to implement this using just basic authentication, because Apache’s Satisfy directive can either be set to any or all, with no condition attached.

To get around this, I placed a rewrite rule above the basic authentication. The rule says to redirect to the main site if the host is not whitelisted:

RewriteEngine on
RewriteCond %{REMOTE_HOST} !^111\.111\.111\.111
RewriteCond %{REMOTE_HOST} !^222\.222\.222
RewriteRule / http://domain.com [R=302,L]

<Directory />
  AuthType Basic
  AuthName "Restricted Area"
  AuthBasicProvider file
  AuthUserFile /var/www/htpasswd/users
  AuthGroupFile /var/www/htpasswd/groups
  Require group groupname1
  Order Allow,Deny
  Allow from 111.111.111.111
  Satisfy any
</Directory>

Once the request makes it passed the rewrite rule, the previously setup authentication scheme will follow suit.

As an added benefit, if anyone attempts to visit the site on the QA or staging server, it will redirect them instead of prompting for credentials, which makes the whole thing even less obvious.

Conclusion

It meets all of my goals, which is making it easy to implement and manage, and it provides reasonable protection from someone accidentally stumbling on the site or someone attempting to view the site when they shouldn’t.

As always, use your judgement to determine whether this solution is right for you, and if you have any questions or suggestions, leave them in the comments below.

Featured image by FLY:D.