Hyperledger Fabric访问控制列表(ACL)

参考资料:

访问控制列表(ACL)是 What

之前写了很多文章一直没有介绍访问控制列表(以下统称为 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"
)

这里资源的命名规范是 <component>/<resource>,例如cscc/GetConfigBlockCSCC 组件中调用的 GetConfigBlock 的资源

策略

策略是 Fabric 运行的基础,因为它们允许根据与完成请求所需资源相关联的策略来检查与请求关联的身份(或身份集)。背书策略用来决定一个交易是否被合适地背书。通道配置中定义的策略被引用为修改策略以及访问控制,并且在通道配置本身中定义。

策略可以采用以下两种方式之一进行构造

  • Signature 策略
  • ImplicitMeta 策略

Signature 策略

这些策略标示了要满足策略而必须签名的用户。例如:

Policies:
  MyPolicy:
    Type: Signature
    Rule: "Org1.Peer OR Org2.Peer"

上述策略可以被这么解释:一个名为 MyPolicy 的策略,其类型为 Signature,它的签名规则是只有被 Org1 的 peer 节点或者是 Org2 的 peer 节点签名才可以通过

签名策略支持 ANDORNOutOf 的任意组合,能够构造强大的规则。

ImplicitMeta 策略

ImplicitMeta 策略聚合配置层次结构中更深层次的策略结果,这些策略最终由签名策略定义。他们支持默认规则,比如“组织中大多数管理员”。这些策略使用的语法和 Signature 策略不同但是依旧很简单: <ALL|ANY|MAJORITY> <sub_policy>

比如: ANY Readers 或者 MAJORITY Admins

这是一个 ImplicitMeta 策略结构的例子:

Policies:
  AnotherPolicy:
    Type: ImplicitMeta
    Rule: "MAJORITY Admins"

上述策略可以被这么解释:一个名为 AnotherPolicy 的策略,其类型为 ImplicitMeta,它的规则是可以通过大多数管理员同意的方式来满足。

这种配置中默认有以下三种角色:

  • Admins ,具有管理权限,可以访问资源的策略往往是针对网络的敏感操作方面(例如在通道上实例化链码)
  • Writers,可以提交账本更新,比如一个交易,但是不能拥有管理权限
  • Readers,可以访问信息,但是不可以提交账本更新,也没有管理权限

在哪里定义访问控制权限

configtx.yaml文件中定义 ACL,这个文件用来编译通道配置。

ACL 可以通过两种方式来更新:

  • 编辑 configtx.yaml 文件自身,这样之后使用这个文件生成通道配置的通道都会更新更改过的 ACL
  • 通过特定通道的通道配置来更新访问控制

如何在 configtx.yaml中格式化ACL

ACL 被格式化为一个键值对: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/<PolicyName>
    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

从源码来看ACL

看下 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 的一个别名):

  • ChannelApplicationReaders,即配置文件中格式化的 /Channel/Application/Readers
  • ChannelApplicationWriters,即配置文件中格式化的 /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 做了一下几种类型判断

  • pb.SignedProposal,签名提案
  • common.Envelope,交易类型
  • common.SignedData,签名数据

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/106670925
今日推荐