参考资料:
之前写了很多文章一直没有介绍访问控制列表(以下统称为 ACL )这个东西,在 peer node start
命令源码很前面的地方就有 ACL 相关的代码。这是 Fabric 1.2 新增的一个特性,这篇文章就来分析分析 ACL 到底是什么。
Access Control Lists,访问控制列表,Fabirc 使用 ACL 通过关联一个策略来管理资源。这里介绍一下这两个关键概念
Fabirc 的用户交互通过用户链码,系统链码,或者事件流来实现。因此,这些被视为应该在其上执行访问控制的资源。下面看下源码中设定的默认资源,文件在 core/aclmgmt/resources/resources.go
:
//fabric resources used for ACL checks. Note that some of the checks
//such as Lscc_INSTALL are "peer wide" (current access checks in peer are
//based on local MSP). These are not currently covered by resource or default
//ACLProviders
const (
//Lscc resources
Lscc_Install = "lscc/Install"
Lscc_Deploy = "lscc/Deploy"
Lscc_Upgrade = "lscc/Upgrade"
Lscc_ChaincodeExists = "lscc/ChaincodeExists"
Lscc_GetDeploymentSpec = "lscc/GetDeploymentSpec"
Lscc_GetChaincodeData = "lscc/GetChaincodeData"
Lscc_GetInstantiatedChaincodes = "lscc/GetInstantiatedChaincodes"
Lscc_GetInstalledChaincodes = "lscc/GetInstalledChaincodes"
Lscc_GetCollectionsConfig = "lscc/GetCollectionsConfig"
//Qscc resources
Qscc_GetChainInfo = "qscc/GetChainInfo"
Qscc_GetBlockByNumber = "qscc/GetBlockByNumber"
Qscc_GetBlockByHash = "qscc/GetBlockByHash"
Qscc_GetTransactionByID = "qscc/GetTransactionByID"
Qscc_GetBlockByTxID = "qscc/GetBlockByTxID"
//Cscc resources
Cscc_JoinChain = "cscc/JoinChain"
Cscc_GetConfigBlock = "cscc/GetConfigBlock"
Cscc_GetChannels = "cscc/GetChannels"
Cscc_GetConfigTree = "cscc/GetConfigTree"
Cscc_SimulateConfigTreeUpdate = "cscc/SimulateConfigTreeUpdate"
//Peer resources
Peer_Propose = "peer/Propose"
Peer_ChaincodeToChaincode = "peer/ChaincodeToChaincode"
//Events
Event_Block = "event/Block"
Event_FilteredBlock = "event/FilteredBlock"
//Token resources
Token_Issue = "token/Issue"
Token_Transfer = "token/Transfer"
Token_List = "token/List"
)
这里资源的命名规范是
,例如cscc/GetConfigBlock
是 CSCC
组件中调用的 GetConfigBlock
的资源
策略是 Fabric 运行的基础,因为它们允许根据与完成请求所需资源相关联的策略来检查与请求关联的身份(或身份集)。背书策略用来决定一个交易是否被合适地背书。通道配置中定义的策略被引用为修改策略以及访问控制,并且在通道配置本身中定义。
策略可以采用以下两种方式之一进行构造
Signature
策略ImplicitMeta
策略Signature
策略这些策略标示了要满足策略而必须签名的用户。例如:
Policies:
MyPolicy:
Type: Signature
Rule: "Org1.Peer OR Org2.Peer"
上述策略可以被这么解释:一个名为 MyPolicy
的策略,其类型为 Signature
,它的签名规则是只有被 Org1 的 peer 节点或者是 Org2 的 peer 节点签名才可以通过
签名策略支持 AND
, OR
和 NOutOf
的任意组合,能够构造强大的规则。
ImplicitMeta
策略ImplicitMeta
策略聚合配置层次结构中更深层次的策略结果,这些策略最终由签名策略定义。他们支持默认规则,比如“组织中大多数管理员”。这些策略使用的语法和 Signature
策略不同但是依旧很简单:
。
比如: ANY
Readers
或者 MAJORITY
Admins
。
这是一个 ImplicitMeta
策略结构的例子:
Policies:
AnotherPolicy:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
上述策略可以被这么解释:一个名为 AnotherPolicy
的策略,其类型为 ImplicitMeta
,它的规则是可以通过大多数管理员同意的方式来满足。
这种配置中默认有以下三种角色:
在 configtx.yaml
文件中定义 ACL,这个文件用来编译通道配置。
ACL 可以通过两种方式来更新:
configtx.yaml
文件自身,这样之后使用这个文件生成通道配置的通道都会更新更改过的 ACLconfigtx.yaml
中格式化ACLACL 被格式化为一个键值对:resource:policy
# ACL policy for invoking chaincodes on peer
peer/Propose: /Channel/Application/Writers
# ACL policy for sending block events
event/Block: /Channel/Application/Readers
其中策略被定义成为一个路径的形式,例如上面例子中的策略,可以在 configtx.yaml
中根据路径去找到:
Application: &ApplicationDefaults
# Policies defines the set of policies at this level of the config tree
# For Application policies, their canonical path is
# /Channel/Application/
Policies:
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
configtx.yaml
中的默认 ACL假如想修改 peer/Propose
的策略,可以直接修改它后面策略内容,我们也可以自定义一个策略 MyPolicy
Application: &ApplicationDefaults
Policies:
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
MyPolicy:
Type: Signature
Rule: "OR('SampleOrg.admin')"
然后修改
# ACL policy for invoking chaincodes on peer
peer/Propose: /Channel/Application/MyPolicy
看下 peer node start
命令启动时候的内容,peer/node/start.go
:
func serve(args []string) error {
//....
//startup aclmgmt with default ACL providers (resource based and default 1.0 policies based).
//Users can pass in their own ACLProvider to RegisterACLProvider (currently unit tests do this)
aclProvider := aclmgmt.NewACLProvider(
aclmgmt.ResourceGetter(peer.GetStableChannelConfig),
)
//....
}
可以看到 aclmgmt.NewACLProvider 函数创建了 ACL 的 Provider,它的参数 peer.GetStableChannelConfig 最后被转成了一个 aclmgmt.ResourceGetter ,看下这个是什么东西
//resource getter gets channelconfig.Resources given channel ID
type ResourceGetter func(channelID string) channelconfig.Resources
// GetStableChannelConfig returns the stable channel configuration of the chain with channel ID.
// Note that this call returns nil if chain cid has not been created.
func GetStableChannelConfig(cid string) channelconfig.Resources {
chains.RLock()
defer chains.RUnlock()
if c, ok := chains.list[cid]; ok {
return c.cs.bundleSource.StableBundle()
}
return nil
}
ResourceGetter 顾名思义,资源获取者,从注释可以看出该函数就是从给定的通道ID中获取通道配置中的资源的。
//ACLProvider consists of two providers, supplied one and a default one (1.0 ACL management
//using ChannelReaders and ChannelWriters). If supplied provider is nil, a resource based
//ACL provider is created.
func NewACLProvider(rg ResourceGetter) ACLProvider {
return &aclMgmtImpl{
rescfgProvider: newResourceProvider(rg, NewDefaultACLProvider()),
}
}
//create a new resourceProvider
func newResourceProvider(rg ResourceGetter, defprov ACLProvider) *resourceProvider {
return &resourceProvider{rg, defprov}
}
//resource provider that uses the resource configuration information to provide ACL support
type resourceProvider struct {
//resource getter
resGetter ResourceGetter
//default provider to be used for undefined resources
defaultProvider ACLProvider
}
NewACLProvider 有一个成员 rescfgProvider,它是一个 resourceProvider 类型,可以通过传递的参数看到,一个是从配置中获取的资源,另一个是默认的ACLProvider,默认的ACLProvider 是用来给未定义的资源设置策略的,我们来看下 NewDefaultACLProvider 函数中做了什么
const (
CHANNELREADERS = policies.ChannelApplicationReaders
CHANNELWRITERS = policies.ChannelApplicationWriters
)
type defaultACLProvider struct {
policyChecker policy.PolicyChecker
//peer wide policy (currently not used)
pResourcePolicyMap map[string]string
//channel specific policy
cResourcePolicyMap map[string]string
}
func NewDefaultACLProvider() ACLProvider {
d := &defaultACLProvider{}
d.initialize()
return d
}
func (d *defaultACLProvider) initialize() {
d.policyChecker = policy.NewPolicyChecker(
peer.NewChannelPolicyManagerGetter(),
mgmt.GetLocalMSP(),
mgmt.NewLocalMSPPrincipalGetter(),
)
d.pResourcePolicyMap = make(map[string]string)
d.cResourcePolicyMap = make(map[string]string)
//-------------- LSCC --------------
//p resources (implemented by the chaincode currently)
d.pResourcePolicyMap[resources.Lscc_Install] = ""
d.pResourcePolicyMap[resources.Lscc_GetInstalledChaincodes] = ""
//c resources
d.cResourcePolicyMap[resources.Lscc_Deploy] = "" //ACL check covered by PROPOSAL
d.cResourcePolicyMap[resources.Lscc_Upgrade] = "" //ACL check covered by PROPOSAL
d.cResourcePolicyMap[resources.Lscc_ChaincodeExists] = CHANNELREADERS
d.cResourcePolicyMap[resources.Lscc_GetDeploymentSpec] = CHANNELREADERS
d.cResourcePolicyMap[resources.Lscc_GetChaincodeData] = CHANNELREADERS
d.cResourcePolicyMap[resources.Lscc_GetInstantiatedChaincodes] = CHANNELREADERS
d.cResourcePolicyMap[resources.Lscc_GetCollectionsConfig] = CHANNELREADERS
//-------------- QSCC --------------
//p resources (none)
//c resources
d.cResourcePolicyMap[resources.Qscc_GetChainInfo] = CHANNELREADERS
d.cResourcePolicyMap[resources.Qscc_GetBlockByNumber] = CHANNELREADERS
d.cResourcePolicyMap[resources.Qscc_GetBlockByHash] = CHANNELREADERS
d.cResourcePolicyMap[resources.Qscc_GetTransactionByID] = CHANNELREADERS
d.cResourcePolicyMap[resources.Qscc_GetBlockByTxID] = CHANNELREADERS
//--------------- CSCC resources -----------
//p resources (implemented by the chaincode currently)
d.pResourcePolicyMap[resources.Cscc_JoinChain] = ""
d.pResourcePolicyMap[resources.Cscc_GetChannels] = ""
//c resources
d.cResourcePolicyMap[resources.Cscc_GetConfigBlock] = CHANNELREADERS
d.cResourcePolicyMap[resources.Cscc_GetConfigTree] = CHANNELREADERS
d.cResourcePolicyMap[resources.Cscc_SimulateConfigTreeUpdate] = CHANNELWRITERS
//---------------- non-scc resources ------------
//Peer resources
d.cResourcePolicyMap[resources.Peer_Propose] = CHANNELWRITERS
d.cResourcePolicyMap[resources.Peer_ChaincodeToChaincode] = CHANNELWRITERS
d.cResourcePolicyMap[resources.Token_Issue] = CHANNELWRITERS
d.cResourcePolicyMap[resources.Token_Transfer] = CHANNELWRITERS
d.cResourcePolicyMap[resources.Token_List] = CHANNELREADERS
//Event resources
d.cResourcePolicyMap[resources.Event_Block] = CHANNELREADERS
d.cResourcePolicyMap[resources.Event_FilteredBlock] = CHANNELREADERS
}
它内部有两个 map 变量,可以理解为 ACL 的 resource:policy
这样的形式,根据注释当前这个 pResourcePolicyMap 是还没有用到的(1.4版本),可以看到 initialize 函数中 pResourcePolicyMap 设置的资源的 value 都是空字符串,而 cResourcePolicyMap 即通道资源的策略,它设置了一堆默认的 ACL。资源部分我在文章开始就放出来看过了,而策略部分主要就是两种策略(CHANNELREADERS 是 ChannelApplicationReaders 的一个别名):
/Channel/Application/Readers
/Channel/Application/Writers
ACLProvider 提供了一个 CheckACL 函数,CheckACL 使用 idinfo 在ACL中检查通道资源。 idinfo 是诸如SignedProposal之类的对象,可以从中提取 id进行策略测试
//CheckACL checks the ACL for the resource for the channel using the
//idinfo. idinfo is an object such as SignedProposal from which an
//id can be extracted for testing against a policy
func (am *aclMgmtImpl) CheckACL(resName string, channelID string, idinfo interface{}) error {
//use the resource based config provider (which will in turn default to 1.0 provider)
return am.rescfgProvider.CheckACL(resName, channelID, idinfo)
}
//CheckACL implements the ACL
func (rp *resourceProvider) CheckACL(resName string, channelID string, idinfo interface{}) error {
resCfg := rp.resGetter(channelID)
if resCfg != nil {
// 先在通道配置中定义的 ACL 中找
pp := &aclmgmtPolicyProviderImpl{&policyEvaluatorImpl{resCfg}}
policyName := pp.GetPolicyName(resName)
if policyName != "" {
aclLogger.Debugf("acl policy %s found in config for resource %s", policyName, resName)
return pp.CheckACL(policyName, idinfo)
}
aclLogger.Debugf("acl policy not found in config for resource %s", resName)
}
// 再在默认的 ACL 中找
return rp.defaultProvider.CheckACL(resName, channelID, idinfo)
}
很简单,分两步,第一步先在通道配置中定义的 ACL 中找是否符合,如果CheckOK,则直接返回,如果CheckErr,则再在默认的 ACL 中去找。
因此根据这种规则,假设一个资源对应的策略在通道配置中和默认的 ACL 中都配置了,那么就会去 Check 通道配置中的 ACL(因为通道配置中的先 Check)
看下默认 ACL 的 CheckACL 是怎么做的
//CheckACL provides default (v 1.0) behavior by mapping resources to their ACL for a channel
func (d *defaultACLProvider) CheckACL(resName string, channelID string, idinfo interface{}) error {
policy := d.defaultPolicy(resName, true)
if policy == "" {
aclLogger.Errorf("Unmapped policy for %s", resName)
return fmt.Errorf("Unmapped policy for %s", resName)
}
switch typedData := idinfo.(type) {
case *pb.SignedProposal:
return d.policyChecker.CheckPolicy(channelID, policy, typedData)
case *common.Envelope:
sd, err := typedData.AsSignedData()
if err != nil {
return err
}
return d.policyChecker.CheckPolicyBySignedData(channelID, policy, sd)
case []*common.SignedData:
return d.policyChecker.CheckPolicyBySignedData(channelID, policy, typedData)
default:
aclLogger.Errorf("Unmapped id on checkACL %s", resName)
return fmt.Errorf("Unknown id on checkACL %s", resName)
}
}
它主要对 idInfo 做了一下几种类型判断