DACS DACS - The Distributed Access Control System


DACS Tips & Examples

The following practical examples, tips, and "HOW-TOs", collected from the DACS technical documentation, our experience, and questions from administrators, show how DACS can solve common kinds of authentication and access control problems. Because the technical detail within the manual pages can sometimes be overwhelming, we hope that by examining these bite-size topics informally, blog-like, you will gain a better understanding of DACS and the kinds of things that it can do - and, especially, that it can do much more than just "access control for Apache". It's a cookbook in the making.

Additions are made to the list from time to time, and suggestions and contributions are welcome.

It is probably best to look at the examples in the order they are presented. Please note that some examples will not work with older releases, and they sometimes require features available only in an upcoming release. Also, the examples usually assume that defaults are being used and they may have been simplified to make them easier to understand. Of course you will need to change the domain name example.com to your own domain name if you want to try an example. You can try a few of the examples without having to install DACS.

Other than mod_auth_dacs and a few basic Apache modules, no other Apache modules are required to do anything described here (not the mod_auth* modules and not mod_rewrite). And once Apache has been configured to delegate authorization of the appropriate pieces of URL space to DACS, no additional changes to Apache's configuration are required by DACS and changes to the DACS configuration do not require restarting Apache.

As you look through some of the examples you may be tempted to say, "So what? I can write an N-line Perl script to do that." And that may be true, especially for the simpler cases. DACS gives you cross-platform tools, built on top of a solid, secure framework, that can help you to quickly construct, modify, extend, and combine solutions to these problems and many more, while hiding much of the detail and complexity, and without you having to write any code.

Similarly, there are standard and third-party Apache modules that can do different bits and pieces of what DACS does, but we are not aware of any that can do everything DACS can. Combining Apache modules to get the functionality you need is likely to result in a more complicated, error-prone, and less secure system compared to DACS.

After you become comfortable with DACS, you should begin to see how it can solve more problems and enable new modes of managed sharing. Don't think of it as merely "an access control system for web sites", or a "single sign-on system for web services", also think of DACS as an authentication and authorization toolbox.

Tip Index:

  1. How to Find Out if DACS is Working
    Or, What just happened?
  2. Log Smog
    Or, Log files are your friends, mostly
  3. Private Passwords
    Or, Making accounts just for DACS
  4. Password Validation
    Or, Who goes there?
  5. Anonymous Login
    Or, Low-security accounts
  6. One-Time Passwords
    Or,Quick and easy two-factor authentication
  7. Post-Authentication Control Flow
    Or,After authentication, then what?
  8. Debugging SSL/TLS Problems
    Or, SSL not treating you well?
  9. Access Control Based on Filenames
    Or, Shortcuts for suffixes
  10. Authentication Using Basic Auth
    Or, Accepted nearly everywhere
  11. Password-Protect Your Web Site
    Or, Simple authorization
  12. Authentication Using htpasswd/htdbm Files
    Or, Why not use what you've got?
  13. Using Roles
    Or, Do you have any goals for your roles?
  14. Mapping User Names
    Or, What's in a name?
  15. Disabling Accounts and Services
    Or, Pulling the plug
  16. Restricting Administrator Login
    Or, A precaution if you use ADMIN_IDENTITY
  17. Tracking User Activity
    Or, Big Brother may be watching
  18. Constraints
    Or, Passing information from rules to programs they protect
  19. Autologin
    Or, Logging in without logging in
  20. Interactive, Indirect Authentication
    Or, Getting credentials without giving DACS your password
  21. Apache 1.3, IIS, etc.
    Or, Using DACS with "unsupported" web servers
  22. An All-in-One Admin Tool
    Or, Your one-stop URL for managing DACS
  23. Data-Driven Access Control Rules
    Or, An alternate way to administer rules
  24. Using Dynamic Rules
    Or, Replacing many rules with just one
  25. Time-Based Restricted Access
    Or, Limiting access to a certain day or time
  26. Examining CGI Arguments
    Or, Testing for the existence or value of a CGI parameter
  27. Managing Access Based on Knowledge of a URL
    Or, How to configure a site like craigslist.com
  28. Customizing User Roles
    Or, Generic methods for obtaining user roles
  29. Checking for Authorization, Part 1
    Or, Rule-driven access control and customization
  30. Checking for Authorization, Part 2
    Or, Authorization testing for web-based software
  31. Stateless Authentication
    Or, Authenticate-And-Go
  32. Accounts With Limited Lifetimes
    Or, Disposable accounts
  33. Persistent Counters
    Or, Count on counters
  34. Short Links, Permalinks, and Smart Links
    Or, URLs-R-Us
  35. Rlinks
    Or, Simplified, per-URL access control
  36. Extending Single Sign-On to Applications
    Or, Should users have to authenticate more than once?
  37. Using Multiple Password Files
    Or, Separate accounts for different user classes
  38.  

How to Find Out if DACS is Working

