剖析當前標準並提出智能合約中完全動態訪問控制管理的可能解決方案
訪問控制是軟件基礎設施安全的基本要素。企業應用程序需要嚴格規定誰可以做什麼,這取決於每個用戶的權限。
可以說,智能合約中的訪問控制需要更嚴格的審查,因為漏洞可能導致惡意行為者控制系統。
今天存在智能合約中簡單形式的靜態訪問控制。最常見的是onlyOwner 模式。另一個是Openzeppelin 的Roles 合約,它使合約能夠在部署之前定義角色。
雖然這為大多數智能合約應用程序提供了良好的基礎,但現代基於角色的訪問控制(RBAC) 系統使管理員能夠在運行時動態定義角色。角色契約在這個意義上是限制性的,因為角色不能在部署後定義。
本文介紹了智能合約訪問控制的現有模式,並提出了RBAC 和基於屬性的訪問控制(ABAC) 協議的定義。
唯一擁有者
onlyOwner 模式是智能合約最常用且易於實現的訪問控制方法。它很原始,但非常有效。
圖1 顯示了Ownable 合約的Openzeppelin 實現。
圖1:Ownable.sol
此模式假設智能合約只有一個管理員,並允許管理員將所有權轉移到另一個地址。擴展Ownable 合約允許子合約使用onlyOwner 自定義修飾符定義函數。這些功能要求交易的發送者是單一管理員。
圖2 顯示瞭如何在子合約中實現這一點的示例。
圖2:實現Ownable
角色
在Ownable 僅限於單個管理員的情況下,Openzeppelin 的角色庫允許定義多個角色。
圖3:Roles.sol
圖3 顯示了角色契約的實現。與Ownable 不同,Roles 不提供自定義訪問修飾符。相反,使用此庫的合約必須在函數內部實現角色要求。他們還必須為每個角色定義Roles.Role 類型的狀態變量。圖4 顯示了一個實現兩個角色的ERC20 合約示例:_burners 和_minters。
圖4:實施角色
它具有使合約能夠根據需要定義盡可能多的角色的靈活性,但這將require 語句的實現留給了合約。由於沒有提供自定義訪問修飾符,這在一定程度上降低了可讀性,並且增加了引入編碼錯誤的可能性。但是,沒有什麼能阻止合約實現包含require 語句的自定義訪問修飾符。
這些當前的解決方案在部署合約之前提供了角色的靈活性。由於角色依賴於硬編碼規則,因此不支持動態創建的角色。這非常適合在已知的內部智能合約之間提供訪問控制,但它不能提供與現代面向用戶的訪問控制軟件等效的靈活性。
具有活躍用戶群的軟件,尤其是企業軟件,本質上需要不同級別的訪問權限。隨著組織的發展或縮小,訪問控制應用程序的管理員需要能夠輕鬆添加和分配新角色。這些結構以基於角色的訪問控制(RBAC) 和基於屬性的訪問控制(ABAC) 的形式存在於當今的軟件中。
基於角色的訪問控制(RBAC)
在RBAC 中,每個用戶都被分配了一個角色,每個角色都有一組權限,只要他們的角色具有正確的權限,用戶就可以訪問資源。
如果系統從不需要新角色,RBAC 主要滿足於角色契約。
基於屬性的訪問控制(ABAC)
在ABAC 中,每個用戶都被分配了一組主題屬性,每個資源都被分配了一組對象屬性。中央訪問控制機構定義了關於需要哪些主體和客體屬性起作用的規則。
這是設置和維護比RBAC 更複雜和耗時的解決方案。但是,它對於大型應用程序和企業來說更加靈活,因為它允許每個用戶擁有不同的權限。
在運行時,Ownable 或Roles 都不滿足這些常見模式中的任何一個。如果需要創建新角色,則需要發布新代碼。這些架構不允許信息安全管理員通過簡單的界面創建、更新或刪除新角色。
幸運的是,有一些努力可以解決這個問題。
Openzeppelin 即將發布的v3.0(目前處於Beta 版)停止使用Roles 庫,取而代之的是名為AccessControl 的抽象契約。
注意:不建議你在生產應用程序中使用此解決方案,因為它仍處於測試階段。
圖5 顯示了AccessControl 的實現。
圖5:AccessControl.sol(測試版)
它展示了與角色相同的大部分功能,但不需要子合約來為每個角色實現aRoles.Role 狀態變量。相反,子合約實現的每個角色都由一個bytes32 變量表示。 AccessControl 還為超級管理員定義了DEFAULT_ADMIN_ROLE,如圖6 中子合約的實現所示。
圖6:實現AccessControl
圖6 中提供的示例同樣是靜態的,但AccessControl 提供了實現動態版本所需的靈活性。 Alberto Cuesta Cañada 有一個名為Hierarchy 的示例合約,它實現了AccessControl,可以在運行時動態創建角色。圖7 顯示了層次結構合約代碼。
圖7:Hierarchy.sol
這在某種程度上可以按照RBAC 標準啟用動態訪問控制。然而,這只是解決方案的一半。
它允許動態設置角色,但仍必須對功能的訪問級別進行硬編碼。例如,我們有一個名為Settings 的智能合約,它只能由具有“ADMIN”和“EDITOR”角色的用戶調用。 Settings 合約中的require 語句必須按照以下方式進行硬編碼:
function aSettingsFunction() public onlyMember(‘ADMIN’) onlyMember(‘EDITOR’) {}
因此,同樣的問題仍然存在。如果信息安全管理員想要動態添加一個應該有權訪問此功能的新角色,則必須為此功能編寫並提供新定義。
另一種方法是為每個用戶分配多個角色。因此,管理員將被分配角色“ADMIN”、“EDITOR”和“WRITER”。假設所有管理員也是編輯和作家,那麼上面的函數可以這樣寫:
function aSettingsFunction() public onlyMember(‘EDITOR’) {}
但這並沒有真正遵循RBAC,每個用戶都有一個角色,並且在分層系統中,他們繼承了較低角色的權限。
而且我們仍然存在能夠在需要時引入新角色並動態地將它們分配給函數的問題。
首先,我提出了一個名為DynamicAccessControl 的子合約,它類似於Hierarchy,但和onlyMember 修飾符一樣實現了一個onlyMembersOf 修飾符,它接受一組角色ID 並要求發送者至少被分配到這些角色中的一個。
圖8 顯示了它的實現。
圖8:DynamicAccessControl.sol
作為實現此修飾符的合約示例,管理員用戶只需要“ADMIN”角色,函數定義將像這樣定義。
函數aSettingsFunction() public onlyMembersOf([‘ADMIN’, ‘EDITOR’]) {}
在這個階段,仍然存在能夠輕鬆更新子功能的訪問修飾符的問題,因此InfoSec仍然無法在不發布新代碼的情況下更新某些角色對某些功能的訪問。但是,我們現在可以為每個用戶分配一個角色,從而為每個功能分配多個訪問級別(角色)。
為了能夠更新功能的權限,子合約需要維護角色集。
其次,我提出了一個名為RoleSets 的庫,它維護訪問受控合約可以使用的角色集。圖9 顯示了這個庫可能是什麼樣子的部分示例。
圖9:RoleSets.sol 庫
現在讓我們假設我們有一個名為ControlledContract 的訪問控制合約,如圖10 所示。
圖10:ControlledContract.sol
定義的三個RoleSet 在每個函數的定義中都有屬性,因此只有該中心化角色的成員才能調用這些函數(由onlyMembersOf 訪問修飾符要求)。
例如,只有secondaryRoleSet 角色的成員才能調用addRoleToTertiarySet()。
除此之外,可以更新集合以包括創建的新角色。因此,在此合約中,可以將動態創建的角色添加到集合中,從而提供對訪問控制功能的訪問。如果不發布新的硬編碼RoleSet,這是以前無法完成的事情。
然而,仍然需要硬編碼的一個方面,因為RoleSets.RoleSet 狀態變量的數量取決於對它們的編碼。
本質上,這導致了基於角色的訪問控制(RBAC) 和基於屬性的訪問控制(ABAC) 之間的混合。每個用戶只分配一個角色,每個功能分配一個包含多個角色的RoleSet(可以與ABAC 中的屬性進行比較)。這消除了為每個用戶授予多個角色以手動強制繼承較小角色的必要性(例如,“ADMIN”還授予“EDITOR”和“WRITER”角色以繼承調用具有這些屬性的函數的能力)。
定義的RoleSets.RoleSet 狀態變量可以更新以包含新的和動態創建的角色,從而提供信息安全管理員所需的功能。然而,這仍然在某種程度上受限於最初硬編碼的集合數量。
未來發展
我相信有一種方法可以從這個解決方案中完全刪除硬編碼。不是在單個合約中控制對功能的訪問,而是整個合約都可以在RoleSet 的訪問控制之下。
我將嘗試將具有關聯RoleSet 的整個合約註冊到中央機構合約。每個註冊的合約都可以由root 管理員動態更改關聯的RoleSet,從而完全不需要硬編碼。
非常感謝對本文中提出的想法的任何反饋。
資訊來源:由0x資訊編譯自COINCODECAP。版權歸作者Gaurav所有,未經許可,不得轉載