Packages Used |
Apache 1.3.3: http://www.www.apache.org
Authen::Smb: http://www.perl.com/CPAN |
Soon after I learned about mod_perl, I wanted to know how I could use it to secure my web site. Apache has a number of phases it progresses through as it serves web pages. Three of those phases are access control, authentication, and authorization. In this article, I'll discuss each phase and demonstrate five examples of what they can do.
The access control phase is the first of the three authentication phases available in Apache. This phase allows you to restrict access to specific URLs based on criteria other than who the visitor is. This has traditionally been used to allow or deny access for certain hosts. However, with mod_perl you can restrict access to specific directories for any reason you like: time of day or week, phase of the moon, user agent, the referring page, and just about anything else you can envision.
The Apache::HostLimit module, is a simple access control handler. For a more detailed explanation of handlers, read Doug MacEachern and Lincoln Stein's article Stately Scripting with mod_perl in TPJ #9, or wait for their upcoming book from O'Reilly.
To activate the access control handler, you'll need the following in your configuration file:
PerlAccessHandler Apache::HostLimit
This tells Apache to invoke the Apache::HostLimit module during access control.
This module is very simple. It determines what computer the visitor is coming from, and then it does one of two things. It either returns FORBIDDEN, indicating that the user is not to be granted access, or DECLINED, indicating that the module has no opinion one way or ther other. Line 1 contains the package declaration common to all Perl modules. Line 3 contains the use strict pragma. All mod_perl modules should employ the strict pragma to help overcome some of the traps associated with mod_perl (see mod_perl_traps.pod, bundled with the mod_perl distribution). Line 4 pulls in the common Apache return codes for use later. Line 6 begins handler(), the standard handler routine. Whenever mod_perl executes a handler, it looks for a subroutine by that name. All mod_perl handlers are called with one argument, the request object. (The one exception is when the handler has a $$ prototype. Then it's treated as an object-oriented method call.)
Line 9 makes use of the request object, $r, to obtain the name of the browser's computer. Lines 11 to 21 determine the request's fate. If the host variable matches, the module notes this in the error log, line 12 and 16) and returns FORBIDDEN. If the host does not match, the request falls through, returning DECLINED to indicate that all is well and the request can move on to the next handler.
This example is very simple, but it shows how much can be accomplished in this phase. For instance, you might have handler() read the information from a file so that it could be updated without restarting the server. (This is just an example. If you want to stop an annoying person (or robot) from accessing your website, you would be better off using the URI translation handler. The translation handler is the first phase after the request has been read; by denying access there, you avoid the extra processing for a request that is destined to fail anyway).
In the second access-checking phase, authentication, the server uses a technique like username/password verification to determine if the visitor is welcome. You can invoke a variety of resources at this point, including:
Listing 2 demonstrates the Apache::AuthenSmb module, which authenticates users against an NT domain server - a nice feature for Unix web servers in an NT environment. This is a stripped down example, lacking the authorization phase; you can obtain the complete module from the CPAN or create your own.
The configuration directives for this module make use of PerlSetVar to avoid hardcoding configuration details. (PerlSetVar allows you to set variables that will be accessible in your Apache modules.) In addition to some mod_perl directives, you must use the AuthName, AuthType, and require directives for Apache to invoke the authentication handlers. Documentation on these is bundled with Apache.
AuthName "Authentication Realm" AuthType Basic PerlSetVar myPDC PDCSERVER PerlSetVar myBDC BDCSERVER PerlSetVar myDOMAIN DOMAIN PerlAuthenHandler Apache::AuthenSmb require valid-user
In addition to the PerlSetVar directives, which in this case provide several configuration details for the module, a handler directive is required. This directive behaves just like the access control module directive that we used for Apache::HostLimit.
The interesting part begins on line 9, where a call into the Apache API obtains the type of authentication and the password sent. Line 10 checks to make sure the authentication type is basic. Line 12 makes another call into the request object to get the username entered by the client. Lines 14 to 16 pull in the configuration information set by PerlSetVar.
The downside of basic password authentication is that it sends the username and password to the server in the clear. Apache also supports digest authentication, a more secure method, but it is not available on many browsers.
Up to this point, this listing shows a standard setup for an authentication module. Now we start getting into the heart of the module. The first step is to check that the user actually entered a username. If not, the module calls note_basic_auth_failure() on line 19, logs the error, and returns AUTH_REQUIRED, which indicates that authentication was unsuccessful. The next lines, 24 to 28, check to make sure a PDC (Primary Domain Controller) was configured. If not, the module again notes and logs the failure and returns AUTH_REQUIRED. Line 30 makes a call into the Authen::Smb module (on the CPAN) using the username and password sent, along with the configuration details. Line 36 checks the return value of the Authen::Smb call. If the call fails, it notes and logs the failure, returning AUTH_REQUIRED. If the request makes it this far, line 42 returns OK indicating success and allows Apache to continue with the request.
MORE NT AUTHENTICATION
The Apache::AuthenOverideSmb module builds on Apache::AuthenSmb. It allows you to override or supplement the users available in your NT domain with an external password file. This can be convenient if you need to grant access to individuals or groups that might not have an NT account.
This module is configured in much the same way as Apache::AuthenSmb, adding only the definition of the external password file:
AuthName "Authentication Realm" AuthType Basic PerlSetVar myPDC PDCSERVER PerlSetVar myBDC BDCSERVER PerlSetVar myDOMAIN DOMAIN PerlSetVar password_file /your/.htpasswd PerlAuthenHandler Apache::AuthenOverideSmb require valid-user
This module is similar to the two you've seen so far. However, it adds line 11, which pulls in the path to the password file. Lines 18 to 22 create an HTTPD::UserAdmin object using the path to our password file. Lines 24 to 32 check to see if an entry exists in the password file. If so, the module checks to make sure the password is correct. In that case, the module returns OK; otherwise, the module notes and logs the failure and returns AUTH_REQUIRED. From this point the module is the same as Apache::AuthenSmb, making a call to Authen::Smb to determine if the user should be granted access.
Once a user has been authenticated, we're ready for the final stage: authorization. This determines whether an authenticated user possesses the proper credentials to access the protected URL. This is where you can use the Apache require directive in most any Apache module, allowing you to specify whether access should be granted to a valid user, a list of users, or to a Unix group.
Listing 4, the Apache::AuthzExample module shows a very basic authorization handler, handling the require user <username> and require valid-user directives. The configuration details for authorization handlers are similar to other handlers. However, in addition to the require directive, you have to add a mod_perl directive like the one below:
PerlAuthzHandler Apache::AuthzExample require valid-user
This module begins much like the others, pulling in therequired modules and getting the request object. Line 8 makes an API call to get the array of require directives. If no directives have been defined, line 9 returns OK. Line 11 obtains the username entered in the authentication phase. (Remember that this user will have already passed the authentication phase.)
Lines 13 to 21 iterate over the require directives. Line 14 splits off the type of require and the rest of the information for that directive. Line 15 checks to see if the type is "user." If so, line 16 checks to see if the username is present. If so, it returns OK. Line 18 checks whether the type is valid-user and returns OK if so, because the user has already been authenticated. If any of these checks fail, the module drops through to the final statements, which logs the failure and returns AUTH_REQUIRED to the client.
Apache::AuthzManager is similar to Apache::AuthzExample, but it shows how easily you can add a type to the require directive. Assume that you want to limit access based on the user's manager, a good tool for department-level web pages. I'll assume the existence of a module called CheckManager that takes care of the work behind the scenes.
This module is exactly the same as Apache::AuthzExample, with the exception of the checks for the require type, lines 16 to 22. Here the module looks for the manager keyword and then calls the checkManager() function for each keyword found. It returns OK if the user is allowed access, or falls through to the failure state and returns AUTH_REQUIRED if the user is not authorized.
These examples illustrate some of the simplest mechanisms for access checking. You can customize them in as sophisticated a manner as you wish: limit access to URLs during business hours, exclude hosts from a continuously-updated blacklist, or authenticate against a company LDAP database. For more information on mod_perl, check out the sites www.modperl.com and perl.apache.org. Questions about mod_perl can be sent to the mod_perl mailing list; to join, send a message to majordomo@apache.org with subscribe modperl in the body of the message.
__END__