Table of Contents:
Lets state it, your site or service can easily become a target for some Internet ``terrorists'', for something that you said, because of success your site has, or for no obvious reason. Your site can be broken in, the whole data can be deleted, some important information can be stolen, or a much easier the site can be turned into a useless with a _simple_ Services Deny Attack. What can you do about it? Protect yourself! Whatever you do, your site will be still vulnerable as long as you are connected to the network. Cut the connections down, turn off your machine and put it into a safe - now it is more protected, but very useless.
Let's first get acquainted with security related terminology:
When you want to make sure that a user is the one he claims to be, you generally ask her for a username and a password, right? Once you have both: the username and the password, you can validate them in your database of username/password pairs, and if both match - you know that user has passed the Authentication stage. From this moment on if you keep the session open all you need is to remember the username.
You might want to let user foo to access to some resource, but restrict her from accessing to another resource, which in turn is accessible only for user bar. The process of checking access rights is being called Authorization. For Authorization all you need is a username or some other attribute you authorize upon. For example you can authorize upon IP number, for example allowing only your local users to use some service. Be warned that IP numbers or session_ids can be spoofed, and that is why you would not do Authorization without Authentication.
Actually you are familiar with both terms for a long time - when you telnet to your account on some machine you go through a login process (Authentication). When you try to read some file on your file systems the kernel checks first the permissions on this file. (you go through Authorization). That's why you could hear about Access control which is another name for the same term.
I am going to present some real world security requirements and their implementations.
If you run an Extranet (Very similar to Intranet but partly accessible from the outside, e.g. read only) you might want to let your internal users an non-restricted access to your web server, but if these users are calling from the outside of your organization you want to make sure these are your employees.
First one is achieved very simply using the IP patterns of the organization
in a Perl Access Handler in an .htaccess file
, which consequently sets the REMOTE_USER environmental variable to a
generic organization's username, so that certain scripts which rely on
REMOTE_USER
environment variable will work properly.
The second one should detect that the IP comes from the outside and user should be authenticated, before allowed in.
As you understood this is a pure authentication problem. Once user passes
the authentication, either bypassing it because of his IP address, or after
entering correct login/password pair, the
REMOTE_USER
variable is being set. Now having it set we can talk about authorization.
OK let's see the implementation. First we modify the <httpd.conf>:
PerlModule My::Auth <Location /private> PerlAccessHandler My::Auth::access_handler PerlSetVar Intranet "100.100.100.1 => userA, 100.100.100.2 => userB" PerlAuthenHandler My::Auth::authen_handler AuthName realm AuthType Basic Require valid-user </Location>
Now the code of My/Auth.pm:
sub access_handler {
my $r = shift;
unless ($r->some_auth_required) { $r->log_reason("No authentication has been configured"); return FORBIDDEN; } # get list of IP addresses my %ips = split /\s*(?:=>|,)\s*/, $r->dir_config("Intranet");
if (my $user = $ips{$r->connection->remote_ip}) {
# update connection record $r->connection->user($user);
# do not ask for a password $r->set_handlers(PerlAuthenHandler => [\&OK]); } return OK; } sub authen_handler {
my $r = shift;
# get user's authentication credentials my ($res, $sent_pw) = $r->get_basic_auth_pw; return $res if $res != OK; my $user = $r->connection->user;
# authenticate through DBI my $reason = authen_dbi ($r, $user, $sent_pw, $niveau);
if ($reason) { $r->note_basic_auth_failure; $r->log_reason ($reason, $r->uri); return AUTH_REQUIRED; } return OK; } sub authen_dbi{ my ($r, $user, $sent_pw, $niveau) = @_;
# validate username/passwd
return 0 if (*PASSED*) return "Failed for X reason";
}
Either implement your authen_dbi()
routine, or replace
authen_handler()
with any authentication handler such as
Apache::AuthenDBI
.
access_handler()
sets REMOTE_USER
to be either userA
or
userB
according on the IP, if non matched PerlAuthenHandler
will be not set to OK, and the next Authentication stage will ask the user
for a login and password.
To force authenticated user to reauthenticate just send the following header to the browser:
WWW-Authenticate: Basic realm="My Realm" HTTP/1.0 401 Unauthorized
This will pop-up (in Netscape at least) a window saying that the Authorization Failed. Retry? And an OK and a Cancel buttons. When that window pops up you know that the password has been cleared. If the user hits the Cancel button the username will also be cleared, and you can have a page that will be output written after the header if this is a CGI (or PHP, or anything else like that). If the user hits the OK button, the authentication window will be brought up again with their previous username already in the username field.
In Perl API you would use note_basic_auth_failure()
method to
force reauthentication.
Since the browser's behaviour is in no way guaranteed, it also may not work, and that should be noted.
When your authentication handler returns OK, it means that user has
correctly authenticated and now $r->connection->user
will have the username set for all the subsequent requests. For
Apache::Registry
and friends, where the environment variables setting weren't turned off, an
equivalent $ENV{REMOTE_USER}
variable will be available. Password is available only through Perl API
with help of get_basic_auth_pw()
method.
If there is a failure, returned AUTH_REQUIRED
flag will tell the browser to pop up an authentication window, to try
again, unless it's a first time. For example:
my($status, $sent_pw) = $r->get_basic_auth_pw; unless($r->connection->user and $sent_pw) { $r->note_basic_auth_failure; $r->log_reason("Both a username and password must be provided"); return AUTH_REQUIRED; }
Let's say that you have a mod_perl authen handler, where user credentials
are checked against a database, and it either returns
OK
or AUTH_REQUIRED
. One of the possible authentication failure case might happen when the
username/password are correct, but the user's account has been suspended
temporarily.
If this is the case you would like to make the user aware of this, through a page display instead of having the browser pop up the auth dialog again. At the same time you need to refuse the authentication, of course.
The solution is to return FORBIDDEN
, but before that you should set a custom error page for this specific
handler, with help of
$r->custom_response
. Something like:
use Apache::Constants qw(:common); $r->custom_response(SERVER_ERROR, "/errors/suspended_account.html"); return FORBIDDEN if $suspended;
|
||
Written by Stas Bekman.
Last Modified at 09/25/1999 |
![]() |
Use of the Camel for Perl is a trademark of O'Reilly & Associates, and is used by permission. |