说明:链码开发语言是golang,源码分析是基于v1.4.3版本
系列文章
1、Fabric自定义插件的开发-扩展插件的组织方式
2、Fabric自定义插件的开发-Decorator插件开发
3、Fabric自定义插件的开发-Validators插件开发
Auth插件,可以理解为peer的一个前置防火墙,屏蔽掉一些非法的提案。虽然在链码侧也可以对提案做一些校验,但如果能在最初的入口处拒掉非法请求岂不是更好?本文梳理Auth插件的加载和使用方式,以及分享一个样例。
读取插件,在自定义的插件中查找NewFilter函数,且函数格式为:func() auth.Filter,即需要返回一个实现了接口core.handlers.auth.Filter的对象,并将该对象存储在插件库对象registry的成员filters中。
函数:func (r *registry) initAuthPlugin(p *plugin.Plugin) {}
位置:core/handlers/library/registry.go:168
core.handlers.auth.Filter接口定义:
type Filter interface {
peer.EndorserServer
Init(next peer.EndorserServer)
}
peer.EndorserServer接口提供了一个方法:ProcessProposal(context.Context, *SignedProposal) (*ProposalResponse, error),用于处理client提交的提案。
所以,Auth插件对象需要提供两个方法:Init和ProcessProposal。
Fabric对Filter接口,有三个默认实现:expirationCheckFilter、filter和reset,其中reset用在特殊场景中,本文不分析,而其它两个实现是对所有场景都使用的。
所有的实现,都有一个next接口成员,用于承载下一个过滤器对象,而Init就是对这个next进行赋值。
同样,peer的插件库单例registry对象提供了Lookup方法给外部使用,以获取对应的插件。
Auth类型插件,都是单例,对所有通道的所有链码都生效。在peer收到client的提案时,就会依次执行插件的ProcessProposal方法。
peer进程,在加载完所有的Auth插件之后,会调用Auth插件的Init函数,把这些插件对象组成一个链,而最后一个插件对象的next接口成员指向最终的背书对象(core.endorser.Endorser)!
函数:func ChainFilters(endorser peer.EndorserServer, filters ...Filter) peer.EndorserServer {}
位置:core/handlers/auth/auth.go:23
即,提案只有经过所有Auth插件校验之后,才会进入最终的背书流程。
而Auth插件的顺序依赖于core.yaml中的配置:
handlers:
authFilters:
-
name: DefaultAuth
-
name: ExpirationCheck # This filter checks identity x509 certificate expiration
样例功能,识别提案消息头签名证书中的属性是否包含test字段。fabric支持对证书的属性进行扩展,OID是"1.2.3.4.5.6.7.8.1",也可以自行扩展。
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"github.com/gogo/protobuf/proto"
"github.com/hyperledger/fabric/common/attrmgr"
"github.com/hyperledger/fabric/core/handlers/auth"
"github.com/hyperledger/fabric/protos/msp"
"github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
"github.com/pkg/errors"
)
// NewFilter creates a new Filter
func NewFilter() auth.Filter {
return &identityCheck{}
}
type identityCheck struct {
next peer.EndorserServer
}
// Init initializes the Filter with the next EndorserServer
func (f *identityCheck) Init(next peer.EndorserServer) {
f.next = next
}
// ProcessProposal processes a signed proposal
func (f *identityCheck) ProcessProposal(ctx context.Context, signedProp *peer.SignedProposal) (*peer.ProposalResponse, error) {
prop, err := utils.GetProposal(signedProp.ProposalBytes)
if err != nil {
return nil, errors.Wrap(err, "failed parsing proposal")
}
hdr, err := utils.GetHeader(prop.Header)
if err != nil {
return nil, errors.Wrap(err, "failed parsing header")
}
sh, err := utils.GetSignatureHeader(hdr.SignatureHeader)
if err != nil {
return nil, errors.Wrap(err, "failed parsing signature header")
}
sID := &msp.SerializedIdentity{}
if err := proto.Unmarshal(sh.Creator, sID); err != nil {
return nil, errors.Wrap(err, "failed parsing creator")
}
block, _ := pem.Decode(sID.GetIdBytes())
if block == nil {
return nil, errors.New("Expecting a PEM-encoded X509 certificate; PEM block not found")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse certificate")
}
attrs, err := attrmgr.New().GetAttributesFromCert(cert)
if err != nil {
return nil, errors.WithMessage(err, "failed to get attributes from the transaction invoker's certificate")
}
if _,ok,_ := attrs.Value("test"); !ok {
return nil, errors.New("Invalid certificate")
}
return f.next.ProcessProposal(ctx, signedProp)
}
func main() {
}
插件的编译使用命令 go build -buildmod=plugin,生成so。
因为有依赖fabric源码,所以编译时,需要先clone一份fabric的代码,且在fabric的目录下进行编译,这样就可以直接用fabric项目的vendor了
这里有个限制,如果你的peer是1.4.0版本,那么在编译so时,也必须使用相同版本的fabric源码,否则会导致so加载失败!
多嘴一句,因为插件so和peer是同一个进程空间,所以,你懂得,peer有的数据,这里都可以获取到~
修改core.yaml中配置,使插件生效:
authFilters:
-
name: DefaultAuth
-
name: ExpirationCheck # This filter checks identity x509 certificate expiration
-
library: /opt/lib/xx.so
使用此插件后,使用普通证书签名的提案就会被peer拒掉,流程并不会走到链码侧。需要在签发签名证书时,增加指定的证书属性。