Ethereum Access Control

Proposing Future Ethereum Access Control

Proposing Future Ethereum Access Control

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.


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.

Figure 1: Ownable.sol

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.

Figure 2: Implementing Ownable

Roles

Where Ownable is restricted to a single administrator, Openzeppelin’s Roles library enables multiple roles to be defined.

Figure 3: Roles.sol

Figure 3 shows the implementation of the Roles contract. Unlike OwnableRoles 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.

Figure 4: Implementing Roles

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.

Figure 5: AccessControl.sol (in Beta)

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_ROLEfor a super administrator as seen in the implementation of a child contract in Figure 6.

Figure 6: Implementing AccessControl

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.

Figure 7: Hierarchy.sol

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.

Figure 8: DynamicAccessControl.sol

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.

Figure 9: RoleSets.sol library

Now let’s assume that we have an access-controlled contract called ControlledContract, shown in figure 10.

Figure 10: ControlledContract.sol

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.

Default image
Alex Roan
Blockchain Developer, writer. https://alexroan.co.uk

Wanna learn about Crypto?

Subscribe to our weekly newsletter

Leave a Reply