ARPA2 Common Libraries  2.2.18
Access Control for Communication

We explain how to restrict access under the InternetWide Architecture. Given the ARPA2 Common libraries, it is easy enough to integrate with the ambituous goals or freeing our online presence as users!

This document is about libarpa2access.so, the Access Control library, and more specifically about its angle on Communication ACLs. Access Control for Communication is an application of the general framework for Policy Rules, to which it adds semantics that are implemented in higher-level functions.

Below you will find:

  • Quick description of Concepts
  • API for ARPA2 Access Control
  • Library Initialisation and Finalisation
  • Lower-level API
  • Communication API
  • Key Derivation
  • System Administration for Access Control
  • Example Code
  • Related Work

Quick description of Concepts

Access Domain is the domain name offering services and user accounts. It is the Access Domain who generally erects a barrier of Access Control at its perimeter.

Remote Identity is an authenticated identity for a client trying to access a service or user under the Access Domain. This client may be part of another domain than the Access Domain, but even when that is not the case this is still how we speak of the access-seeking party as the Remote Identity, to emphasise the general idea that it is impartial to Access Control what assurance of the client identity we got.

Access Type is a general concept for an application that requires Access Control. The application is usually not specific to software or even a protocol, but tries to describe a usage pattern. Access Types define a number of aspects of Access Control that the application can use. An example of an Access Type is Communication; this may cover all of email, chat and telephony, in spite of software or the protocol du jour.

Access Name describes an instance of an Access Type under a specific Access Domain. The format is always a UTF-8 string, but the precise format is specified by the Access Type. Since this is scoped underneath the Access Domain there cannot be a clash when two Access Domains use the same Access Name, such as when a local username is used as Access Name; this is both a convenience and a security measure.

Remote Selector is the most concrete iteration output from the Remote Identity that defines one or more Rules for a requested Access Domain, Access Type and Access Name.

Rules define policy choices that are to be applied; see the Rules documentation for details.

Rights are a set of FLAGS in Rules, each describing a permission to use the service for which it is defined. They are sent to the service application by way of a callback. Communication Access defines rights for white, grey and black lists, as well as to stave off an attacker towards a honeypot.

Attributes are named by a single lowercase letter, like x in =xval, and may be assigned values in Rules. The policies for Communication Access define a few of these. See below for their definitions.

Triggers are short strings that provoke a callback to the service application. No semantics are defined for this in the Communication framework.

API for ARPA2 Access Control

This API builds on the one for Identity which you are assumed to understand.

A service application knows which Access Domain, Access Type and Access Name to use for each request by a Remote Identity. Some applications may have the Access Type hard-coded, others may make it a configuration parameter.

Though the Access Type defines many things, the form is general enough to support generic handling:

  • Access Domains follow the utf8-realm grammar in RFC 7542 so they can support internationalisation without having to consider the DNS-specific encoding in Punycode; API calls assume that they have a terminating NUL attached;
  • Access Types themselves are UUIDs in 16-byte binary form, passed literally to a service application;
  • Access Names are UTF-8 strings followed by a NUL character; the format would often allow construction from elements of the environment such as a URI path or Query parameters and some constant parts;
  • Access Control yields a level to which the Communication request is assigned; this is represented in type access_comm_level and values access_comm_whitelist, access_comm_greylist, access_comm_blacklist and as an even worse punishment than blacklisting the attempt to redirect a known attacker to a honeypot if possible with access_comm_honeypot.

Think of the Communcation ACL process as a call

