BCAP

Belay Cloud Access Protocol & API

Mark Lentczner – draft of May 20, 2011

Very much a work in progress!

In a Nutshell

The Belay APIs and protocols work equivalently between modules, even if either module is part of  a web page, or a distinct application, or a web server across the Internet. The example code below is generally the same no matter what the execution environment. A module uses the Belay API through a CapServer, which is usually provided by the environment on start up.

When the module decides that it will authorize access to some feature of itself, it does so by granting a capability:

cap = capServer.grant(myHandler, key, tags);

The handler is the function that implements the feature. The granting module chooses the key to contain all the information it will need to later supply the feature.  Tags are similar to keys, but optional, and are used to assist with revocation. The CapServer ensures that the the key and tags remain hidden, are not obtainable by modules that invoke the capability.


The new capability object can now be returned or given to whatever system is to be given access. The capability is unforgeable and doesn't expose any internal information, even when serialized to a string, stored, or transmitted (e.g. via HTTP).


Later, when another module wishes to actually exercise the access, it simply invokes the capability it obtained:

cap.invoke(requestData, responseCallbackFunction);

Invocation is an asynchronous operation, returning a status (possibly failure) and a response.


Back at the granting module, the invocation will result in a call via the handler:

myHandler(key, requestData) { ...; return response; }

The key parameter is the key data from the original grant. The CapServer has saved that away, and returned it to the module on invoke. The granting module can rely on the key having remained private to it and its CapServer, and having never been disclosed.


At some point in the future, the granting module may revoke access to the capability by identifying it through any of the tags that were attached to the grant:

capServer.revokeByTags(tags);

From that point on, all future invocations of the matching capability will automatically fail.


Both the capabilities and the grants made with the CapServer can be serialized and restored, and survive module shutdown and restart.

Example

Social feed decides (based on user input) to allow airline to post to user's blog:


Airline posts via the capability:

Later, system decides to revoke airline's ability, and future uses of the cap fail:

Introduction

This design document defines the Belay capability abstraction. This is an abstract API that governs how code can create and use capabilities as a means to securely enable access to resources. While defined generally, it should be clear that this abstraction can be practically, and directly implemented in a variety of different computing contexts such as JavaScript in a web page, or web servers. In particular, it is aimed at supporting web and cloud based computing contexts.

It is also an express aim that it be possible to create implementations in different computing contexts that can interoperate. Given the web and cloud focus, this API, while abstract, is defined in terms of some common web foundations:
  • Int, String, UUID, and URL are taken to be their natural forms in a given computing context, but understood to be interoperable values.
  • JSON is used for structured data interchange, generally with no restrictions on keys, values, or size other than implementation practical considerations.
  • Elements of HTTP, notably status values and transaction terminology are used, even when HTTP is not the underlying transport.


Since this document is abstract, we must start be defining some terms to be used independent of the computing context:

A module is a distinct set of code and data that can execute. (Think: application, process, library, web page, iframe, instance, request handler, etc...). We say "can" because it is understood that any particular module exists over time whether or not it is executing at the moment. It may be "sleeping" or "frozen" or "pickled", and may at a later time be "awoken", "thawed", etc... We make no distinction here between systems that can pause and persist an module automatically, or those which the module's code itself must write the persistent version, and upon restart reload.

All modules are started by a container. (Think: shell, calling module, browser, outer web-page, web server, etc...). The module depends, ultimately, on the container for all its interaction outside itself#. The module must trust the container#.

A capability is an object a module comes to have that gives it the ability to invoke some function in some particular target module. The holding module knows a priori the operation the capability provides, usually by way of how it obtained the capability. For example, if a module is given a capability in response to a UI request for the user to pick a blog to post to, then the module knows the capability provides a posting operation.#

The capability designates the target module. A holding module cannot choose which module a capability targets. The capability, when granted, while providing the expected operation, is generally scoped at the target. For example, which blog may be posted to, or how long the post may be, or how the post might be attributed, are all aspects of the capability that may be fixed at the target end. While holding module can provide arguments when it invokes the capability's action, it cannot, as a rule, view or change the properties that the target has chosen to keep fixed.

Typically, though not always, capabilities are unforgeable: Modules cannot, in general, just manufacture capabilities to other systems. Systems that provide capabilities protect against this by making the capabilities include either a random or encrypted component. Occasionally, systems may wish to make well-known capabilities, which are open to anyone to use, and these may include components which a module can programmatically construct. These capabilities generally lack any substantive privilege, but may serve as entry points to obtaining (with suitable authentication and authorization) other capabilities. The chief use of such capabilities is to allow the whole API of a system to be uniformly defined in terms of this capability framework.


