ARPA2 Common Libraries  2.6.2
Actor Identities after Access Control

Authentication mechanisms output a visitor's Remote Identity, which is a user identity defined under a domain. Actors can be added to a Remote Identity to map it onto a local userid, as defined by a service domain. Actor Identities may depend on the scene being played, that is, Access Control can grant the privilege when acting like a local identity as provided during Access Control.

Acting and scenes are mere metaphors, but they are a reasonable fit to the technical facility of pulling a Remote Identity into a domain and treating it like an internal Identity. This has several uses:

  • Aliases, such as for email relaying, can use the Actor Identity and therefore look like they came from the forwarding domain; this is helpful with DKIM and with SPF because replies work symmetrically when the reverse mapping from the Actor Identity to the original Remote Identity is preserved.
  • Groups allow their members to conceal their Identity, which is both a way to enable private posting (with abuse tracing still possible) and to sort traffic as belonging to the group. Groups can share all sorts of resources; Document Access needs to find the Actor just like Communication Access. The special thing about a group is that it defines its own scene, and assigns member addresses that fall under the general idea of an Actor. Other members are addressed by their Actor name in the same scene, which sets them aside as group peers.

The general idea is that Access Control delivers an Actor when it grants permission. An empty form of Actor Identity is defined to say this is not really an Actor so that it can be recognised in handling routines, which may then publish the Remote Identity instead. When an Actor is defined, then the challenge to a service is to conceal the Remote Identity from anyone but the administrator, and substitute the Actor Identity in its place. For example, email would alter addresses in headers and envelope and many other protocols would do the same. Web-based documents would be listed for the Actor Identity if available, not for the Remote Identity.

When we say "Actor" we actually mean "Actor Identity".

Push and Pull Services

Whether the translation from Remote Identity can be unidirectional depends on whether it is a Push or Pull Service.

  • Pull Services are approached from the outside, requests are submitted and a response is pulled out. Although information may flow in either direction, the service holds on to data until it is pulled out by the outside. Since the Remote Identity will always be presented along with requests, there is only a need to map those to an Actor Identity. RESTful websites are typical Pull Services, but so are IMAP and POP3.
  • Push Services take the initiative to connect to the outside, and push requests to others. Information may flow in either direction, but the Remote Identity must now be presented as the party we are talking to; for this to work, any contact to an Actor Identity must be translated and so the reverse lookup is also needed.

When we say "Push Service" we usually mean "Push-and-Pull Service", with an exception when we explicitly speak of a "Push-Only Service".

Static and Dynamic Actors

On the stage, there are two kinds of Actor, namely those cast to a named role, and those who play an unidentified role in the background. In terms of the ARPA2 Identity model, a similar distinction is made.

  • Static Actors are assigned a fixed name in a "scene", so under a name that serves as the scope for Actors. The names look like <scene>+<actor>@<domain> where the <domain> matches the domain that sets the scene (runs the service) and the <scene> is a userid (not a servicename) with possible aliases. The single <actor> word cannot contain + or @ symbols. Note that the use of + in the <scene> is formally approved, but likely to cause confusion.
  • Dynamic Actors are assigned a name in passing, which looks like a random code although it actually is a derived hash. An expiration may be added to the assignment, for instance in 30 days. This kind of Identity relies on the Signature mechanism, so it looks like <scene>+<signature>+@<domain> where the + before the @ is telling for this construct. Access Control may come up with a model for the <signature> bit, notably including the signature flags and perhaps an expiration day, but it usually defers the creation of the cryptographic part to the caller. The signature flags should include remote domain and remote localpart, so it can derive a consistent Dynamic Actor Identity, which suffices for their use with Pull Services. When the mapping from Actor to Remote Identity is additionally stored, then it will also be possible to use Push Services to contact this identity.

An example of this distinction is clear for group handling. Subscribed members would be allocated a member name, and their identity would be of the <group>+<member>@<domain> form. Guest posting is possible through Dynamic Actors. Whether the reverse mapping is stored determines whether a Pull Service can send responses to the guest; without this stored information, the guest may still be granted access to any group resources, but only through Pull Services.

Parsed Actors

The data structure a2act_t represents Actors, much like a2id_t represents a normal ARPA2 Identity. There are a few differences to note:

  • When no Actor is available, the txt part is an empty string. This can be tested quickly, and allows API functions to skip this potentially added information and fall back on the Remote Identity. This special form is called an empty Actor.
  • Static Actors have the <scene> in the userid part, even when this includes + symbols; this would not be possible with an Identity, where it would always end up in the aliases part. Be careful when mixing Actors and Identities because they are parsed differently; this is not in the txt part but in the ofs array. API calls are also clear on this distinction. The <actor> is stored in the aliases part, prefixed with the last + sign (which is required). The <domain> is where it always is, prefixed with the @ sign, which is also required.
  • Dynamic Actors have the <scene> in the userid part, and the same remarks apply as for Static Actors. There are no aliases. The <signature> is parsed as any Signature would be, and the <domain> is where it always is. Signatures may initially be just a recipe, listing flags and possibly an expiration value before going through a signature computation.
  • Note that an Actor never comes alone; it always pairs up with the real Remote Identity, to allow services to process the underlying address, for instance for trace logs, to generate or verify signatures or just to ignore when the Actor is empty.