After you have installed and configured DACS to provide access control for some of your web resources (i.e., you've "DACS-wrapped" some URLs), how can you be certain that DACS is doing something? Because DACS is normally transparent to its users, especially if access is being granted, it is not always easy to be sure. Maybe it is being granted because you have not configured DACS correctly and no access control is being performed!

One way to check what, if anything, is being done by DACS when you send a request to the web server is to look at the DACS log file. But an easier and more certain solution is to use a web browser that will display the response headers returned by the web server (such as dacshttp using its -v flag, lynx using its -mime_header flag, or Wget using its --server-response flag) and add the DACS_ACS argument to the request.

Let's say that you want to check if DACS is managing access to the web service, dacs_version (you can try this):

% dacshttp -v "https://dacs.dss.ca/cgi-bin/dacs/dacs_version?DACS_ACS=-check_only+-status_line"
The command above should produce output similar to the following (some lines have been deleted):
Method: GET Scheme: https
Host: dacs.dss.ca
User-Agent: DACS-dacshttp/1.4.18

Status-Line: HTTP/1.1 200 OK
DACS-Status-Line: DACS-1.4.18 798 Access granted, unauthenticated user (s7HDFogG)

798 Access granted
The response header named DACS-Status-Line tells you that DACS did indeed perform authorization checking on the request, and at least in this instance would have granted access. If that response header was not sent, no authorization checking was performed on the request by DACS.

Note that the program, dacs_version, was not executed and may not even exist - it is only necessary that the URL be DACS-wrapped.

This feature can also be used by middleware and thick clients to test whether the user is allowed to access something or perform an action, such as when building a menu of operations currently available to the user. We take a closer look at this later.

Log Smog

Looking at its log file is often the best way to discover what DACS is doing, not doing correctly, or not doing at all. These logs files are separate from the Apache log files and are typically found in the /usr/local/dacs/logs directory. The value of the LOG_LEVEL configuration directive that is in effect for a jurisdiction determines how much detail is recorded in its log file. At low logging levels ("info" or above), relatively little information will be logged but everything that is important for you to see should be there.

If you have a problem with DACS, operating at a detailed logging level should help you to understand what is happening, and if you need to report a problem, the relevant portion of the log file should be submitted.

At high logging levels ("debug" or "trace"), a great deal of information is recorded and the log file's size will grow quickly - remember to save, rotate, or truncate the file as you see fit. The LOG_FILTER directive can help you to log events more selectively.

If you prefer to ordinarily set LOG_LEVEL to a lower logging level ("warn" or "error") to avoid large log files, you can selectively increase the logging level depending on the context. For example, if you would like to understand what is happening when a client from the IP address 10.0.0.17 invokes a DACS-wrapped resource, you can use a directive like the following:

LOG_LEVEL (${DACS::REMOTE_ADDR:?} eq "10.0.0.17") ? "TRACE" : "WARN"

DACS utility commands normally write log messages to their standard error stream. For these programs, the -ll, -q, -t, and -v command line flags adjust the logging level. The dacs_acs(8) program, which is run by Apache according to the AddDACSAuth directive, also understands these flags, which will override its LOG_LEVEL.

Private Passwords

Although one of its most important capabilities is that DACS can be configured to authenticate users against your existing accounts, such as Unix, Windows, or Apache accounts, sometimes you need to create a completely separate set of username/password pairs. This could be for security, performance, or availability reasons, because you do not have sufficient privileges to create accounts, and so on. In much the same way that Apache lets you create accounts that it can use to authenticate users through Basic Auth, DACS lets you create accounts that can be used by its local_passwd_authenticate module (also local_simple_authenticate, which uses password-less accounts).

These accounts are completely separate from any other accounts, although the resulting identities may or may not be distinct. Depending on what convention you adopt for these usernames and how you write your access control rules, you can make these usernames distinct from those obtained from other accounts, or equivalent to them. In other words, you may need to think about whether user auggie authenticated through local_passwd_authenticate is the same as user auggie authenticated through local_unix_authenticate. (Aside: this is one of the applications of the AUXILIARY argument to dacs_authenticate: at login, a user might be asked to choose between a staff button and a guest button, with the selected value being assigned to the AUXILIARY argument; staff would enable authentication through the local_unix_authenticate module, while guest would enable local_passwd_authenticate and might modify the username by prepending "guest-" to it.)

The utility program that helps you create and administer these accounts is called dacspasswd(1). A web service version, dacs_passwd(8), is also provided, but we will not discuss it here.

A simple example will show how this all works. By default, DACS uses this directive to tell it where to look for the password file used by dacspasswd and local_passwd_authenticate:

VFS "[passwds]dacs-kwv-fs:${Conf::FEDERATIONS_ROOT}/${Conf::FEDERATION_DOMAIN}/${Conf::JURISDICTION_NAME}/passwd"
So if our jurisdiction is named TEST within the federation domain example.com, the password file will be /usr/local/dacs/federations/example.com/TEST/passwd. It has been configured as a text file, indexed by the username. We could just as easily use a Berkeley DB hash file, for instance, but if there are not a large number of accounts, a text file will perform adequately and has its advantages.

We can create a new account with an initial password, and then take a look at the password file:

% dacspasswd -uj TEST -add bobo
New password for bobo?
Re-type new password for bobo?
% cat /usr/local/dacs/federations/example.com/TEST/passwd
bobo:2|XYZZYtdNiuiYy2Oyxr92C|K4LymEGSUMqtnNsbiCxJwW4T7G3
A secure hash algorithm, which is configurable via PASSWORD_DIGEST, is applied to the plaintext password and a salt string (semi-configurable using PASSWORD_SALT_PREFIX). Plaintext passwords are never stored. Other than for an administrator, requirements for the quality of the password can be specified using PASSWORD_CONSTRAINTS.

Next, we configure DACS to authenticate against our password file by adding this Auth clause to dacs.conf:

<Auth id="passwd">
  URL     "local_passwd_authenticate"
  STYLE   "passwd"
  CONTROL "sufficient"
</Auth>

User bobo should be able to login now. If we make some assumptions about the names, and we say that bobo's password is aArdvArk, then she could use the following URL to authenticate:

https://test.example.com/cgi-bin/dacs/dacs_authenticate?USERNAME=bobo&PASSWORD=aArdvArk&DACS_JURISDICTION=TEST&DACS_BROWSER=1
While she could use a link or bookmark to authenticate, most likely you would provide your users with a login page that would submit a form that includes the USERNAME and PASSWORD arguments. The DACS distribution includes some examples of login pages. Or you would use DACS's ability to work with Basic Auth to have the user's browser prompt for the username and password.

Password Validation

It is sometimes necessary for a shell script (or other kind of program) to validate a password. The dacsauth(1) utility is what you need. It can be called in a variety of ways, depending on how you want to handle the password and whether you need to authenticate against a DACS jurisdiction. Keep in mind that passwords that appear on a command line (following the -p flag) may be visible to other users and processes on the system. When possible, a better idea is to use the -prompt flag to ask the user to type the password or the -pf flag to have dacsauth read the password from a file or its standard input without prompting.

To validate the Unix system password for user bobo, you can use the command:

% dacsauth -q -m unix passwd required -u bobo -prompt
The program will prompt for bobo's password. Note that on most Unix systems, dacsauth will have to run as root so that it can access the password file.

The exit status of dacsauth tells you whether the password was valid (exit status is 0) or not (exit status is not 0).

This program can be used without having to create a DACS federation, configuring a DACS jurisdiction, or accessing a web server. Just build DACS and use dacsauth!

Here is a simple, but complete example shell script:

#! /bin/sh

dacsauth -q -m unix passwd required -u bobo -prompt
if test "${?}" = 0
then
  echo "Authentication succeeded"
  exit 0
fi

echo "Authentication failed"
exit 1

Depending on how it was built, your dacsauth may be able to validate a password using any of several methods. Unix-type authentication may not have been included in your dacsauth. To see what is available, run:

% dacsauth -modules

If the http module is available, you can validate the password for a given Google™ account:

% dacsauth -m http passwd suff \
  -OAUTH_URL="https://www.google.com/accounts/ClientLogin" \
  -OUSERNAME_PARAMETER=Email -OPASSWORD_PARAMETER=Passwd -Oservice=xapi \
  -Osource=DSS-DACS-1.4 -u bobo.booboo -pf pwdfile
(change bobo.booboo to the account name). Here, the password will be read from the file pwdfile. dacsauth can also read a password from its standard input.

If you want to validate a password against a particular authentication module at a DACS jurisdiction on the same machine or on a remote host, it is a little more complicated. A command similar to this can be used:

% dacsauth -m unix passwd required \
    -url 'https://example.dss.ca/cgi-bin/dacs/local_unix_authenticate' \
    -DVFS="[federation_keys]dacs-fs:/usr/local/dacs/federation_keys" \
    -u bobo -prompt -fn FEDROOT -fj EXAMPLE
Here, dacsauth will pretend to be the jurisdiction named EXAMPLE in the federation named FEDROOT. The Unix password authentication module at example.dss.ca will be called, which must be in the FEDROOT federation and use the same federation encryption keys as are stored in federation_keys.

If you want to create accounts specifically for your application, use dacspasswd(1). Then, to validate a password for the account name bobo, for example:

% dacsauth -m passwd passwd required -vfs '[passwds]dacs-kwv-fs:/usr/local/dacs/passwd' -u bobo -prompt

Anonymous Login

Configuring an essentially anonymous account, like those used with ftpd(8), can be done with DACS configuration that looks like this:

<Auth id="anon">
STYLE "expr"
CONTROL "sufficient"
EXPR '((${Args::USERNAME} eq:i "anonymous" or ${Args::USERNAME} eq:i "guest") \
   and ${Args::PASSWORD:n}) \
     ? print("Login: ", ${Args::PASSWORD}, " from ", ${Env::REMOTE_ADDR}), \
           strtr(${Args::USERNAME}, "A-Z", "a-z") : ""'
</Auth>
The expression looks for the username "anonymous" or "guest", along with a non-empty password string. It does not care what the password string is - it could require it to look like an email address though. It logs the password and maps the username to lowercase. To collect these "passwords", you might use a LOG_FILTER directive like this one:
LOG_FILTER 'any user debug %bD/logs/user_log'

Note that the two usernames recognized in this example have no special meaning to DACS. It is up to the access control rules to determine whether these identities will have restricted capabilities. You should also be careful not to create confusion between these usernames and those with "real" accounts of the same name (e.g., what if Christopher Guest can authenticate as "guest" by another method?). You could address this potential problem by selecting a username syntax for these special users that will guarantee uniqueness.

One-Time Passwords

DACS gives you two-factor authentication that can be used not only for web login (see dacs_authenticate(8)), but for use with virtually any program (using dacsauth(1)), without having to use a web server or fully configure DACS.

At present, DACS has been tested with the Authenex A-Key (V3.6 OTP-Only Token), Feitian Technologies OTP C100 and C200 units, and the OATH Token and iOATH Lite software applications for the iPod Touch, iPhone, and iPad. All you need to start using strong authentication is DACS and one of the supported hardware or software tokens. A hardware token generates a new, apparently random password each time its button is pressed [photo]. The user gives this password to DACS for verification. If a PIN has been configured for the user's DACS account, it must immediately precede the token's password.

Please consult dacstoken(1) for instructions on how to set up a DACS user account for a token.

After an administrator has created a user account, a program can authenticate the user by calling dacsauth and testing its exit status. Here is all you typically need to do from the command line:

% dacsauth -m token passwd sufficient \
  -vfs '[auth_token]dacs-kwv-fs:/usr/local/dacs/tokens' \
  -vfs '[auth_token_keys]dacs-fs:/usr/local/dacs/auth_token_keys' \
   -user bobo -prompt
The first -vfs flag in the example tells dacsauth where the token accounts are. The second -vfs flag tells it where the encryption keys for the account are. The username being authenticated, here "bobo", is given using the -user flag. The program has been instructed to prompt for the password. If the account has a PIN, it must be provided on the command line following the -aux flag (in DACS 1.4.16, this security weakness will be addressed by allowing the auxiliary string to obtained in the same ways as the password). An exit status of 0 means that the password (and PIN, if provided) was correct, while any other value indicates that the password could not be validated.

DACS must have been configured with the --enable-token-auth flag for dacsauth to have this authentication module built-in. dacsauth must run with sufficient privileges to be able to read and write the account file and read the encryption keys. Note that in this example, dacsauth does all of the work and no server component is accessed.

A token is used in a similar way for DACS login. The necessary configuration directives are described in dacstoken(1).

To purchase a token, request a sample, or for additional information, please contact the vendor.

Post-Authentication Control Flow

After users successfully authenticate, you might want to redirect them to your site's home page, possibly with some customizations. Or you might want each user to be redirected to his or her personal home page. To redirect the newly authenticated user to a web page based on the user's identity, jurisdiction, roles, or other contextual state, configure AUTH_SUCCESS_HANDLER to specify the URL of a DACS-wrapped CGI program that you must write. After examining environment variables automatically passed to it by DACS, or its query arguments, your program can emit an appropriate redirect. To get a better idea of what happens, configure:

AUTH_SUCCESS_HANDLER "url /cgi-bin/dacs/dacs_prenv"
(making sure that dacs_prenv(8) has been installed) and examine the information that is passed to it and displayed. The shell script, Perl script, compiled program, etc. that you write emits the redirect that you want. Install it so that it is DACS-wrapped with an appropriate ACL, and configure AUTH_SUCCESS_HANDLER to point to it. See RFC 2616, Section 10.3.3 for additional details.

Here is a super basic example that displays a simple sign-on message after authentication and then sends each user to his home page:

#!/bin/sh

home_url="https://example.com/~${DACS_USERNAME}/index.html"

echo "Content-Type: text/html"
echo ""
echo "<html><head><title>Welcome</title>"
echo "<meta http-equiv=\"refresh\" content=\"5;url=${home_url}\">"
echo "</head>"

echo "<body>"
echo "Hello, <tt><b>${DACS_USERNAME}</b></tt>.<br/>"
echo "You have signed on to <tt><b>${HTTP_HOST}</b></tt>"
echo "through DACS.<br/>"

echo "Follow <a href=\"${home_url}\">this link</a> to your home page"
echo "or wait to be redirected there shortly."

echo "</body></html>"

exit 0
With only a little more effort the script could look up the target URL in a database. Since this script must be DACS-wrapped, we would have to add a rule that will grant access to it, probably only requiring that a user be authenticated.

In another common situation, a user attempts to access a page but is denied access because she has not logged in. You would like her to be automatically redirected to a login page and, after successful authentication, redirected to the originally requested page. This behaviour can be configured through the ACS_ERROR_HANDLER directive:

ACS_ERROR_HANDLER "NO_AUTH /cgi-bin/login.pl"
With this directive, when access is denied the user is redirected to a script, /cgi-bin/login.pl, which you must write to accommodate the look & feel of your site. That script is passed several arguments that describe the original request. The script must emit an appropriate login page that causes all of the arguments to the script to be submitted in a form as hidden inputs, along with the USERNAME, PASSWORD, and other arguments necessary for authentication at the jurisdiction. Additionally, the form should send the variables:
<input type="HIDDEN" name="DACS_BROWSER" value="1">
<input type="HIDDEN" name="ENABLE_AUTH_HANDLERS" value="1">

The programmability of DACS has been improved through configuration directives that provide an opportunity for the administrator to perform arbitrary actions when various events occur: AUTH_SUCCESS, which specifies an expression to be evaluated if authentication by dacs_authenticate succeeds, AUTH_FAIL, which is evaluated if authentication fails, and ACS_SUCCESS and ACS_FAIL, both of which are understood by dacs_acs and evaluated if authorization succeeds or fails, respectively. You could use these directives to provide system event data for a real-time monitoring system, for instance, or to send an email notification when an event of interest occurs.

Debugging SSL/TLS Problems

Configuring SSL/TLS for Apache and for DACS can sometimes be frustrating. The sslclient(1) utility can help you to test that SSL/TLS is working properly or find out what is going wrong. For example, the following command will emit debugging information while attempting to connect to port 443 at dacs.dss.ca:

% sslclient -un -v -v dacs.dss.ca:443
If the connection is established, sslclient will copy its standard input to the connection. You can try typing:
GET / HTTP/1.0
(Then enter a blank line.) If DACS has been configured to use SSL/TLS (to call an authentication module, for instance), it may need to be configured through the SSL_PROG_ARGS directive. For example, you may need to specify the -sm flag if wildcard server certificates are used:
SSL_PROG_ARGS "-sm .*\.fedroot\.com"

Note that the dacshttp(1) utility uses sslclient for its SSL/TLS functionality, so if dacshttp does not seem to work properly when an https URL is invoked, the first thing to do is make sure that sslclient is able to establish a connection to the server.

Access Control Based on Filenames

It is sometimes necessary to control access to a certain type of file, regardless of exactly where the files are located within a DACS-wrapped area. We would like to avoid having to explicitly name all of these files and would prefer a single rule to achieve this so that we do not need to update the rule every time a new file of this type is added. (We are really doing filename suffix-based access control rather than "type of file" access control because we are assuming that the suffix correctly determines the type.)

The solution is to use regmatch() to examine the request URI with a rule that looks something like this:

<acl_rule enabled="yes">
  <services>
    <service url_expr='regmatch(${DACS::URI}, ".*\.pps$")
      ?  ${DACS::URI} : "do-not-match"' />
    </services>

    <rule order="allow,deny">
      <allow>
        <!-- identify who is allowed access -->
      </allow>
    </rule>
  </acl_rule>
This rule will be applied to all URLs that end with .pps, unless another rule matches the URL exactly and is examined before this rule. So if there are any exceptions, their rules must be ordered so that they are processed before this general one. The numeric suffix on the end of rule names is used to order the rules; acl-foo.0 is examined before acl-foo.1, and so on (see dacs.acls(5)). There is not much special about the "do-not-match" string; it is just a string that cannot match a request because it does not begin with a slash and self-documents our intention. Note that ${DACS::URI} is only the path component of the request URI.

Authentication Using Basic Auth

The Basic Authentication Scheme (RFC 2617), a simple framework for supporting web-based authentication, is implemented by most web browsers and is widely used by web sites. DACS provides ways for you to use "Basic Auth" to prompt a user for a username and password that can then be passed to any suitable DACS authentication module for validation. You can validate the given username/password against a Unix login, Windows login, Apache password file, a web service, and so on. The details can depend on the resource that the user requests. No additional configuration of Apache is required - everything is done by DACS.

This capability works with the neon library, for instance, so that any application based on it should be able to authenticate users using Basic Auth through DACS. We are using Subversion in this way (and if you examine files in the DACS distribution you are likely to find a DACS identity in the revision stamp).

For more information and examples, please refer to HTTP Authentication and the HTTP_AUTH directive. Release 1.4.18 extended this feature.

Authentication Using htpasswd/htdbm Files

If you have a lot of Apache user accounts managed by htpasswd (or htdbm), the last thing you want to have to do is create a new set of usernames and passwords for those same users so that you can authenticate them using DACS. Or perhaps you simply prefer to use those Apache utilities. In any case, if you configure DACS to use its local_apache_authenticate authentication module, it can validate a given username/password pair against account information it finds in the htpasswd (or htdbm) file that you specify.

For web-based authentication, all that you need to do for this form of authentication is to add an Auth clause like the following to dacs.conf:

<Auth id="apache">
  URL     "apache"
  STYLE   "password"
  CONTROL "sufficient"
  OPTION  "AUTH_FILE=/usr/local/apache2/conf/passwords"
  OPTION  "AUTH_MODULE=mod_auth"
</Auth>
Here, AUTH_FILE gives the name of the password file to use. The AUTH_MODULE tells the module what kind of password file it is (in this case, one used by Apache's mod_auth module).

This example uses the version of local_apache_authenticate that is built-in to dacs_authenticate. When dacs_authenticate runs as a CGI program it must be able to read the password file. This authentication module needs to be enabled at build time (using --enable-apache-auth).

Note that this form of authentication can be used with Apache or DACS Basic authentication RFC 2617, but it is not required. The USERNAME and PASSWORD arguments can be passed to dacs_authenticate using the HTTP GET or POST methods. So you can construct an HTML FORM that asks for a username and password, for example, and validate the information provided by a user against your htpasswd file.

See dacs_authenticate(8) for additional information and examples.

Validating a password against an Apache password file is easy. All you need is dacsauth(1). Assuming that DACS was built with --enable-apache-auth (to check, do: dacsauth -modules) and you are running dacsauth on a machine from which the htpasswd file is directly accessible, construct an appropriate command:

% dacsauth -m apache passwd sufficient -OAUTH_MODULE=htpasswd \
    -OAUTH_FILE=/usr/local/apache2/conf/passwords -u bobo -prompt
Here, you will be prompted for the password for user bobo. Of course the password file must be readable to the program. Any of the other supported password file types can also be used in this manner. An exit status of zero indicates success, while any other value indicates that the password was incorrect or an error occurred.

The same kinds of test can be made using dacsauth against an Apache password file at any DACS jurisdiction, provided the program has sufficient privileges and the necessary federation encryption keys are available. Please refer to dacsauth(1) for details.

Password-Protect Your Web Site

One of the simplest ways to protect a web page (or other web resource) is to demand a valid password be presented before access is granted. And perhaps the easiest way to get a password is to let the user's browser do the prompting - you don't need to make a login page. And if you don't really care who the user is, there's no need to create accounts; just get the password from the user and check that it's valid. Let's look at one way to do this. (It may help if you have already taken a look at Authentication Using Basic Auth and Stateless Authentication.)

Let's say that we want to password-protect everything under /bobo in our jurisdiction's URL space. As always, that area of the web site must be DACS-wrapped, which is explained in detail elsewhere (see mod_auth_dacs and the Quick Start Tutorial), but after initial configuration you would add Apache directives like:

<Location /bobo>
    AuthType DACS
    AuthDACS dacs-acs
    Require valid-user
</Location>

We must then add a DACS access control rule so that our jurisdiction grants access only to users that know the password:

<acl_rule enabled="yes">
  <services>
    <service url_pattern='/bobo/*'/>
  </services>

  <rule order="allow,deny">
    <allow>
      user("auth")
    </allow>
  </rule>
</acl_rule>

If you try this example, verify that an unauthenticated user is not allowed to access anything under /bobo.

Next, we add some DACS configuration directives so that we can use HTTP Basic Auth (RFC 2617) to cause the user's browser to prompt for a username and password. We will ignore the username.

EVAL ${Conf::http_auth_401} = "yes"
HTTP_AUTH_ENABLE "pre_acs_only"
HTTP_AUTH "basic 'Password Required' /bobo/* -pre \
   -m expr expr suff -expr '\${Args::PASSWORD} eq \"foozle\"'"
In Basic Auth jargon, we have created a realm. Note that we have not added any Apache configuration other than what we needed to DACS-wrap the /bobo area.

When a user first tries to access a URL under /bobo, such as https://www.example.com/bobo/index.html, she will be prompted by her browser for a username and password. If and only if the password matches the string foozle exactly will access be granted. (If you're trying this, verify that only a user that gives the correct password is allowed to access anything under /bobo.)

Note that any new resources that are placed under /bobo will automatically be password protected - you don't have to add or change anything in our configuration. Also note that once a user has given a correct password, she will not be prompted again if she accesses any other URLs in the same realm as long as her browser remembers her authenticated session and the expected password has not changed. Keep in mind that were you to add full-fledged authentication to your site, users who already authenticated normally would not be prompted for a password when accessing a URL under /bobo.

It is straightforward to extend this configuration in various ways, such as having different passwords for different web pages, getting the password from a file instead of embedding it in dacs.conf, making the expected password the result of evaluating an expression instead of being a static string (though take care not to change the expected password after a correct password is seen), using the string entered as the "username" for some special purpose, and so on. The password required for access by this directive is left as an exercise:

HTTP_AUTH "basic 'Password Required' /bobo/* -pre \
   -m expr suff -expr \"\${Args::PASSWORD} == strftime('%a')\""

Using Roles

DACS provides roles as a convenient way to classify users that have something in common. The idea is that, at authentication time, you can say "this user is a staff member" or "this user is a teacher" or "this user is a guest" or "this user is a sales rep in the computer department" and have that information stored in the user's credentials for efficient access. Why would you want to classify users? To make it easier to write and administer access control rules. A rule (or set of rules) can then simply say, "grant access to any staff member" or "deny access to all students". The set of staff members can change without having to modify rules that depend on its membership.

Another application of roles is to selectively and conveniently pass descriptive information into DACS without any risk of revealing potentially confidential information. For example, if DACS were used to control access to an e-learning application, its administrator might want to hide student names and various personal information from DACS (and from other applications, too). By indirectly providing DACS with role information, such as a student's school, age, and gender, access control rules and DACS-wrapped web services can access this role information but because DACS does not directly access the application's data, the student's name and other personal information need not flow into DACS. The DACS identity assigned after successful authentication does not have to include the username provided at authentication time, so a user can sign on as "sally smith" but be assigned the DACS username "483919289".

DACS has many ways to obtain roles and new ways can be added fairly easily. Ideally, you are already storing and maintaining role information somewhere (i.e., you already have username-to-roles mappings) and you can just import it. But if not, you may be able to write a simple program to generate the information that DACS needs or you can do it manually. We will take a quick look at one of the ways of obtaining roles and show how roles work with access control rules.

Except for some restrictions on the syntax of roles, DACS does not assign any meaning to them. A user can have different roles at different times, and new roles can be added freely.

The easiest way to set up roles is to use a plain text file. The default site configuration includes this directive:

VFS "[roles]dacs-kwv-fs:${Conf::FEDERATIONS_ROOT}/\
${Conf::FEDERATION_DOMAIN}/${Conf::JURISDICTION_NAME}/roles"

So a file named /usr/local/dacs/federations/example.com/test1/roles might contain mappings for our jurisdiction TEST1 within the federation domain example.com. This file will consist of a series of lines, each one giving a username, followed by colon, and then the list of roles belonging to that username:

alice:student
bob:teacher,staff
carol:student
don:guest

It does not matter at all to DACS what you use for a role name as long as it is made up from valid characters. Choose any names you want.

An access control rule anywhere within the federation can use these predicates to represent "any teacher" and "any student", respectively:

user("%TEST1:teacher") 
user("%TEST1:student")

Please refer to dacs_authenticate(8) and dacs.exprs(5) for details. Much more can be done with roles, but we will save that for another time.

Mapping User Names

Sometimes the name a user must give to authenticate is not convenient to use with DACS (e.g., it is a long email address or an X.500/LDAP Distinguished Name) or is invalid to use with DACS (e.g., because it contains an unacceptable character). Or maybe a user can authenticate using different names but you want each of them to map to the same DACS username. How is this kind of name mapping done?

Authentication based on an SSL client certificate (dacs_authenticate(8)) has this capability built-in through the certnamemap item type. But a solution that can be used with any authentication method is available.

The following example is probably the simplest way to make this work, but it is not the only way. We begin by creating some initial mappings. We will use a plain text file, with the name used for authentication (the key) followed by a colon, followed by the DACS name to assign to the authenticated user (the value):

bobo@bobopc.example.com:bobo
bobo@example.com:bobo
Bobo Wilson:bobo
Let's say this file is called /usr/local/dacs/federations/example.com/EXAMPLE/usernames. We would extend and revise this file as necessary over time.

Next, we add the following directive to dacs.conf:

VFS "[usernames]dacs-kwv-fs:${Conf::FEDERATIONS_ROOT}/\
${Conf::FEDERATION_DOMAIN}/${Conf::JURISDICTION_NAME}/usernames"
Now DACS knows where this file is and what access method to use with it.

Lastly, in an appropriate Auth clause, we add the directive:

EXIT* 'vfs(exists,usernames,${Auth::CURRENT_USERNAME}) ? \
    vfs(get,usernames,${Auth::CURRENT_USERNAME}) : ""
If authentication succeeds, the username given by the user (or returned by the authentication module) will be looked up in the file. If it is found, the corresponding value is returned, otherwise the empty string (an invalid username) is returned.

An obvious extension to DACS would be for the keys and values to be expressions.

Disabling Accounts and Services

Disabling an account for use by DACS is easily done - just add an expression to the revocation list that names the account or describes a condition that must be satisfied - it does not matter where the user's account is or how the user authenticates through DACS. This lets you disable accounts with respect to use by DACS but does not actually make any changes that are visible outside of DACS (i.e., DACS provides user provisioning only for accounts that it specifically manages; accounts managed by other software, such as Unix or Windows accounts, are never actually modified by DACS but they can be disabled for use by DACS).

If an expression in the revocation list evaluates to True, authentication will fail, even if the correct password etc. are given.

The same revocation list is checked during access control processing, providing a convenient place to override all access control rules. (With release 1.4.16 of DACS, it became possible to independently disable accounts or deny access.)

(A related capability of DACS is that it can alert an administrator to weak user passwords, regardless of how a user authenticates through DACS. See the PASSWORD_AUDIT directive for details.)

By default, the revocation list is stored in a plain file:

VFS "[revocations]dacs-fs:${Conf::FEDERATIONS_ROOT}/\
${Conf::FEDERATION_DOMAIN}/${Conf::JURISDICTION_NAME}/acls/revocations"

If we were to add the following line to the revocation list in our jurisdiction, the DACS identity bobo would not be allowed to obtain DACS credentials in our jurisdiction:

deny user(":bobo")

The following line prevents both authentication and any access to DACS-wrapped resources by the DACS identity EXAMPLE:auggie:

deny user("EXAMPLE:auggie")

This line prevents all access to DACS-wrapped resources by any request that is not received over an SSL/TLS connection:

deny ${Env::HTTPS} eq.i "off"
Should you need to disable access via HTTPS, replace "off" with "on".

The following three lines deny all access from the specified IP address range, deny all access on any Sunday, and deny access to all unauthenticated users, respectively:

deny from("10.0.0.0/8")
deny time("wday") eq 0
deny user("unauth")
The third line also prevents anyone from authenticating at the jurisdiction unless they have already authenticated somewhere else.

Although a rule is the preferred way to manage access to one or more DACS-wrapped resources, the revocation list can be used to deny access, especially if you only want to override a rule temporarily. If we were to add the following line to the revocation list in our jurisdiction, all access to myprog would be denied:

deny ${Env::SCRIPT_NAME} eq "/dacs-cgi-bin/myprog"

We could instead disallow access by any unauthenticated user or by bobo, respectively:

deny ${Env::SCRIPT_NAME} eq "/dacs-cgi-bin/myprog" and user("unauth")
deny ${Env::SCRIPT_NAME} eq "/dacs-cgi-bin/myprog" and user(":bobo")
Note that requests that are not denied by the revocation list may still be denied by a rule.

There is no directive to enable or disable the revocation list. The list must exist, although it can be empty or nothing but comments.

Restricting Administrator Login

The configuration directive ADMIN_IDENTITY is used to specify a DACS identity that has extra privileges and the function dacs_admin() tests if any identity of the current user matches one of these administrative identities.

Starting with release 1.4.16, the revocation list can be used to disable a DACS identity if an expression is True:

disable expression
To restrict authentication of any ADMIN_IDENTITY to a given IP range, for instance, the following line would be added to the revocation list:
disable dacs_admin() and not from("10.0.0.0/24")
In this example, an ADMIN_IDENTITY must authenticate from an IP address in the range 10.0.0.0 through 10.0.0.255.

Tracking User Activity

DACS has an optional feature by which login, logout, and access events can be tracked. Instead of having to comb through log files, a DACS administrator can easily determine when a user has logged in through DACS and the requests the user has made for DACS-wrapped resources. This information can be used to construct an audit trail for a particular identity or session.

Jurisdictional participation is optional, however, so the event logs will be complete only if all jurisdictions are properly configured.

Event records for a user are recorded at the jurisdiction at which the user was authenticated (the "home jurisdiction"). This is true even for access events that take place at other jurisdictions within the federation and requires an access control rule at the home jurisdiction to allow the other jurisdictions (probably using dacs_admin()) to append a record through the home jurisdiction's dacs_vfs(8) web service's put operation. Therefore, all of a given identity's activity throughout his federation is conveniently recorded in one place (instead of being scattered amongst the jurisdictions).

A vfs_uri (see VFS) similar to this one will be dynamically constructed to record an event at the home jurisdiction:

dacs-vfs:https://e1.example.com/cgi-bin/dacs/dacs_vfs
This URI is formed from the sending jurisdiction's metadata for the home jurisdiction.

The event log is an XML document formatted according to dacs_user_info.dtd. Three different record types can be written; their formats should be pretty much self-explanatory. Note that each event's date/time stamp is expressed in Unix's seconds-since-the-epoch format and is relative to the clock of the generating jurisdiction; an appropriate degree of clock synchronization is desirable. The event log at a given jurisdiction can be browsed using dacs_admin(8), with appropriate permission. If the event log is stored in a regular file, the usual text processing tools (e.g., grep(1)) can be useful.

To use this feature, DACS must be built with the --enable-user-info flag. The user_info item type must be configured at each jurisdiction that should record events that occur there. For example, by adding this directive to dacs.conf, event log records will be written to a regular file, /usr/local/dacs/logs/user_info:

VFS "[user_info]file:///usr/local/dacs/logs/user_info"

DACS does not exploit this information yet - several new features could be based on it (for example, an inactivity timeout to complement dacs_signout(8)). There is currently no tool to collect and merge all of a federation's event logs, but this should not be a difficult exercise.

Note that there is no built-in facility to rotate or truncate the event log, so it will continue to grow.

Constraints

The rule that is responsible for granting access to an executable resource can ask DACS to pass an arbitrary string to the program through an environment variable (DACS_CONSTRAINT or DACS_DEFAULT_CONSTRAINT). This mechanism is explained in detail in dacs.acls(5). How you use this capability is entirely up to you because DACS does not do anything with the string other than to pass it along. But it was originally introduced as a way for rules to tell programs that they should enforce additional restrictions on their behaviour, usually fine-grained conditions that are either difficult, inefficient, or impossible for DACS to test for, and that is why they are called constraints.

Let's look at a simple example that uses this rule fragment:

<allow constraint="admin">  user(":bobo")   </allow>
<allow constraint="update"> user(":jj")     </allow>
<allow constraint="read">   user(":auggie") </allow>
When bobo invokes an application protected by the rule, the program will see the value of DACS_CONSTRAINT as "admin". When jj runs it, the value of DACS_CONSTRAINT will be "update", and for auggie it will be set to "read". The program can do what it likes with this information, including ignore it, but presumably whoever wrote the rule knows something about the program and expects it to understand these strings and modify its behaviour accordingly.

In this example the program might implement three operational levels ("admin", "update", and "read"). User auggie might be limited to read-only functionality while bobo can do anything. The application will probably include code like the following to test if its user should be allowed to perform some operation:

  if ($ENV{'DACS_CONSTRAINT'} eq "admin") {
     # Allow all operations...
  }
  elsif ($ENV{'DACS_CONSTRAINT'} eq "update") {
     # Allow most operations...
  }
  else {
     # Allow read-only operations...
  }

Autologin

The idea behind what we call autologin is to provide a way for users to obtain DACS credentials (that is, to sign in, sign on, log in, login, log on, ...) without engaging in a special interaction, such as entering a password. Authentication in this way might be initiated when the user makes an explicit request to login (possibly providing a username), or in a way that is transparent to the user. In any event, something that appears in the context of the user's HTTP request is used to determine the user's identity. The procedure might use the IP address from which her request appears to originate, her SSL client certificate, a component of the request URI, an Authorization header (or some other request header), and so on. Although it can be very convenient for users, this technique is clearly appropriate only in situations where a relaxed degree of security is allowable. See Stateless Authentication for a closer look at one of these methods.

More to appear soon.

Interactive, Indirect Authentication

One of the authentication modules that is provided with dacs_authenticate(8) is local_cas_authenticate, which is primarily intended to work with a Central Authentication Service (CAS) server via the CAS 2.0 Protocol. The DACS side can interoperate with any login service that implements the protocol, however, and it turns out that the login service side of the protocol is quite simple and easy to implement.

Perhaps the main feature of the protocol is that the "relying party" (in this case, DACS) never sees users' passwords. This can be an important consideration when the authority responsible for the accounts being accessed is different from the authority that manages DACS. For instance, one government department might "own" the accounts for its employees and be completely responsible for administration of the login procedure, while a second government agency might operate DACS-wrapped resources that it wants those employees to be able to access; that is, before an employee can access resources on one server, he must identify himself by signing on to his account on some other server. By configuring DACS to use the cas style of authentication, the first agency retains control over the login procedure and its user accounts, but passwords (and any other authentication material) never pass through DACS. For example, once a simple login service is implemented, users might authenticate themselves to DACS, indirectly, by giving their Unix (or Windows) user name and password to the login service.

In a nutshell, here is how this style of authentication is performed. When DACS authentication is required, a user is redirected by DACS to the login page configured for the login service. The user signs on in the usual way. After successfully logging in, using any web-based method, the user is redirected by the login service back to DACS; the redirection includes an argument that identifies the login transaction. DACS performs a server-to-server operation, asking the login service to verify that authentication succeeded, passing the transaction identifier. If the login service issues its own cookie as a result of successful authentication, a subsequent login that is triggered by DACS may not result in the user having to repeat the login procedure at the login service - and DACS credentials can be issued almost transparently.

If you are acquainted with OpenID this procedure may have a familiar feel to it, although the OpenID protocol is considerably more complex.

IIS, etc.

When it comes to providing transparent access control for web-based resources, DACS works best with Apache 2.4 and the mod_auth_dacs module on Unix-type platforms. At one time DACS would build on Windows with Microsoft Visual C/C++, and there used to be a mod_auth_dacs module for Apache 1.3, but neither option is available now. DACS has never been integrated with IIS or any other web server. Another impediment to deploying the preferred configuration at some installations is that using a non-standard Apache module (such as mod_auth_dacs) is not permitted on production servers. Some of these issues are discussed in the FAQ.

If you are in one of these situations and would like to use DACS to protect resources on a web server where you are not able to run DACS, one viable approach may be to add another server box (or a virtual server on an existing box) that can run Apache 2.x/mod_auth_dacs/Unix. This additional server will run Apache 2.x as an HTTP proxy for one or more other web servers (the origin servers), and all DACS authentication and authorization will be performed on it. If this arrangement is a possibility, keep reading.

There are other solutions, but this approach is simplest and does not involve any programming or changes to URLs. This solution is not exactly equivalent to the "native" (non-proxied) one because web services at an origin server do not see a full DACS environment, but in many cases this is not important; when it is important, there are ways to instantiate a suitable environment. Let's look at how this works using a simple example.

Let's assume that our web resources are currently being served by www.example.com, and it is that stuff (or parts of it) that we want to DACS-wrap. Here is the basic idea. We will:

What we are doing is "focussing" all incoming web service requests on a DACS-enabled Apache server that acts as a proxy. Requests for DACS services are performed there, but requests for other DACS-wrapped resources are forwarded to some other server, provided access is granted. The other web servers need to be configured, at whatever level works best for you, such that it is impossible for requests to bypass the proxy server.

To tell Apache what it needs to proxy (and not proxy), you'll likely need to configure its ProxyPass, ProxyPassReverse, and ProxyPassMatch directives. If there is a tricky part, it is getting the origin server to use the right user identity (if the user has authenticated); that is, you probably want the origin server to set REMOTE_USER to whatever the proxying Apache server sees. To accomplish this, you can pass the value in an HTTP request header, then have the origin server extract the value and set the identity (REMOTE_USER). On the proxying side, you might use the Proxy and RequestHeader directives to help with this, and quite possibly the dreaded mod_rewrite module. At an origin server, you may use mod_rewrite, or you may need to write a small ISAPI filter (see this thread for helpful details).

An All-in-One Admin Tool

Over time, DACS has accumulated a collection of web services to display information about its configuration and operation and to make changes. Starting with release 1.4.21, these web-based activities have been consolidated in a single administrative tool called dacs_admin(8). Future development will be directed at dacs_admin, and the older web services will eventually disappear to simplify DACS administration and reduce redundancy.

In release 1.4.21, dacs_admin just provides read-only operations and HTML responses. Aim your browser at a jurisdiction's dacs_admin and browse through that jurisdiction's resources. You will find that performing a GET on a "leaf" resource returns the HTML-encoded value of the resource, unadorned with any other markup so that its value can easily be obtained by a program.

More to appear soon.

Data-Driven Access Control Rules

To grant access to a particular resource, you will typically create a rule for that resource that has an allow element that explicitly lists the identity or identities that should be granted access:

<allow>
  user(":alice") or
  user(":bob") or
  user(":carol") or
  user(":dave");
</allow>
If the list changes, the rule must be modified. For lists that are not relatively static, and especially for long lists, using roles or groups can make it easier to administer the rule. Sometimes roles or groups are not quite the solution you want, however.

Since release 1.4.16, another solution is possible. The list of users can be maintained in a separate file, or in any way accessible using the virtual filestore facility. The list is read into an array at run time (using setvar()). Instead of explicitly listing the users that should be granted (or denied) access, the rule can instruct DACS to examine the elements of the array (using user()).

Let's put the list of users to be granted access to our application in the file /usr/local/dacs/my_app_users, one per line:

:alice
:bob
:carol
:dave
Then, we can change our example rule fragment above:
<allow>
  setvar(load, MY_APP_USERS, "/usr/local/dacs/my_app_users");
  user("namespace MY_APP_USERS");
</allow>
To change the list, we can simply edit the file. Or we can generate a new file using a program that we write, or the list can be retrieved using any storage scheme supported by the virtual filestore, such as HTTP. The rule itself is not changed and the new list takes effect instantly.

Using Dynamic Rules

The service element of a rule specifies the resource (or resources) to which the rule applies. When the resource (or resources) have "static" names, the url_pattern attribute should be used. It is sort of like Apache's Location directive. But when the names depend on run time conditions, the url_expr attribute is used. This attribute gives a DACS administrator a mechanism to create dynamic rules, greatly reducing administrative effort. For details, please see dacs.acls(5).

Consider a typical application where each user is allocated his or her own part of a site's URL space. Let's say that our web site is www.example.com and the user areas on the site are arranged so that every URL under https://www.example.com/u/alice contains the things that belong to Alice. Similarly, https://www.example.com/u/bob contains the things that belong to Bob, and so on for all the other users. For instance, https://www.example.com/u/alice/foo.mp3 might contain an MP3 that belongs to Alice. It doesn't matter to DACS what any of these URLs correspond to; they could be data files, programs, images, blogs, profiles... whatever. By creating just one rule (instead of one rule per user), we can ensure that only Alice can access her things, only Bob can access his things, and so on:

<acl_rule enabled="yes">
  <services>
    <service url_expr='"/u/${DACS::USERNAME:?*NOBODY*}/*"'/>
  </services>

  <rule order="allow,deny">
    <allow>
      user(":${DACS::USERNAME}")
    </allow>
  </rule>
</acl_rule>

This is only a simple example to illustrate the basic concept. Of course, it is not much harder to create public and private per-user areas or other common arrangements.

Time-Based Restricted Access

Granting access to a resource (or resources) for a certain user or set of users according to the date or time of access is straightforward and is usually done through the time() function.

This simple rule limits all access to weekdays for all users:

<acl_rule enabled="yes">
  <services>
    <service url_pattern="/*"/>
  </services>

  <rule order="allow,deny">
    <allow>
      time("wday") gt 0 and time("wday") lt 6
    </allow>
  </rule>
</acl_rule>

To take the time of day into account, just substitute an expression that represents the required condition (e.g., time("hour") ge 9 and time("hour") lt 17). In more complicated cases, such as when different users have different restrictions, the precondition element might be used.

Examining CGI Arguments

Whether CGI arguments have been provided in the URI as a query component (RFC 3986, content type application/x-www-form-urlencoded) or in the body of a request (content type multipart/form-data, such as with the POST method), or both, they can be examined by rules. These arguments are in the Args namespace.

For example, this rule fragment grants access if an argument named foo was passed to the CGI program wrapped by the rule:

<allow>
  ${Args::foo:e}
</allow>
If the foo argument should be treated case insensitively, substitute ${Args::foo:ie}.

This rule fragment grants access if foo was passed and has the value "baz" using a case-sensitive comparison:

<allow>
  ${Args::foo} eq "baz"
</allow>
For additional information, please see dacs_acs(8) and dacs.exprs(5).

Managing Access Based on Knowledge of a URL

Some web sites do not have to have a high level of access control and do not even require user accounts to be created, but they do need to have something to prevent abuse that is easy to administer and simple for users. A well-known example of this type of site is craigslist, which allows people to post classified ads without having to register with the site, and to later perform administrative functions on their ad, such as modifying or deleting it.

When an ad is created, a URL is returned to the ad's owner by email. Anyone who knows the URL can then perform administrative functions on the ad. The URL is relatively concise and can easily be typed or pasted into a browser, but is a bit difficult for the average person to memorize. In essence, the URL contains an embedded password, and therefore security, such as it is, depends on the URL being kept secret and sufficiently hard to guess. Because the implications of an attacker discovering the URL are not dire in this application, it is an entirely appropriate solution.

There are many ways for DACS to support this capability - the most appropriate design choices depend on the overall context of the web site. We will outline one approach. Notably, no user authentication is needed and so no DACS credentials are created. The method relies upon carefully crafting a structured URL that both represents the object (whether it's an ad or something else) and confers administrative permission for the object. For example, a URL like this might be returned to a user when she creates an object:

https://www.example.com/manage/227312552/36qj4
Visiting the URL displays a web page for the object that includes interfaces to administrative functions. Here, the 227312552 component of the URL might uniquely name the object and the 36qj4 portion of the URL acts as an authorization component that might be obtained from a secure keyed hash of the preceding portion of the URL. Assuming that the authorization component is encoded as five case-insensitive alphanumeric characters, the number of different "passwords" exceeds 60 million (36^5), and if the encoding is case sensitive, there are over 916 million (62^5), so correctly guessing an authorization component is highly unlikely. The length of the authorization component can easily be extended if desired (to discourage over-the-shoulder eavesdropping, for instance).

Assuming URLs of this type are used, a dynamic access control rule would deny administrative access to an object unless the request URL includes a valid permission component. The rule would compute the expected authorization component and compare it to the one that appears in the request URL; only an exact match will cause the rule to grant access. Note that the authorization components of these URLs do not need to be stored on the server. Read-only access to the object, which is publicly available, might be provided through a different URL (and therefore a different access control rule), such as:

https://www.example.com/display/227312552

Again, this method does not offer a particularly secure solution because it relies solely on URLs being kept confidential, something that is difficult to do in general - they can typically be found in a browser's history list or server's log file. But for the right applications and when deployed with care, the method can be attractive.

Some sites employ single-use URLs or time-limited URLs, which are a related application of URLs of this general type. The idea is that after the user acknowledges some licensing terms, or pays a fee, or whatever, her browser is redirected to a special URL that lets her retrieve a file or document, run a program, and so on. As with the constructions described earlier, it is highly unlikely that anyone will guess or predict one of these URLs. Additionally, the URL might become invalid immediately after it is used or after a certain period of time - so it becomes worthless quite quickly, making it difficult for a user to share the link. Using its Rlinks feature, DACS makes implementation of these kinds of URLs straightforward.

Incidentally, this general type of solution does not involve security by obscurity. The details of the mechanism and its implementation, which are hardly complex, can be made public without compromising its security. The degree of security primarily rests on the difficulty of guessing or manufacturing a valid URL and can be freely "dialed up" by increasing the size of the URL space. The approach is weak in that if not used carefully these URLs can "leak" and be grabbed by unauthorized users, particularly if they do not have relatively short lifetimes or if a user has no stake in keeping a URL secret. You could attach a password to the URL, or limit its user to a given IP address, but in any event use them with care and only in situations where they offer an appropriate level of security. Sites will often include a legal warning to address improper use or sharing of these kinds of links.

Customizing User Roles

DACS includes methods for obtaining per-user roles using its roles modules or in conjunction with authentication (for example, see local_ldap_authenticate). But sometimes other methods are needed, so we will outline a general-purpose way for DACS to acquire role information.

For example, if we write a CGI program called getroles that is passed a DACS username (${Auth::DACS_USERNAME}, which is relative to the current jurisdiction - in some situations ${Auth::DACS_IDENTITY} might be a better choice) and returns a role descriptor for that user, we can configure the jurisdiction to use a Roles clause that looks something like this (note that this configuration has not been tested with releases prior to 1.4.17):

<Roles id="getroles">
  EXPR  'http("https://example.com/cgi-bin/getroles", GET,
     DACS_USERNAME, ${Auth::DACS_USERNAME})'
</Roles>
It is not necessary for getroles to be DACS-wrapped. In fact, it can be located anyplace that is sufficiently secure.

Clearly the CGI program can retrieve or compute the roles for the specified user in any way it likes, so this configuration gives administrators a way to import DACS roles from any type of database or web service, and through whatever system the CGI program runs on. You can pass whatever arguments you require.

The following script is a trivial example of getroles that returns the same role string for all users (it does not even look at its argument). Still, this example might be useful for testing purposes:

#!/bin/sh

echo ""
echo -n "pitcher,starter,reliever"
exit 0

A more complete program might examine its CGI argument and perform a database query (non-interactively, of course), for instance:

#!/usr/local/bin/perl

use CGI;

$q = new CGI;
print "Content-Type: text/plain\n\n";
$username = $q->param("DACS_USERNAME");

# Here we would find the roles for $username (e.g., using a MySQL query)
# and emit them in the required syntax

# But for now, just replicate the previous example
print "pitcher,starter,reliever\n";   

exit(0);

Your program can (and should) be tested from a browser or dacshttp(1) using a URL like so:

% dacshttp 'https://example.com/cgi-bin/getroles?DACS_USERNAME=bobo'

Checking for Authorization, Part 1

DACS has a feature that allows you to call it as a web service to ask it whether it would grant or deny access to a DACS-wrapped request without the web server actually performing the request. You do this by sending the request as usual but including a special argument named DACS_ACS. When dacs_acs(8) receives the request, it performs an authorization check and then returns immediately with a response that tells you what the result of the check was. A similar capability is provided by the dacscheck(1) command, but we will not talk about that now.

Any program that is capable of making an HTTP request may ask for an authorization check - no DACS software is needed on the client side. A C/C++ program might use neon or fetch(3), for instance. Script-based programs might use lynx, fetch(1), Wget, or dacshttp(1). It can be used by JavaScript and, of course, you can try it from a browser. The response is simple and easily parsed. The DACS-enabled system that does the authorization checking can be located anywhere, as long as your clients can connect to it. Multiple clients can share the same rules - a change to the rules is instantly and simultaneously effective for all of the clients without having to do anything on the client side.

As always, because this feature deals only with URLs, it does not matter what kind of resource is being requested. The resource can be anything and it does not need to be modified in any way for these checks to work. In fact, the resource specified by the URL does not even need to exist, which means that the feature can be used to construct rules for abstract objects.

Let's use some simple examples to demonstrate how this works:

% dacshttp 'https://dacs.dss.ca/cgi-bin/dacs/dacs_prenv?DACS_ACS=-check_only'
797 Access denied
% dacshttp 'https://dacs.dss.ca/cgi-bin/dacs/dacs_version?DACS_ACS=-check_only'
798 Access granted
% dacshttp 'https://dacs.dss.ca/secure/index.html?DACS_ACS=-check_only'
797 Access denied
% dacshttp 'https://dacs.dss.ca/secure/some_app?DACS_ACS=-check_only&OP=read'
798 Access granted
% dacshttp 'https://dacs.dss.ca/secure/some_app?DACS_ACS=-check_only&OP=update'
797 Access denied
The default rule governing dacs_prenv denies access to an unauthenticated request, but all requests for dacs_version are allowed. Note that neither program is actually executed. The URL in the third example just shows that the resource can just as well be a web page or file. The rule responsible for the fourth and fifth URLs looks like this:
<acl_rule enabled="yes">
  <services>
    <service url_pattern="/secure/some_app"/>
  </services>

  <rule order="allow,deny">
    <deny> (not ${Args::OP:e}) or ${Args::OP} eq "update" </deny>
    <allow> ${Args::OP} eq "read" </allow>
  </rule>
</acl_rule>
There is no object on the web server corresponding to /secure/some_app - if you remove the DACS_ACS argument in the fourth example and try that URL, you will get a 404 error. If no OP argument is present, or if its value is "update", access is denied. If OP is "read", access is granted. If OP has some other value, the default is to deny access. In any of these requests, DACS credentials can be sent to identify the user on whose behalf we are making the authorization check.

This feature can be useful in at least two situations. Applications and middleware that need to customize their appearance or behaviour based on what their user can and cannot do, or depending on their user's preferences, can benefit from this feature. An application might consult DACS to find out which of its operations are accessible to the current user; those that are not accessible might be excluded from a menu or not be selectable. Or, an application might ask whether an option has been enabled in a user's profile - in this case we are using DACS for its rule-processing abilities rather than for access control as such. For example, you could create custom RSS feeds for authenticated users by constructing rules that compare a user's roles with the roles assigned to each feed, selecting only those feeds that match a user's configured preferences.

The feature can also be employed by applications and middleware that want to delegate their authorization checking to DACS instead of implementing it themselves. There are many advantages to rule-based decision making over explicitly coded implementations. It could be used by applications that need to control access to physical devices (web cams, printers, electronic locks), other programs, files, or anything else you can think of.

For additional details and examples, please see Rule-based access control: Improve security and make programming easier with an authorization framework.

Checking for Authorization, Part 2

What if you cannot add the mod_auth_dacs module to Apache, you are not even using Apache, or you are looking for a more simple solution? You may still be able to leverage DACS to perform powerful, rule-driven authorization tests by using dacscheck(1). Any CGI-based application written in just about any language should be able to run dacscheck, a general-purpose program for authorization checking.

Through command line arguments, you can ask dacscheck questions of the form:

Can Identity
Do Action
To Thing
For instance:
Can bob@example.com
Do GET
To /my_photos/me.jpg
Or:
Can anonymous
Do POST
To /calendar_app?NAME=Bobo&DATE=1-Apr-08&DATA=xyz

dacscheck searches through rules that you have provided, looking for a match against the Thing component of the question. It answers with one of three possibilities: yes, no, or error.

Although there are some syntactical constraints, dacscheck is not concerned with the meaning of the questions it is asked. It does not know or care what Identity is, whether it exists, or how it was obtained. So you can just as easily ask the question:

Can bob@example.com
Do send-email
To alice@example.com
User bob@example.com (or his email agent) might use such a query to determine whether alice@example.com will accept messages that he sends, and alice@example.com (or her email server or agent) might use this query to decide whether to accept or reject messages from bob@example.com.

There's more about dacscheck in the FAQ, in "Rule-based access control", and with DACS tools, which lists the many advantages of rule-based authorization testing.

Stateless Authentication

Starting with release 1.4.18, DACS included new and powerful capabilities for performing authentication at authorization checking time. In previous releases, users typically had to engage in a separate authentication step (using dacs_authenticate(8), for instance, whether interactively or non-interactively) to identify themselves and obtain credentials. Amongst other important benefits, this alternative mechanism allows DACS to work with user agents incapable of handling redirection and in situations where HTTP cookies cannot be used to convey DACS credentials.

This mechanism is stateless because an unauthenticated user's service request can include arbitrary information (in the URL's path component, or in an argument either visible or invisible to the invoked resource) that you can configure DACS to use to create credentials that normally exist only for the duration of the service request. No credentials are returned to the user, so the single sign-on capability is lost to users that authenticate in this manner. In exchange for the considerable degree of flexibility in associating an identity with a request, visibility of the identity or authentication information means that it may be easily copied, and possibly reused, by an attacker. So this mechanism should be used with care, and probably in situations where security is not a major concern.

This feature is available through two mechanisms: the first leverages HTTP Basic and Digest authentication (RFC 2617) to optionally prompt a user for a username and password (HTTP_AUTH with the -pre flag), and the second obtains a username by expression evaluation (ACS_PRE_AUTH). The latter mechanism provides a way to establish a username and roles by inspecting the request, its arguments, and whatever other context is needed. For example, a username and password could be provided as arguments to a web service and validated by an expression.

Here is a simple example to demonstrate ACS_PRE_AUTH. We will use an HTTP extension header of our own choosing to associate a DACS username with a request. We configure DACS to look for the username and use it if it is present:

ACS_PRE_AUTH '${Env::HTTP_DACS_USERNAME:e}'
Then we can try it with a command like this:
% dacshttp -v -header DACS_USERNAME bobo 'http://example.com/cgi-bin/dacs/dacs_prenv'
Try this yourself. Revise the URL appropriately for your jurisdiction; if you use dacs_prenv, change its rule in /usr/local/dacs/acls/acl-prenv.0 to grant access. Examine the output of dacs_prenv and note that DACS_IDENTITY etc. have been set. Also note that DACS has not returned a cookie.

If you want to assign roles to the user, you can do so by setting ${Auth::CURRENT_ROLES} in the ACS_PRE_AUTH expression. You could use another HTTP header for specifying the roles.

The resulting DACS identity is relative to the jurisdiction that receives the request. The identity does not have to exist in any sense - it need not correspond to any account - it is simply created. Obviously anyone who understands the method used in this example could assume any identity he chooses at the jurisdiction, so either the client should be restricted (e.g., by checking the IP address or SSL client certificate), or it should be used in such a way that there are no security implications (e.g., by restricting the specified identity to something harmless).

If you understand the example above, you probably realize that the same general idea can work with an identity embedded in a URL or assigned to an argument. The special DACS_ACS argument can be used to make such an argument invisible. It is only a matter of configuring ACS_PRE_AUTH to locate the identity and applying any mapping that is necessary, and making the corresponding changes to the requests. An encrypted or encoded username could be used with a little additional effort. For instance, your site might employ a URL like this:

https://www.example.com/manage/227312552/36qj4
where 227312552 is a random identifier assigned to the user (making it difficult to guess a valid one) that is looked up in a database to get the corresponding username.

If you are so inclined, you can add a level of security by including a password, or its hash, as an argument and comparing it to a stored value. This brings us to the other mechanism for authorization-time authentication, which uses the HTTP_AUTH directive with the -pre flag. This lets you leverage the widely-available Basic Auth (RFC 2617) authentication mechanism in conjunction with any suitable DACS authentication module. For example, this might cause a user to be prompted for a username and password which can be validated against a Unix or Windows password, or a password file that is private to DACS.

Here is a very basic (pun intended) example. Just configure DACS like this:

EVAL ${Conf::http_auth_401} = "yes"
HTTP_AUTH_ENABLE "yes"
HTTP_AUTH "basic 'Unix Password Required' /cgi-bin/dacs/dacs_prenv -pre -m unix suff"
Someone who now requests /cgi-bin/dacs/dacs_prenv relative to this jurisdiction will be prompted for a username and password (unless they have already been provided), which will be validated against Unix passwords. If you try this, you will need to change /usr/local/dacs/acls/acl-prenv.0 to grant access. Also, dacs_acs will have to be setuid root (or possibly setgid root) so that it can access the system passwords. If you examine the output of dacs_prenv you will see that DACS_IDENTITY etc. have been set. Also note that DACS has not returned a cookie. It is a simple matter to change this to use some other authentication method instead.

Accounts With Limited Lifetimes

It is sometimes necessary to create an account that can be used only one time, or used until a specified date. With release 1.4.18, these sorts of things are easy to do using the AUTH_SUCCESS directive or the corresponding on_success() function. A DACS administrator can arrange for the account to automatically be deleted under certain conditions instead of having to remember to perform the administrative task at some later date.

To create a password-protected account that can be used one time by dacs_authenticate(8), create the account using dacspasswd(1) in the usual way. Then configure AUTH_SUCCESS to call vfs() to delete the account, using an expression something like this:

AUTH_SUCCESS 'vfs(delete, passwds, ${Auth::CURRENT_USERNAME})'
After successful authentication, the account will be permanently deleted, assuming dacs_authenticate is permitted to do so. The credentials given to the user will continue to exist until they are deleted or expire, but the user will not be able to authenticate as that identity again.

Removing the account after a certain date is more complicated because you will be off-by-one if you use the AUTH_SUCCESS directive to do it. The time() function can be employed to test the current date. In the following example, if the account name begins with the string guest- and it is July 1 or later, the account is deleted the first time the user tries to login using dacs_authenticate:

<Auth>
STYLE expr
CONTROL required
PREDICATE '(regmatch(${Auth::CURRENT_USERNAME}, "^guest-.*") and time(month) ge 6) \
    ? (vfs(delete, passwds, ${Auth::CURRENT_USERNAME}), 0) : 1'
EXPR '${Auth::CURRENT_USERNAME}'
</Auth>
This additional Auth clause must appear after the a clause that identifies the user so that ${Auth::CURRENT_USERNAME} has been set. This solution might be considered buggy because if the user never logs in, the account will never be deleted. A better approach might be to use dacssched(1), have cron(8) run a script that uses dacsexpr(1), or construct a solution that uses counters.

Persistent Counters

Authorization tests often involve restricting access based on how many times something is allowed to happen. Like what? Like constructing accounts that can be used just once, or granting access to a particular resource only once, or limiting a given user's access to a resource to a certain number of times (or a certain number during some time interval), and so on. It is also handy to be able to count how many times an event has occurred.

Starting with release 1.4.18, DACS includes operations on persistent integer counters to make it easier to express these kinds of requirements. Counters are accessed through the virtual filestore subsystem, so there is considerable flexibility in where and how they are stored. Counters are managed through the counter() function.

Here is a simple example to demonstrate how you might use counters. First, let's tell DACS how and where to store the counters by adding a directive like this to dacs.conf:

VFS "[login_counters]dacs-kwv-fs:/usr/local/dacs/logs/login_counters"
Next, every time there is a successful login via dacs_authenticate we will increment a counter that corresponds to the user's DACS username:
AUTH_SUCCESS 'if (counter(exists, login_counters, "${Auth::CURRENT_USERNAME}"))
   { \
     counter(inc, login_counters, "${Auth::CURRENT_USERNAME}"); \
   } \
   else { \
     counter(set, login_counters, "${Auth::CURRENT_USERNAME}", 1); \
   }'
These usernames cannot contain a space or other character that would be invalid in a counter name, so we do not have to worry about encoding them. Release 1.4.19 added a feature to simplify this expression a little.

After a few logins we can look at our counters, which might look like this:

% cat /usr/local/dacs/logs/login_counters
booboo 4
bobo 1
julia 9
We could also use (and with some storage schemes, must use) the dacsvfs(1) command to see the counters.

Now that we know how to count logins, we can easily test a counter value in the revocation list or during authentication to limit access. Instead of counting logins, we can use the ACS_SUCCESS directive to count the number of times a resource has been successfully accessed by a user and use that information to restrict future access. Or, we can maintain customized real-time hit counts from which we can create nifty reports.

You may have across one of the many web sites that offer a service whereby you give them a URL (often a lengthy one, possibly with query arguments) and they provide you with an equivalent but much shorter link. You make the shorter link public - you can embed it in a web page, set a bookmark for it, or email it to friends - and anyone accessing the shorter link will be automatically redirected by the service to the target link that you configured. This service is useful because long links can be particularly difficult to copy and cut-and-paste correctly. Some services let you replace the target link and some let you see a hit count for your link.

For example, you would enter a URL like this at their site:

http://example.com/a/very/long/link/with/args?a=1&b=2&c=3&d=4&e=5&f=and_so_on
and their site would produce a new link for you that might look something like this:
http://url.dacs.dss.ca/70474220
It is this new link that you send to people, embed in a web page, or whatever.

Release 1.4.18 lets you configure DACS to get a feature-rich version of this capability so that you can create and manage your own short links. You could use it to create a URL redirection service like one of the existing ones, or as a powerful tool just within your own web site. Configuring this capability on your own DACS-enabled web site means that your short, public URLs are not bouncing off of someone else's web server, where they could potentially be analysed to see who is using them, stored (are there any passwords or account names embedded in those URLs?), or used in some other way that you should be concerned about. Also, you and users of your link are not forced to see an ad. And you can use SSL/TLS, or not, with your DACS URLs, as you choose.

Not only can the target link be changed at any time (which means you can create a permalink - a "permanent" link - as far as users of that link are concerned), but the mapping between the public and target links can be "smart" in the sense that the latter can be programmed - contextual information can be used to make the public link behave differently under different conditions. The public link could map to different places depending on the time or date, the identity of the person using the link, whether a potential target is inaccessible, or just about anything else you can think up.

This capability can be used to track or redirect QR codes, since a barcode can encode a URL.

You may wonder what this feature has to do with access control, and you would sort of have a point. But an example will illustrate how DACS turns the problem into an authorization processing activity. Basically, we will look at this class of problem as handling a request for a resource that will always be denied. DACS will say, "sorry, you can't access that URL but try this one instead". We will compute an alternate URL and redirect the client to that target URL. The target URL can be anything (it doe not have to be DACS-wrapped), but the GET method will always be used. And as we have said before, the request URL does not have to name a "real" resource, it must only be DACS-wrapped. The key is the redirect() function with the BY_SIMPLE_REDIRECT argument; its documentation includes an example.

Here is the simplest possible example to illustrate the basic idea:

<acl_rule enabled="yes">
  <services>
    <service url_pattern='/private/foo.html'/>
  </services>

  <rule order="allow,deny">
    <deny>
      redirect(BY_SIMPLE_REDIRECT, "http://example.com/foo.html")
    </deny>
  </rule>
</acl_rule>
Assuming the URL path /private/foo.html is DACS-wrapped, anyone trying to access it will be redirected by this rule to the target URL http://example.com/foo.html. Note that, as always, the DACS-wrapped URL does not need to actually exist - it does not need to name a "real" resource. Since the target URL can be computed in the deny clause before redirect() is called, you are free to send the client wherever you want. By inspecting the request's URL components or arguments, depending on the identity of the user making the request, by doing a database lookup, using any other contextual information (like the time of day or the date) that is available, or by calling an external program or web service, you can construct the target URL. The target URL can have query arguments, of course.

In case this idea is not obvious, here is another very simple example. This time we will only show a rule fragment:

<deny>
  if (time(wday) eq 4) {
    redirect(BY_SIMPLE_REDIRECT,
          "http://example.com/cgi-bin/dacs/dacs_list_jurisdictions?FORMAT=XML")
  }
  else {
    redirect(BY_SIMPLE_REDIRECT,
          "http://example.com/cgi-bin/dacs/dacs_version")
  }
</deny>
This rule will redirect all users accessing the resource to one URL on a Thursday and a different URL on any other day.

Here is an example that is a little more general. Let's say that we want to create short links that look like http://www.example.com/slinks/ident, where ident will be a randomly generated string from a "safe" set of characters and is sufficiently long so that it is highly unlikely that any ident will ever be generated more than once. For example, we could get them like this:

% dacsexpr -e 'random(string, 12, "a-zA-Z0-9")'
"VXydsDRALmrV"
We need to write a generic rule to recognize one of these short links, perform a lookup to find out what it maps to, and then redirect the client there. Here is one possibility:
<acl_rule enabled="yes">
  <services>
    <service url_pattern='/slinks/*'/>
  </services>

 <rule order="allow,deny">

   <deny>
     if (setvar(split, "X", ${Env::REQUEST_URI}, "/") eq 3) {
       // Extract the ident component of the URI's path
       ${x} = var(get, X, ${X::#} - 1);

       // Find out what it maps to
       ${t} = vfs(get, "dacs-kwv-fs:/usr/local/dacs/slinks", ${x});

       // Deny access and redirect the client
       redirect(BY_SIMPLE_REDIRECT, ${t})
     }
   </deny>

 </rule>
</acl_rule>
After DACS-wrapping this portion of the server's URL space in httpd.conf with something like this:
AddDACSAuth dacs-acs /usr/local/dacs/bin/dacs_acs

<Location /slinks>
  AuthType DACS
  AuthDACS dacs-acs
  Require valid-user
</Location>
and installing our rule with the custom rules for our jurisdiction, the only thing left to do is to maintain our mappings, which in this example are being kept in /usr/local/dacs/slinks as keyword/value pairs:
hGkVOhVho5Wr:http://dacs.dss.ca
Qdo7iFzIXJIv:https://dacs.dss.ca/cgi-bin/dacs/dacs_version?FORMAT=HTML
(Everything to the left of the first colon is the ident, everything following the first colon is the corresponding URL). If http://example.com/slinks/hGkVOhVho5Wr is fielded by our jurisdiction, clients will be redirected to https://dacs.dss.ca. If a referenced ident has no mapping or if the request URL is badly formed, access will be denied. All we need to do is write a simple command or CGI program to add, delete, or replace mappings - we can use dacsexpr(1) to do much of the work. We do not need to change the rule or add new rules, or restart Apache after making changes to the mappings.

Hopefully it is now clear how you can use this feature to replace long, complicated links with short ones, create permanent links that can point to different places on your web site as you reorganize resources, or make simple URLs smart by dynamically mapping them to wherever you want. You can apply this feature even if you have no interest in DACS authentication or single sign-on. Just configure a simple one jurisdiction federation and start playing.

With release 1.4.19, dacscheck(1) could emit the redirect URL, so it is possible to write a script to implement these links that can be used with any web server and without requiring the mod_auth_dacs module.

The Rlink (rule link) is a powerful feature of DACS that is used by administrators and programmers to do things like:

In the normal case, a web service request for a DACS-wrapped resource is fielded by Apache and delegated to DACS (specifically, dacs_acs(8)) for authorization checking, which examines the request and the configuration directives that apply to it, selects the access control rule that matches best, and then processes the rule to determine whether access should be granted or denied. Then it tells Apache how to handle the request.

The Rlink mechanism implements a slightly different authorization-checking control flow: the only difference from the normal method is that the rule selection step is preceded by evaluation of RLINK directives, which are configurable hooks that may compute a specific rule to use. If the request is determined to be an Rlink, there is no searching for the rule to use.

The most important characteristic of an Rlink is that it lets an HTTP request itself name the access control rule to use. This may seem like a dangerous idea, although in practice it is not, if used with care in appropriate situations. But you should probably keep in mind that it is inherently less secure than the normal method.

An Rlink is an ordinary DACS-wrapped URL that usually includes an Rname (rule name). Exactly how it is included is up to you, the DACS administrator, but it might be a query argument or it might be embedded in the URL's path (this idea was discussed previously). The dacsrlink(1) command can help to make an Rlink or an Rname. (Incidentally, it can also be used to create simple access control rules.)

It is the job of the RLINK directive to map a request to a particular rule (or not, in which case normal processing occurs; you do not need to use Rlinks for everything, only for those resources that you specifically configure). Because RLINK specifies an expression that is evaluated at run time, it can examine any aspect of an incoming request that it wants to extract an Rname. An Rname can be embedded in the request URI, be sent in an HTTP cookie or other HTTP header, and so on.

You can construct different Rlinks for the same resource, and then give each one to a different person. Each Rlink might point to a different rule - so each can have different access control specifications associated with it. You can modify or delete one of these rules at any time. Additionally, you can attach a DACS identity to an Rlink, directly or indirectly, so that a rule can tell who is making the request. Two or more rlinks can point to the same rule but have different identities attached. Since they are just URLs, you can embed them in a web page, email them, save them as bookmarks, and so on.

Note that several bugs in the version 1.4.18 dacsrlink(1) command were fixed in the next release.

One application of Rlinks is for a self-enrollment (registration) feature for DACS accounts. We sometimes would like a user to be able to create his own account but require at least some measure of confidence in his identity and some degree of security. This capability may be appropriate for sites that are open to everyone and do not need to know exactly who these users are, but still do not want truly anonymous users, It is also useful for intranet or other constrained use, where a site does not need to be concerned about abuse because users have already been screened in some way (e.g., users must be employees or must access the site from certain IP addresses).

Let's outline how this can be done. The user would begin by visiting a "sign up" web page where he is prompted for an email address and a new password. When the user submits the form, a message is sent to the given email address. The message contains a link and instructions. If the user invokes the link in the message, he would be directed to a web page that prompts him to re-enter the password and which contains the Rname of the Rlink that was created at the same time the message was sent. This second form is submitted as an Rlink - if the rule grants access, a new account is created and assigned the password given by the user. If this procedure goes smoothly, we have some confidence that whoever this user might be, we know 1) an email address with which he is associated and 2) that someone associated with that email address knows the sign up password and has claimed the new password-protected account. Only someone who knows the secret Rname generated by DACS and the initial password, and is able to receive email sent to the specified address can claim the new account.

This scheme or something similar is used by many web sites, so there's no innovation here, although it may offer somewhat more overall security because it would be deployed as part of coherent system rather than a special-purpose component. DACS hides much of the detail to make this sign-up mechanism more accessible, and because the final decision of whether to create the account is made by a DACS access control rule, you can express custom requirements.

Here is some additional detail. To enroll, a user might be instructed to visit a web page (say, create_account.html) where he enters an email address and a password. The username for the new account might be the email address, the mailbox part of the email address, or might be prompted for separately. We want the new account name to be unique and syntactically valid; dacspasswd(1) will do that. The password should probably be re-entered in a separate field for confirmation. The form is submitted to account_create, where it is validated. Anti-spam measures might be taken. If all goes well, dacsrlink(1) is used to create an Rlink, which might look something like this:

<acl_rule enabled="yes" name="8D8m5CTj" expires_expr='time(now) ge 1181698376'>
<services>
<service url_pattern="/cgi-bin/dacs/account_finalize"/>
</services>

<rule order="allow,deny">
<allow>
password(check, ${Args::PASSWORD}, "2|XYZZYlfe7G8Jj1mK7CHnT|XxE3nqRZm7dR9mtubIAcD1B1ur4")
</allow>
</rule>
</acl_rule>
The Rname here is 8D8m5CTj. This Rlink expires 8 hours after its creation, which is expressed as an absolute time in the rule. The link in the message sent to the user includes the Rname and state variables as query arguments, and points to the program account_confirm. The account_confirm program emits a form that contains the Rlink and prompts the user to repeat the password. This form, which is submitted to account_finalize and is only executed if the Rlink grants access, creates the account and deletes the Rlink. Whether the Rlink expires or is deleted, it can never be used again. If all goes well, the user can proceed to sign in with his new username and password.

Release 1.4.19 included some new features to make it easy to build your own enrollment capability. A prototype has been written to demonstrate this capability; contact us if you would like a copy. If there is sufficient interest, the prototype may be distributed with a future release or the feature may be incorporated into DACS.

Another capability frequently needed by web sites is making a file (for instance) available only to people who have paid a fee or satisfied some other requirement. The apparently tricky part is that once a person has been given "the" URL for the file, what is to prevent him from sharing that URL so that anyone can access the file? There are many ways to implement this, but Rlinks offer a simple solution. And it is not necessary to obfuscate the name of the file.

Of course, once someone has obtained a copy of the file he might redistribute it himself, but that is a different (and much more difficult) problem.

Let's say that want to limit access to http://example.com/foo.html. First, we make sure that the normal DACS access control rules deny all access to the file. Next, whenever we want to grant access to foo.html to an individual, we run dacsrlink(1) (probably in a script, but possibly manually) to create an Rlink. We give the Rlink to the user, perhaps by embedding it on a web page, or by email. The Rlink that can be used with any web browser to access the file might look like this:

http://example.com/foo.html?RNAME=Fszrky94lt
In the simplest case, the rule associated with this Rlink would look like:
<acl_rule enabled="yes" name="Fszrky94lt">
  <services>
    <service url_pattern="/foo.html"/>
  </services>

  <rule order="allow,deny">
   <allow>
   </allow>
  </rule>
</acl_rule>
And that's all there is to it! No fancy file renaming or URL rewriting is necessary. The only thing remaining is to delete the rule, which we might choose to do as soon as the file is successfully accessed or after a given period of time. Then this Rlink will no longer work - of course, other Rlinks to the same file with different Rnames will continue to work. The rule can easily be customized to require a password argument, user identity or group membership, restrict the IP address of the user, log a successful access, and so on. If we wanted to let the Rlink above control access to a set of files, we might change the selection part of the rule to look like:
  <services>
    <service url_pattern="/foo.html"/>
    <service url_pattern="/bar.html"/>
    <service url_pattern="/baz.html"/>
    <service url_pattern="/foo-app/*"/>
    ...
  </services>
Any of those files can then be accessed by attaching the RNAME=Fszrky94lt argument to their URL.

Extending Single Sign-On to Applications

Let's say that you have a web-based application that does its own user authentication. It may have its own user accounts or it may depend on some other system's accounts and authentication mechanism (such as Unix or Windows login names and passwords).

Let's also say that you install DACS, perhaps for reasons that have nothing to do with this particular application. You quickly notice that your users have to login once through DACS, then once again if they need to access the application. This seems kind of dumb to you and your users - isn't it possible for them to login just once?

If you are able to modify the application or put a suitable wrapper around it, or if it has the necessary hooks, the answer is most likely "yes".

Since the application is DACS-wrapped, it can examine some environment variables to determine the identity of its user: DACS_IDENTITY (equivalently, REMOTE_USER), DACS_CONCISE_IDENTITY, DACS_USERNAME, and DACS_ROLES (see dacs_acs(8) for details).

Recall that when a user is authenticated by DACS, secure credentials representing a DACS identity are passed back to the user's web browser (or other user agent, or perhaps to middleware sitting between a user agent and DACS). DACS credentials are encrypted, so software outside of DACS cannot inspect them itself. If an application or middleware holds a cookie representing DACS credentials, it can make an HTTP (preferably, https) request to dacs_current_credentials(8), at any jurisdiction within the federation where the user was authenticated to determine the validity of the credentials, the identity represented by the credentials, and other information. It sends the cookie in the usual way, using the HTTP COOKIE request header. If the FORMAT parameter is used to request an XML reply, a document conforming to dacs_current_credentials.dtd will be returned. Note that the name of the cookie should not be relied upon for identity purposes unless the cookie value has been validated.

If the credentials are found to be valid and the identity is satisfactory to the application, it may decide that a second user login is unnecessary. If the credentials are invalid (maybe they expired or were tampered with), it might demand that the user login, either through itself or via DACS.

The basic idea can be extended in various ways. For example, the application could issue its own cookie to avoid future checks or to help enforce its own security policies with respect to authentication.

For security reasons, DACS cookies (available via HTTP_COOKIE or DACS_COOKIE) are not ordinarily accessible to a web service - the access control rule for the web service must explicitly enable this. The situation can also be a little more complicated. There could be multiple DACS (or other) cookies for the application to deal with. And because DACS tries to keep its cookies private to prevent theft of credentials, you may need to carefully adjust your DACS configuration to get this method to work securely (for instance, the COOKIE_PATH might need to be adjusted, and VERIFY_UA may need to be turned off).

Using Multiple Password Files

It is still not clear whether this capability is needed or even if it is a good idea. It appears to introduce more problems than it solves...

$Id: $