NAME
Apache::TicketAccess - Cookie based access module.
SYNOPSIS
# in httpd.conf
<Location /protected>
PerlAccessHandler Apache::TicketAccess->authenticate
PerlSetVar TicketRealm protected
</Location>
<Location /loginform>
SetHandler perl-script
PerlHandler Apache::TicketAccess->login_form
PerlSetVar TicketRealm protected
</Location>
<Location /login>
SetHandler perl-script
PerlHandler Apache::TicketAccess->login
PerlSetVar TicketRealm protected
</Location>
<Location /protected/logout>
SetHandler perl-script
PerlHandler Apache::TicketAccess->logout
PerlSetVar TicketRealm protected
</Location>
# in startup.pl
Apache::TicketAccess->configure('protected', {
TicketUserTable => 'users:usenaem:passwd',
TicketTable => 'tickets:ticket_hash',
TicketSecretTable => 'ticketsecrets:sec_data:sec_version',
TicketDomain => '.foo.com',
TicketPath => '/protected',
TicketSecure => 1,
TicketLoginForm => '/loginform',
TicketLoginScript => '/login',
TicketExpires => 15
});
DESCRIPTION
This module provides ticket based access control. The theory behind this is similar to the system described in the eagle book.
This module works using HTTP cookies to check if a user is authorized to view a page. If a cookie named Ticket exists, then the ticket is verified to ensure that the ticket is valid. If the ticket is found to be invalid, then the user is redirected to the URL specified by TicketLoginForm, and access to the requested page is denied.
This module was desigend to be as extensible as possible. Its quite likely that you will want to create your own subclass of Apache::TicketAccess in order to customize various aspects of this module (show your own versions of the forms, override database methods etc).
This system uses cookies to authenticate users. When a user is authenticated throught this system, they are issued a cookie consisting of the time, the username of the user, the expriation time of the cookie, a "secret" version (described later), and a cryptographic signature. The cryptographic signature is generated using the MD5 algorithm on the cookie data and a "secret" key that is read from a database. Each secret key also has a version number associated with it. This allows the site administrator to issue a new secret periodically without invalidating the current valid tickets. For example, the site administrator might periodically insert a new secret key into the databse periodically, and flush secrets that are more than 2 days old. Since the ticket issued to the user contains the secret version, the authentication process will still allow tickets to be authorized as long as the corresponding secrets exist in the tickets table.
The actual contents and length of secret data is left to the site administrator. A good choice might be to read data from /dev/random, unpack it into a hex string and save that.
This system should be reasonably secure becuase the IP address of the end user is incorporated into the cryptographic signature. If the ticket were intercepted, then an aattacker would have to steal the user's IP address in order to be able to use the ticket. Plus, since the tickets can expire automatically, we can be sure that the ticket is not valid for a long period of time. Finally, by setting TicketSecure to a true value, the ticket is not passed over unencrypted connections. In order to attack this system, an attacker would have to exploit both the MD5 algoright as well as SSL. Chances are, by the time the user could break both of these, the ticket would no longer be valid.
CONFIGURATION
There are two things you must do in order to configure this module:
1) configure your mod_perl apache server
2) create the necessary database tables.
Apache Configuration - httpd.conf
There are two ways that this module could be configured. Either by using a function call in startup.pl, or by configuring each handler explicitly in httpd.conf. By using the startup.pl method, the amout of duplicated information in httpd.conf is greatly reduced. As such, I do not discuss this method here. I recommend using the startup.pl method, and this is what is discussed here.
There are four blocks that need to be entered into httpd.conf. The first of these is the block specifying your access restrictions. This block should look somrthing like this:
<Location /protected>
PerlAccessHandler Apache::TicketAccess->authenticate
PerlSetVar TicketRealm protected
</Location>
TicketRealm is just a label that is used to tell Apache::TicketAccess which configuration to use for this block.
This specifys that any URL in /protected must pass authentication through Apache::TicketAccess. Directory blocks should also work fine here (although I have not tested this).
The remaining blocks control how to display the login form, and the login and logout urls. These blocks should look similar to this:
<Location /loginform>
SetHandler perl-script
PerlHandler Apache::TicketAccess->login_form
PerlSetVar TicketRealm protected
</Location>
<Location /login>
SetHandler perl-script
PerlHandler Apache::TicketAccess->login
PerlSetVar TicketRealm protected
</Location>
<Location /protected/logout>
SetHandler perl-script
PerlHandler Apache::TicketAccess->logout
PerlSetVar TicketRealm protected
</Location>
It should be noted that Apache::TicketAccess is Apache::Filter aware. So if you implement your own subclass of Apache::TicketAccess that emits SSI tags, you could do something like this:
<Location /loginform>
SetHandler perl-script
PerlHandler Apache::TicketAccess->login_form Apache::SSI
PerlSetVar TicketRealm protected
PerlSetVar Filter On
</Location>
Apache Configuration - startup.pl
Any TicketAccess configuration items can be set in startup.pl. You can configure a TicketRealm using:
Apache::TicketAccess->configure(String realm, *Hash config)
config is a reference to a hash specifying configuation values.
Valid configuration items are:
- TicketDB
-
This directive specifys the DBI URL string to use when connecting to the database. Also, you might consider overloading the dbi_connect method to handle setting up your db connection if you are creating a subclass of this module.
example: dbi:Pg:dbname=test
- TicketDBUser
-
This directive specifys the username to use when connecting to the databse.
- TicketDBPassword
-
This directive specifys the password to use when connecting to the databse.
- TicketTable
-
This directive specifys the ticket hash table as well as the column name for the hash.
Format: table_name:column_name
Example: tickets:ticket_hash
- TicketUserTable
-
This directive specifys the users table and the username and password column names.
Format: table_name:username_column:password_column
Example: users:usrname:passwd
- TicketPasswordStyle
-
This directive is currently unimplemented. Only cleartext passwords in the database are currently supported. In future versions, you can set this to a different value to support crypt or md5 style passwords.
- TicketSecretTable
-
This directive specifys the server secret table as well as the names of the secret data column and the version column.
Format: table_name:data_column:version_column
Example: ticketsecrets:sec_data:sec_version
- TicketExpires
-
This directive specifys the number of minutes that tickets should remain valid for. If a user exceeds this limit, they will be forced to log in again.
- TicketDomain
-
This directive specifys the "domain" field to pass with the Ticket cookie.
- TicketPath
-
this directive specifys the "path" field to pass with the Ticket cookie.
- TicketSecure
-
This directive specifys if the "secure" flag should be set with the Ticket cookie. If this is set to a true value, then the cookies will only be sent over SSL connections.
- TicketLoginForm
-
This directive specifys the URL of the login form.
Example: /loginform
- TicketLoginScript
-
This directive specifys the directive of the login handler
Example: /login
- TicketLogoutURI
-
This directive specifys the URL that the user should be sent to after they are successfully logged out (this is done via a redirect).
Example: /logged_out_message.html
Database Configuration
Three database tables are needed for this module:
- users table
-
This table stores the actual usernames and passwords of the users. This table needs to contain at least a username and password column. This table is confgured by the TicketUserTable directive.
example: CREATE TABLE users ( usename VARCHAR(32) NOT NULL, passwd VARCHAR(32) NOT NULL );
- tickets table
-
This table stores the ticket hash for each ticket. This information must be stored locally so that users can be forcefully logged out without worrying if the HTTP cookie doesn't get deleted. This table only needs a character field that is 32 characters long to store the hash. Its likely that you will also want to save a timestamp with each hash so that you can delete old tuples from this table periodically.
example: CREATE TABLE tickets ( ticket_hash CHAR(32) NOT NULL PRIMARY KEY, ts TIMESTAMP NOT NULL DEFAULT NOW() );
- secrets table
-
This table contains the server secret and a numeric version for the secret. This table is configured by the TicketSecretTable directive.
example: CREATE TABLE ticketsecrets ( sec_version SERIAL, sec_ts TIMESTAMP NOT NULL DEFAULT NOW(), sec_data TEXT NOT NULL );
METHODS
This is not a complete listing of methods contained in Apache::TicketAccess. Rather, it is a listing of methods that you might want to overload if you were subclassing this module. Other methods that exist in the module are probably not useful to you.
Feel free to examine the source code for other methods that you might choose to overload.
- void make_login_screen($r, String action, String request_uri, String message)
-
This method creats the "login" screen that is shown to the user. You can overload this method to create your own login screen. The log in screen only needs to contain a hidden field called "request_uri" with the contents of request_uri in it, a text field named "username" and a password field named "password". You are responsible for sending the http header as well as the content.
action contains the action URL for the form. You must set the action of your form to this value for it to function correctly.
message will contain an error message if the user was unable to log in or if the user was automatically logged out. You can ignore message if you dont wish to show this information to your users.
- DBI::db dbi_connect()
-
This method connects to the TicketDB data source. You might overload this method if you have a common DBI connection function. For example:
sub dbi_connect { my ($this) = @_; return Foo::dbi_connect(); }
Note that you can also adjust the DBI connection settings by setting TicketDB, TicketDBUser, and TicketDBPassword in httpd.conf.
CREDITS
The idea for this module came from the Ticket Access system in the eagle book, along with several ideas discussed on the mod_perl mailing list.
AUTHOR
Michael Schout <mschout@gkg.net>