Groups make a Scene

The idea of an ARPA2 Group is bold, but incredibly useful: every member of a group is given an address within the group, and it looks like <group>+<member>@<domain> so that routing always passes through the <domain> that defines the <group>, and so that the <member> name is local to the group. Actual communication needs to translate from/to an actual delivery address for the addressed <member> names. Clever forms of syntax have been devised to address one, multiple or all-but certain <member> names.

Upon arrival of communication for a group, the sender becomes an Actor, and by doing this starts to look like its member name. When passing out traffic to a group, the addressed group members translate to a delivery address, often including the one that the sender originally used.

The <group> is an example of a <scene>. The customary use of a <member> is for subscribing members but, if supported, visitors may obtain a dynamic name and continue. Whether this is permitted is up to Access Control for the <group>@<domain>.

Going NAT

NAT is terrible. It disables peer-to-peer freedom, and as long as we consider IPv4 worth supporting it means that we need to use intermediate servers for all we do. And that is not always in the interest of privacy (and if you don't care about yours, you may be dragging down others with your carelessness). Clearly, for the Internet Protocol the addition of NAT blocks progress.

The problems with NAT are mostly due to limited port spaces, and the lack of clarity about bidirectional translation, and support for end-to-end connections. For the much larger address space of Actors, this need not be a problem. The Static Actor may be considered equivalent to port forwarding, which works well in every situation. The Dynamic Actor may use a timeout if this is desired for policy reasons, such as storage restrictions for Push Services.

So on the whole, the problems with NAT for IPv4 do not seem to reproduce when we translate ARPA2 Identities to Actors.

API for Actors

A few support functions are defined to work with a2act_t structures:

#include <arpa2/identity.h>
bool a2act_parse (a2act_t *out, const char *in, unsigned inlen);
bool a2act_isempty (const a2act_t *tested);
bool a2act_isstatic (const a2act_t *tested);
bool a2act_isdynamic (const a2act_t *tested);
bool a2act_isdynamicrecipe (const a2act_t *tested);

The parser function works along the lines of a2id_parse(), and the others test if the Actor is empty, static, dynamic or recipe-for-dynamic. Rules are likely to hold recipes for dynamic addresses, which must then be passed through a2id_sign() to complete them to the dynamic form.

The most interesting places where an Actor structure pops up is when a Remote Identity is passed into an Access Control function:

#include <arpa2/identity.h>
#include <arpa2/access_comm.h>
a2id_t *remote = ...;
a2id_t *local = ...;
access_comm_level out_level;
a2act_t out_actor;
bool ok = access_comm (remote, local, ..., &out_level, &out_actor);

This tries to communicate from remote to local, using whatever protocol, and if out_level is set to access_comm_whitelist then the value in out_actor should be interesting to use.

For Document Access, the pattern is almost the same:

#include <arpa2/identity.h>
#include <arpa2/access_document.h>
a2id_t *remote = ...;
char *xsname = ...;
access_rights out_rights;
a2act_t out_actor;
bool ok = access_document (remote, xsname, ..., &out_rights, &out_actor);

In both patterns, most upcoming functions calls would mention remote and &out_actor together.

It is often useful to allow a user to "downgrade" their login_identity to a more specific acl_identity in a separate operation. This may be useful to select a role or group membership, to preselect an alias, and so on. This may be done in response to such things as an authorisation identity presented during login, a web-interaction where a user selects one of multiple roles, or perhaps during the flow of a protocol. A good example of the latter would be SMTP, where the MAIL FROM: command selects a more specific identity and the From: header may be even more specific.

This question comes down to "may I act on behalf of" and makes a comparison, including Rules DB access if needed, between a authentication identity (the login_id) and a desired specific authorisation identity (the acl_id). When successful, the system can continue to treat the acl_id as if it were the login_id. This function can fail, in which case the surrounding operation should also fail due to lacking Access Control privileges to perform the requested operation. The test for this is:

#include <arpa2/identity.h>
#include <arpa2/access_actor.h>
a2id_t login_id = ...;
a2id_t acl_id = ...;
bool ok = access_actor (&login_id, &acl_id);

Note that the previous functions had an output argument &out_actor, and although &acl_id could be considered taking on an Actor Identity, it works as an input argument in this call.

The access_actor() call succeeds trivially when the two arguments are the same and represent an existing user. The call may be made more than once in succession, gradually reducing the access privilages and/or allowing to trivially skip an optional reduction in access privileges as it may occur in a protocol or service program. Every step allows the use of acl_id as a replacement of the login_id, also for use with other Access Control functions such as access_comm() or access_document().