Readers familiar with capability security should take note of these specifics about this version of the abstraction:

In Belay, capabilities are equivalent to their serialized form (see below), and modules can choose to exchange those serialized forms over channels available to them, and thus share capabilities. The ability to turn these serialized forms back into functional capabilities doesn't enable a module to create capabilities it hasn't been given because they are (generally) unforgeable, as discussed above.

We consider each module to be a single trust domain. That is, all code within a module trusts all other code, and is coded in concert with it. In particular, as each module has a capability server (see below), all the code in the module has authority to manage capabilities issued by any other
code in the module. In some computing contexts, more fine grained facilities might be available, and modules might be dividable into defensibly coded sub-modules. In such contexts we expect that the ability to create matching sub-divided capability servers can be made available.


Using Capabilities

Capabilities can be invoked. This operation is asynchronous, and can fail. (How this is mapped on to the facilities of a programming language is environment specific. Here, like all other examples, are shown in JavaScript.)

cap.invoke(request :: JSON,

          success(response :: JSON),

          failure(code :: Status))


Invoking passes a request payload to the capability target, and, if successful, returns a response payload. These payloads are structured "plain ol' data" defined as that which can be represented in JSON.

If the invocation fails, it may have failed before the request was delivered, while being processed, during return of the response, or due to the local time out. The failure information is given as an HTTP status value, indicates what caused the failure. Modules must handle that if the failure was due to locally detected issues (such as time out or network failure), it will be unknown if the target processed the request or not.

Capabilities can be tested for their status. This returns an indication if the local system knows that the capability will fail for all time in the future. If the status indicates that invoking the capability can never succeed, then a module can use this information to generate a better user experience. This operation does not perform any interaction with the target of the capability, it is simply querying the local system's state.

cap.status() :: Status




Capability Server

A capability server is the interface that allows a module to offer capabilities. When a module wishes to give out a capability to something within it, it uses the capability server to grant the capability. Later, the module can choose to revoke the capabilty, causing it to no longer function.

When a capability is granted, the module must associate an invokable item to which the capability refers. When some other module invokes the capability, the invokable is triggered to handle the invocation. There are three kinds of invokables:
  1. A function. When invoked, the function is applied to the request data, and returns the response data, or possibly a failure code.
  2. A URL. When invoked, an HTTPS POST call is made to the URL with the request, response, and status mapped from the HTTP protocol.
  3. A separate wrapped capability. When the granted capability is invoked, the wrapped capability is invoked. Note that the granted capability (the wrapping capability) is distinct from the wrapped capability, and can be revoked independently. Of course, if the wrapped capability is revoked, the wrapping capability is effectively revoked as well


capServer.grant(invokable :: Invokable,

               key :: String, tags :: [String]) :: Capability


Invokable = Function(key :: String, data :: JSON) :: JSON

         | URL

         | Capability


The granted capability is associated with the key (a string) and the tags (a set of strings). The generated capability will not leak any of these strings to holders of the capability. In this way, tamper-proof information about the capability, often the arguments to implement the capabilities function, can be conveniently associated with the capability.

A note about functions and keys: The mechanism by which an invoked capability is assoicated with a particular piece of code to run is very computing context specific. In some contexts, like JavaScript, a function object can be supplied which carries a closure and contains all it needs to know to implement the code. In other contexts, such as C++, a function pointer can be passed, but no closure. In yet others, such as many web servers, a mapping between paths and code must be established at startup or configuration time. We conceptually support all these methods by specifying that the grant is made to a function and a key. The key is passed back to the function on invocation, and serves to provide the missing context in some systems that a closure would have provided in others.


A capability server manages the relationship between the externally visible capabilities, and the internal information that maps those capabilities to callable functions. Capability servers need to be as durable as the capabilities being issued. Capability servers for long-lived capabilities will store this information in persistent data stores. Capability servers for web services that only need to be valid for as long as the service is running could keep such information in memory.

In one common implementation, for each grant, the capability server allocates a random UUID, and internally associates it with the information it needs to invoke the function.  In this case, the server must maintain a durable database of mappings between UUIDs and the invocation information.  In another implementation, the capability server exposes an encrypted and MAC’d form of the invocation information as the externally-visible capabilities.  In this case, the server must maintain the necessary encryption keys in a durable (and secret!) manner.