(accessLevel,localID',[group]) := access_comm (localID,remoteID,aclData...)

where the localID may be modified in-place to redirect the traffic to an alias, a group or whatever else makes sense. See the attribute definitions below for controls that support this kind of redirection.

In practice, there are two variants for two different service scales:

  • Manual Deployment stores rules alongside the objects to protect, such as in a database or a site-local configuration in a web server. In this case, all Rules are evaluated to find the Rights for Selectors that match the Remote Identity. The most concrete determines the level of access granted and any modifications to the Local Identity.
  • Automated Deployments collect Rules in an efficiently indexed database, and start an Iterator over the Remote Identity. Combined with the Access Domain, Access Type and Access Name this lookup key determines only a few indexes to look for data, instead of iterating over all Rules and skipping parts based on Selectors embedded into them. The first index that is found will be the most concrete one, and determines the level of access granted and any modifications to the Local Identity.

The two service kinds differ in where the Rules come from; a basic configuration means that a set of Rules is provided in all API calls for Access Control. Scalable configuration does not supply that information, because it is already present in the database.

Manual Deployments

When Rules are stored in the service application to request access, then the choice of the ruleset is up to the application. For this reason, the only required inputs are those that help to evaluate that ruleset. Having zoomed in on a ruleset already, the only need is the Remote Identity.

#include <stdint.h>
#include <stdbool.h>
#include <arpa2/identity.h>
#include <arpa2/access_comm.h>
bool access_comm (const a2id_t *remote, a2id_t *local,
const uint8_t *opt_svckey, unsigned svckeylen,
const char *opt_acl, unsigned acllen,
access_comm_level *out_level,
a2act_t *optout_actor);

The Remote Identity and Local Identity are fundamental to all Communication attempts. Under manual deployment, the opt_acl is supplied, along with the total lenght in acllen. The ACL is a Ruleset of concatenated Rules, where each ends in a NUL character. The Service Key in opt_svckey and svckeylen add no value in the manual deployment scenario. The input local domain is used as the Access Domain and its user or service name (without +aliases or +svcargs) is used as the Access Name; finally, Communication Access defines its own Access Type.

When an error is detected, the function returns false and sets errno to an error that may be decoded with the com_err framework.

The optout_actor is for detection of an Actor Identity of the remote through the =g attribute. It can be set to NULL by applications that want to treat groups like a normal identity. Otherwise, the access_comm() call may set it up for continued group processing or sender-aliased forwarding. Applications that are aware of groups will test optout_actor to see if it holds a group member; if so, the sender address changes from remote to the Group Member Identity in optout_actor and local is expanded to the destination Group Member Identities. The communication is forked to all these destination addresses.

When successful, the function returns true and the value in out_level reveals the level of access granted. Your application may treat access_comm_greylist and/or access_comm_honeypot if it can handle them; otherwise they can be considered as you would for access_comm_blacklist, the general rejection of the proposed communication. Only access_comm_whitelist is the acceptance without prejudice of the proposed communication.

When access_comm_whitelist is returned from a successful call, the local field may have been modified to reflect another userid/svcname, different aliases/svcargs and, perhaps one day, a different domain. Consider this ACL-driven redirection; it is one example where the general Policy Rules framework can help us define refined control over incoming communication. An example use might be where email sets a Delivered-To header and delivers accordingly to a local mailbox.

Example Rules and expected outputs can be found in test/access/CMakeLists.txt and a supporting example program is test/access/xs-comm.c.

Automated Deployments

A service with a scalable configuration calls another Access Control function with almost the same information – just not the Rules; these are separately stored in the database and can be found with the Access Domain, Type and Name. The same API call is used:

#include <stdint.h>
#include <stdbool.h>
#include <arpa2/identity.h>
#include <arpa2/access_comm.h>
bool access_comm (const a2id_t *remote, a2id_t *local,
const uint8_t *opt_svckey, unsigned svckeylen,
const char *opt_acl, unsigned acllen,
access_comm_level *out_level);

In this case however, the opt_acl is set to NULL and the acllen will be ignored. The Access Name and is derived from local as for manual deployment, but a digest of the Access Domain and Database Secret must be provided as the opt_svckey with svckeylen. Other than this, the function behaves as described for manual deployments.

The Service Key may be configured in the calling application as a hexadecimal string, but it must be supplied here in its binary form. The reason for not deriving this information here is that the Database Secret may be isolated from services if this "lower grade" and more specific Service Key is used. It is not possible to derive another Service Key from the one provided here. See Rules documentation for the functions rules_dbkey_domain() and rules_dbkey_service() that take the two steps down from the Database Secret to the Service Key needed here.

A few example objects in LDAP could look like this,

dn: uid=test,associatedDomain=example.com,ou=IdentityHub,o=arpa2.net,ou=InternetWide
object: accessControl
accessType: 84283358-8ee3-444a-be2e-81e69f50b7fa
accessName: /some/identity/structure
accessRule: ^service ~+@.
accessRule: ^tickle =lfool %R ~@. =xuser %CWR ~@example.com
accessRule: =xmaster %ACDWR ~admin@example.com
dn: uid=john,associatedDomain=example.org,ou=IdentityHub,o=arpa2.net,ou=InternetWide
object: accessControl
accessType: b4f0fc38-d4d7-3bb9-ad69-5bf75efc46dd
accessName: john
accessRule: =ofriends %CWRKV ~mary@example.com ~miles@example.net
accessRule: =mjohn+cook %CWRKV ~cooks@example.com ~gourmets@example.net
accessRule: =oguests %V ~@. %RKV ~@example.net

The accessType is the UUID before it is mapped to an AccessDomainType, the accessName is the UTF-8 string to locate an instance and the accessRule defines who may do what to the containing object.

This form permits a few extra words because they will be sorted into the direct-lookup database form:

  • rights define Rights, valid until the end of the Rule or the next % declaration, whichever comes first;
  • =xstr stores a string str into Attribute x. The value persists until the end of the accessRule Attribute or until the Attribute x is set to a new value. There are 26 lowercase lettered Attributes, their meaning defined by the Access Type;
  • ^str is a Trigger which causes a callback with the value str as part of the evaluation, but only of the first ~ remote that follows;
  • #comment is ignored as a one-word comment;
  • ~remote defines that the foregoing declarations are stored under the remote, the Remote Selector.

The Pulley backend continually listens for changes to these attributes and makes the corresponding changes in the database. This should produce a comfortable link from an object-centric view on your data to one that is strictly for scalable and perhaps distributed operation, because LDAP is highly suitable for automation.

Library Initialisation and Finalisation

You should initialise the library, and you may finalise it to clean properly,

#include <arpa2/identity.h>
#include <arpa2/access_comm.h>
void a2id_init (void);
void access_init (void);
void a2id_fini (void);
void access_fini (void);

One thing these functions do is register error messages in the Common Error framework, so you can send errno into com_err() to print error messages arisen in the operating system, this library, the ARPA2 Identity library, as well as several higher layers of our software, and from others. The system uses a prefix to derive a 24-bit prefix to a 256-entry in the errno value range.

Access Type for Communication Access

Access Control for Communication answers questions like

Can mary@example.net communicate with john+cooks@example.com?

Note how this question says nothing about protocols or software. It could apply equally well to email as to chat or telephony; it may also be used for web-wrappers around any of these functions, including for conferencing tools if the target address is a group.

Access Type b4f0fc38-d4d7-3bb9-ad69-5bf75efc46dd was allocated on http://uuid.arpa2.org for this kind of question.

Attributes for Communication Access

There are a few Attributes that are interpreted for the Communication use case:

  • =a<aliases> is used to match aliases after the userid. In the given example, a=cooks might be sought, and it may provide more specific information than a Rule without this addition. It is also used for service arguments; note that the Access Name already preselects a Rule for a user or a service. To disable the default permission of more aliases than filtered, end with a @ symbol; it is even possible to forbid all aliases or service arguments with the =a@ setting; this is not as simple as it looks; the @ form still permits signatures.
  • =n<userid> overrides the userid, or if it starts with a + it overrides the service, in the local identity. It resets aliases and args but see =o for a way of setting these too. It does not remove local signature content. For group logic, use =g<scene>+<actor> to trigger group iteration, which will fork communication to any number of (local) delivery addresses.
  • =o<aliases> overrides the aliases, if any, following the userid. It can also be used for service arguments. It does not remove local signature content.
  • =s<sigflags> requires a signature, involving at least the given flags. The flags follow the signature flag registry represented as a decimal numbers.
  • =g<scene>+<actor> references an Actor Identity; this aids in group iteration and relaying from a local sender address.

Related Work

For backgrounds, read our blog article series:

This software combines with the Identity library that is also part of the same ARPA2 Common Library package.

Access Control assumes authenticated identities. We did quite some work on protocols for this, especially to support Realm Crossover through SASL and Kerberos. We also added SASL to HTTP, so authentication is taken out of the application layer and brought into the transport layer where security is less questionable.

The link to LDAP can be made with a backend for Pulley, part of the SteamWorks package of LDAP helper tools. Also in there is Crank, an interface between HTTP and LDAP.