Dissecting current standards and proposing possible solutions for fully dynamic access control management in smart contracts
Access control is a fundamental element to the security of software infrastructure. Enterprise applications need strict rules on who can do what, depending on each userโs privileges.
It could be argued that access control in smart contracts needs even greater scrutiny since a vulnerability can result in malicious actors taking control of the system.
Simple forms of static access control in smart contracts exist today. The most common is the onlyOwner
pattern. Another is Openzeppelinโs Roles
contract, which enables contracts to define roles before deployment.
Whilst this provides a good foundation for most smart contract applications, modern Role-Based Access Control (RBAC) systems enable administrators to define roles dynamically at runtime. The Roles
contract is restrictive in this sense since roles cannot be defined after deployment.
This article walks through existing patterns for smart contract access control and proposes definitions for RBAC and Attribute-Based Access Control (ABAC) protocols.
Table of Contents
Only Owner
The onlyOwner
pattern is the most commonly used and easily implemented access control method for smart contracts. Itโs primitive but highly effective.
Figure 1 shows the Openzeppelin implementation of the Ownable
contract.
This pattern assumes that there is a single administrator of the smart contract, and enables the administrator to transfer ownership to another address. Extending the Ownable
contract allows child contracts to define functions with the onlyOwner
custom modifier. These functions require that the sender of the transaction be the single administrator.
Figure 2 shows an example of how this is implemented in a child contract.
Roles
Where Ownable
is restricted to a single administrator, Openzeppelinโs Roles
library enables multiple roles to be defined.
Figure 3 shows the implementation of the Roles
contract. Unlike Ownable
, Roles
doesnโt provide a custom access modifier. Instead, contracts using this library must implement role requirements inside functions. They also have to define Roles.Role
type state variables for each role. Figure 4 shows an example of an ERC20 contract implementing two roles: _burners
and _minters
.
It has the flexibility of enabling contracts to define as many roles as is needed, but that leaves the implementation of the require
statement to the contract. This reduces readability somewhat since no custom access modifier is provided, and it increases the possibility of introducing coding error. However, nothing is stopping the contract from implementing custom access modifiers containing require statements.
Limitations
These current solutions provide the flexibility of roles up until the point of deploying the contracts. Since the roles depend on hard-coded rules, dynamically created roles are not supported. This is well suited to provide access control between known internal smart contracts, but it doesnโt provide flexibility equivalent to that of modern user-facing access-controlled software.
Software with active user bases, especially enterprise software, by nature, requires varying levels of access. As an organisation grows or shrinks, administrators of access-controlled applications need the ability to easily add and assign new roles. These structures exist in software today, in the form of Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC).
Role-Based Access Control (RBAC)
In RBAC every user is assigned a role, every role has a set of permissions and resources can be accessed by users providing their role has the correct permissions.
RBAC is mostly satisfied by the Roles
contract provided the system never requires new roles.
Attribute-Based Access Control (ABAC)
In ABAC each user is assigned a set of subject attributes, each resource is assigned a set of object attributes. A central access control authority defines the rules as to which subject and object attributes are required to act.
This is a more complex and time-consuming solution to set up and maintain than RBAC. However, it is more flexible for large applications and enterprises as it allows for diverse permissions unique to each user.
Neither Ownable
or Roles
satisfy either of these common patterns at runtime. If new roles need to be created, new code needs to be shipped. These architectures donโt allow for an InfoSec administrator to create, update or delete new roles through a simple interface.
Fortunately, there are efforts to resolve this.
Access Control (Beta)
Openzeppelinโs upcoming release v3.0 (currently in Beta) discontinues Roles
library in favour of an abstract contract called AccessControl
.
Note: It is not recommended that you use this solution in production applications whilst it is still in the testing phase.
Figure 5 shows the implementation of AccessControl
.
It exhibits much of the same functionality as Roles
but removes the need for child contracts to implement aRoles.Role
state variable for each role. Instead, each role implemented by the child contract is denoted by a bytes32
variable. AccessControl
also defines DEFAULT_ADMIN_ROLE
for a super administrator as seen in the implementation of a child contract in Figure 6.
The example provided in figure 6 is again static, but AccessControl
provides the flexibility required to implement a dynamic version. Alberto Cuesta Caรฑada has an example contract called Hierarchy
, implementing AccessControl
, which enables the dynamic creation of roles at runtime. Figure 7 shows the Hierarchy
contract code.
This goes some way to enabling dynamic access control, following RBAC standards. However, it is only half of the solution.
It enables roles to be set dynamically, but access levels of functions must still be hardcoded. For example, we have a smart contract called Settings
which should only be called by users with the roles โADMINโ and โEDITORโ. The require statements in the Settings contract must be hardcoded along the lines of:
function aSettingsFunction() public onlyMember('ADMIN') onlyMember('EDITOR') {}
Therefore, the same problem remains. If the InfoSec administrator wants to dynamically add a new role which should have access to this function, a new definition must be written and shipped for this function.
Another route would be to assign multiple roles per user. So an administrator would be assigned the roles โADMINโ, โEDITORโ and โWRITERโ. Assuming all administrators are also editors and writers, the above function could then be written like this:
function aSettingsFunction() public onlyMember('EDITOR') {}
But this doesnโt really follow RBAC, where each user has a single role, and in a hierarchical system, they inherit the permissions of the lower roles.
AND we still have the problem of being able to introduce new roles as theyโre needed, and dynamically attributing them to functions.
Proposal
Firstly, I propose a child contract called DynamicAccessControl
which is similar to Hierarchy
, but as well as the onlyMember
modifier implements an onlyMembersOf
modifier, which accepts an array of role ids and requires that the sender be assigned to at least one of those roles.
Figure 8 shows the implementation of this.
As an example of contracts implementing this modifier, administrator users only need the โADMINโ role, and function definitions would be defined like this.
function aSettingsFunction() public onlyMembersOf(['ADMIN', 'EDITOR']) {}
At this stage, the problem of being able to easily update the access modifiers of child functions still exists, so an InfoSec still canโt update the access of certain roles to certain functions without shipping new code. But, we can now assign each user to a single role and therefore assign multiple access levels (roles) to each function.
To enable updating permissions of functions, child contracts need to maintain sets of roles.
Secondly, I propose a library called RoleSets
which maintains sets of roles that access controlled contracts can use. Figure 9 shows a partial example of what this library might look like.
Now letโs assume that we have an access-controlled contract called ControlledContract
, shown in figure 10.
The three RoleSets
that are defined are attributed in the definitions of each function so that only members of the roles in that set can call those functions (required by the onlyMembersOf
access modifier).
For example, only members of secondaryRoleSet
roles can call addRoleToTertiarySet()
.
As well as this, the sets can be updated to include new roles that are created. So, inside this contract, roles that are created dynamically can be added to the sets, providing access to the access-controlled functions. This is something that could have not previously been done without shipping new hardcoded RoleSets
.
However, there is still an aspect of hardcoding required since the number of RoleSets.RoleSet
state variables depend on coding them.
Conclusion
Essentially this has resulted in a hybrid between Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). Each user is only assigned one role, and each function is assigned a RoleSet (which could be compared to attributes in ABAC) containing multiple roles. This removes the necessity for granting multiple roles per user to manually enforce inheritance of lesser roles (e.g. โADMINโ also granted โEDITORโ and โWRITERโ roles to inherit the ability to call functions with those attributes).
The RoleSets.RoleSet state variables that are defined can be updated to include newly and dynamically created roles, thus providing the functionality InfoSec administrators require. However, this is still somewhat limited to the number of sets hard coded initially.
Future Development
I believe there is a method to remove hard coding completely from this solution. Instead of controlling the access to functions in a single contract, entire contracts could be under the access control of a RoleSet
.
Iโm going to experiment with registering entire contracts, with an associated RoleSet
, to a central authority contract. Each registered contract can have the associated RoleSet
changed by root administrators dynamically, removing the need for hard coding completely.
Any feedback on the ideas presented in this article is greatly appreciated.