A module can revoke capabilities it has granted. When a module revokes a capability, all future invocations of it fail automatically. Invocations that are in-flight may or may not fail.

capServer.revoke(cap :: Capability)


In addition to calling with the capability itself, modules can revoke via:

capServer.revokeAll()

capServer.revokeByKey(key :: String)

capServer.revokeByTags(tags :: [String])

The first revokes all capabilities. The second revokes those that share the key. The third revokes all capabilities that have ALL of the tags specified.

Modules use tags to associate with a capability the aspects of the grant that they might later use to revoke a grant. For example, when granting a capability to post to a blog, the tags might include the enabled operation, an identifier for the grantee, an identifier for the blog, and perhaps even a tag to indicate the user's intent for the grant. Then later, upon user's request or other signals, the module could easily revoke all capabilities for a given grantee, or to post to a particular blog, etc.

A capability server can implement revocation in many ways, either based on white-listing valid capabilities or based on black-listing invalid ones (which may be a good choice in those cases where revocation is expected to be an exceedingly rare event).  For different implementations, different amounts of state must be durably maintained.  Capability servers may be required to handle unbounded numbers of issued and revoked capabilities, and the implementation should be tuned accordingly, e.g., to use cryptographic keys to reduce the amount of durable state.

Persistence

It is expected that modules are written in languages and run in environments in which they will need to save their state as plain data, and restore from plain data at a future time. This interface provides for both the persistence of capabilities held by a module, and the capabilities granted by a module.

Capabilities can be serialized. This returns a URL that can be later used to re-create the capability. The URL might be an https:// scheme URL, or an urn:x-cap: URL.

cap.serialize() :: URL


Capabilities can be restored from a the serialized form. This utility is performed via the capability server object. See Module Resolution below for the mechanics of how.

capServer.restore(url :: URL) :: Capability



The capability server itself can be serialized and restored. The container is responsible for saving the serialized form away, and recreating the capability server when the module is revived. The serialization can save all information about outstanding (granted, and not yet revoked) capabilities except when the invokable is a function. In most languages, a function is either a pointer to loaded code, or a closure. Neither of these is suitable for persistence. Therefore, the module must provide its capability server a resolver which given a key returns an appropriate invokable:

capServer.setResolver(resolver(key :: String) :: Invokable)


This function is called when the capability server needs to restore the connection between a granted capability and the function invokable that implements it. Capability servers in some environments may do this upon the first, or each, invocation. In other environments, where the function might be called immediately for all restored capabilities.

Typically, when a module decides to grant a capability, it generates a key string that represents all the information needed to make the function for the capability, and could therefore call

capServer.grant(resolver(key), key, tags)



Mechanism

When a capability is invoked, the corresponding invokable must be found, and called in a safe manner. This process is divided into two phases:
  • Location: Find the capability server that handles this capability
  • Dispatch: Resolve to the function that will implement the capability, and call it


A capability contains two parts, the authority and the opaque, that correspond to the two phases. During location, the invoking system uses the authority part to locate and establish communication with the capability server for the capability. During dispatch, the target capability server uses the opaque part to find the code to call, and what arguments to supply it.



HTTPS Capabilities
When a capability is an https URL, then these two parts correspond to parts of the URL
  • Capability's authority = URL's authority
  • Capability's opaque = URL's path & query


Location is simply DNS lookup, followed by validation of the server certificate during TLS session establishment. Dispatch is achieved by sending an HTTP POST message. At the server side, dispatch is handled as a normal web request. If the capability server is an independent server, then the opaque part is typically looked up for an internal URL, and the request proxied. If the capability server is integrated with the application server, then the opaque part is looked up to find a new path to re-dispatch on. In either case, the internal URL or path is not accessible from outside the server.

Belay Module Capabilities
When a capability is a urn:x-cap: URI, then there are two UUIDs, one each for authority and opaque. The an authority UUID is associated with each module's capability server by the Belay container. Location is achieved by the Belay container finding the module associated with the capability server that corresponds to the authority UUID. If necessary, the module may need to be re-awakened.


Note on security: For any kind of capability, the invoking module must trust that the container provided location facility correctly identifies the authentic capability server for a given capability. In the case of HTTPS capabilities, since DNS is untrustworthy in general, the facilities of TLS must be used to ensure the remote server is authenticated. In the case of Belay module capabilities, the Belay container must keep track of the mapping from authority UUIDs to modules and their capability servers itself, and not trust modules to remember and present their authority UUIDs, lest they spoof other modules.
Comments