-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pact capabilities #31
Comments
Frrom https://www.cs.ucsb.edu/~chris/teaching/cs290/doc/eros-sosp99.pdf, "EROS: a fast capability system", Shapiro, Smith, Farber 1999: A capability is an unforgeable pair made up of an object identifier and a set of authorized operations (an interface) on that object [9]. UNIX file descriptors [51], for example, are capabilities. In a capability system, each process holds capabilities, and can perform those operations authorized by its capabilities. Security is assured by three properties:
A protection domain is the set of capabilities accessible to a subsystem. An essential idea of capability-based system design is that both applications and operating system should be divided into cleanly separated components, each of which resides in its own protection domain. Subject to constraint by an external reference monitor (Figure 1), capabilities may be transferred from one protection domain to another and may be written to objects in the persistent store. Access rights, in addition to data, can therefore be saved for use across instantiations of one or more programs. |
The previous cite helps motivate the Pact system as a capability system; as it is sometimes suggested that capabilities must also support
The Pact system as proposed instead fits the EROS description of simply binding a program object to an interface/set of authorized operations. |
Investigating key-auth semantics in Pact as a lens onto the similarities and differences in a typical capability system. In Pact, rights enforcement employs a "enforce-before" semantic where the (non-re-entrant) code that follows is thereby "protected" by the previous call.
Indeed, early versions of Pact had
Finally, the "rights system" at play here is static during a given transaction: In a typical capability system, the rights are focused on some resource plus what operation(s) might be allowed. In the example above, let's say that
The code would at some point request the desired right. The mechanism for granting the right would be the keyset enforcement. Upon success, a token would be produced for use authorizing the particular task. The following pseudocode uses example 1, table update access.
with the idea that An advantage of this could be the further rationalization and/or delegation of rights in a system. As of Pact 2.6.0, all module functions are "public" which requires duplicating However, adding the token as an argument, and assuming that the token object is unforgeable, essentially forbids the external world from invoking If we add this notion of a token, what has been called "capabilities" up to this point instead become the predicates-against-the-environment by which access to a token is granted. A potential drawback of tokens is their proliferation. It might make sense to instead be able to "install" tokens into the environment itself, something like the following: (request-access user-table UPDATE) ;; install token
(do-protected-task) ;; needs token to be installed
(do-something-else) Indeed, this style could allow that instead of having an opaque (defcap UPDATE_USERS "capability to update user table")
(defun request-user-table-update ()
(enforce-keyset 'authorized-user)
(grant UPDATE_USERS) ;; `grant` installs UPDATE_USERS into the environment
;; `revoke` would remove it
)
(defun do-protected-task ()
(enforce-capability UPDATE_USERS) ;; this checks the environment for presence of UPDATE_USERS
(update 'users ...))
...
(request-user-table-update)
(do-protected-task)
... FURTHER WORK:
|
Examining row keysets through the capability lens: Current Pact enforces row keysets by reading them before performing actions:
A capability would represent here the ability to debit the balance column of the row at
This implies that a capability object needs to accept some data to service data-driven rights, as opposed to the static UPDATE_USERS cap above. This pseudocode assumes that |
The question to address now is how capability objects are themselves managed. In the examples above they are managed by the environment via the The first question is "who can grant a capability". In a lot of capability discussions (and the main one I'll refer to is [1]) there are two big assumptions:
In an immutable environment like Pact, while we can certainly imitate this functionality behind the scenes with some kind of reference object, this would introduce unfamiliar and undesirable semantics. Environmental management allows for modulation through built-in processes, with the advantage that the semantics cannot be violated by the programmer. The most obvious modulation is revocation upon transaction completion. This is basically inviolable, but the capability discussion for Pact has always wanted a way to transfer rights between steps of a pact, so that e.g. a signer involved in a first step can make some right available to a later step that would not be signed. Generally, the "pact predicate" described handles this in a tighter fashion, and can of course be married to a corresponding grant. In any case, transaction-scope rights fits most use cases well, but an explicit Environment-based management also allows a tight relation between a capability and the module that defines it. In the examples above, the grantable capabilities have been defined using Thus the "who" granting a capability is the autonomous code of the module that defined it. Note that the "predicates + capabilities" design taking shape here assumes some of the responsibility that in OO-style capabilities would be magically endowed to this or that object reference: in this capability model, you have to "re-access" a capability in each transaction that needs it, usually enforcing a keyset. Thus the notion of delegation in OO-style capabilities here moves to designing a predicate that will allow the delegated actor or process to produce the proper environmental conditions to allow the module to grant the desired capability. 1 - http://habitatchronicles.com/2017/05/what-are-capabilities/ |
The code snippets above share the following characteristics:
What is desirable is a way to simplify and reduce these steps as much as possible. Step #4 is necessarily distinct as there could be many code locations where a capability is enforced; however for 1-3 we would like to eliminate errors and confusion about how to specify. Extending NB the following code examples are deprecated as Rewriting the examples above looks like the following: UPDATE_USERS defcap (defcap UPDATE_USERS ()
(enforce-keyset 'authorized-user))
(defun do-protected-task ()
(enforce-capability UPDATE_USERS)
(update 'users ...)) Here, (DEBIT id) defcap
Similarly here, A possible enhancement could be to offer a |
Returning to the original design, we see these now as predicates or guards, not capabilities, and can further motivate their design by the following observations:
EDIT: these were named
EDIT: see below for
1 - http://habitatchronicles.com/2017/05/what-are-capabilities/ |
We provide the signatures for capability management itself. (EDIT: changed capability to be app form within
Rewriting the examples above looks like the following: UPDATE_USERS defcap (defcap UPDATE_USERS ()
(enforce-keyset 'authorized-user))
(defun do-protected-task ()
(with-capability (UPDATE_USERS)
(update 'users ...))) (DEBIT id) defcap
While the above discussion was not sure whether to go with a scoped form, the awkwardness of the CAPABILITY + PARAMS product answered the question syntactically, due to the un-Pact-like placement of a function-like def name in the second position; EDIT: as noted above, using application syntax for capabilities simplifies things enough for a revoke to be tenable, but |
This design now presents a unified model of capabilities and guards as a flexible and safe solution. However the very attractiveness of The problem being, in admin data load (either at contract install, or during module schema migration/upgrade), every write to the tables will require the guarding keyset to be tested. Worse, with generalized governance, the governance function would have to be called again and again, which may be pathological in cases where the governance uses the database. The intention was that the token system would address this by installing the token at first challenge and allowing the implicit transactional scope to allow the further interactions. Unfortunately this is far less well-scoped than the For now we will proceed with this transaction-scoped install of the module admin token in order to make generalized governance practical. We note that a transaction that is capable of being granted module admin is already a very sensitive operation requiring care, so ensuring that any calls to third-party code will not be able to abuse the elevated rights is added to the user's responsibility here. However, we should consider also requiring that top-level code that is not a module upgrade be encased in a dedicated special form, e.g. |
We need to consider if EDIT see below, module-only is very useful. |
The module guard indeed can have valid uses (for instance, giving governance control over unallocated coins/tokens), and as it is a guard, it does not suffer from the scoping issues of granting the module admin capability, as presumably the only reason to deploy the guard is to leverage governance to access some other scoped capability.
|
Implementation NotesHaskell implementation DRAFTThe Term type data Guard
= GPact PactGuard
| GModule ModuleGuard
| GKeySet KeySet
| GKeySetRef KeySetName
| GUser UserGuard
deriving (Eq)
This achieves the objective of making keysets interoperable with other guards in Pact code. The design problem to solve is how to deal with the type at the Pact surface level.
|
An attractive feature would be to offer a way to require a capability with no attempt to obtain it if it is not already acquired. This would make guarding factored functions like (Note that this might be better factored as a However, this requires revisiting the notion above that
New comment for
|
Implemented in #324 |
See below for current design, the following is out of date
As described in upcoming Pact Capabilities paper. Capabilities takes keysets and subsumes them into a "shadow ADT" type,
capability
, which also includes module capabilities, pact capabilities, and custom user capabilities. API changes are as follows:The Pact Capability Model
Keysets provide the inspiration. Keysets in Pact are managed with the
following operations:
define-keyset: this stores a keyset in the global environment under
some unique name.
read-keyset: the only way to construct keysets in user code,
read-keyset interrogates the JSON transaction payload at the specified
key to build a keyset from a special JSON schema found there, specifying
the constituent keys and predicate function.
enforce-keyset: this takes either a keyset object, or a string
referring to a keyset in the global environment, and enacts the
enforcement logic described above to ensure the keyset rule is matched
by public keys used to sign the transaction.
Seeing read-keyset as a constructor, and enforce-keyset as the
enforcement interface, we can generalize to support other capabilities
by providing constructors for the various types, but unifying
enforcement to a single built-in. First, the constructors:
create-module-capability: takes a string identifier to distinguish
different module-controlled resources. Module name is captured
implicitly.
create-pact-capability: takes a string identifier to distinguish
different pact-controlled resources. Pact name and ID are captured
implicitly.
define-capability: takes two arguments, the first holding the data
to be captured, and the second a string identifying a function to enact
the predicate logic.
The values returned by these constructors, as well as keysets, form an
ADT-like opaque datatype which cannot be pattern-matched in Pact code,
of type capability. A capability once created can only be used with
the sole enforcement function:
enforce-capability: takes any capability and enacts the necessary
logic required by its sub-type to execute predicate logic to enforce the
intent. Notably, the predicate logic is executed in the same "pure
environment" used by keysets to ensure no database manipulation can
occur during enforcement.
Example: A Hash-Timelocked Capability in Pact
Hash timelocks enforce an "OR" requirement that either the user provide
a hash preimage, or the original owner can reclaim the funds after a
timeout. The following example code creates a capability which could be
stored in a coin account for guarding access to that balance. Note that
the predicate logic expects the secret value to be in the JSON payload
under the key "secret".
The text was updated successfully, but these errors were encountered: