63 Star 183 Fork 3

Gitee 极速下载/hyperledger-fabric

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/hyperledger/fabric
克隆/下载
main.go 21.10 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/hyperledger/fabric/common/tools/cryptogen/ca"
"github.com/hyperledger/fabric/common/tools/cryptogen/csp"
"github.com/hyperledger/fabric/common/tools/cryptogen/metadata"
"github.com/hyperledger/fabric/common/tools/cryptogen/msp"
"gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
)
const (
userBaseName = "User"
adminBaseName = "Admin"
defaultHostnameTemplate = "{{.Prefix}}{{.Index}}"
defaultCNTemplate = "{{.Hostname}}.{{.Domain}}"
)
type HostnameData struct {
Prefix string
Index int
Domain string
}
type SpecData struct {
Hostname string
Domain string
CommonName string
}
type NodeTemplate struct {
Count int `yaml:"Count"`
Start int `yaml:"Start"`
Hostname string `yaml:"Hostname"`
SANS []string `yaml:"SANS"`
}
type NodeSpec struct {
Hostname string `yaml:"Hostname"`
CommonName string `yaml:"CommonName"`
Country string `yaml:"Country"`
Province string `yaml:"Province"`
Locality string `yaml:"Locality"`
OrganizationalUnit string `yaml:"OrganizationalUnit"`
StreetAddress string `yaml:"StreetAddress"`
PostalCode string `yaml:"PostalCode"`
SANS []string `yaml:"SANS"`
}
type UsersSpec struct {
Count int `yaml:"Count"`
}
type OrgSpec struct {
Name string `yaml:"Name"`
Domain string `yaml:"Domain"`
EnableNodeOUs bool `yaml:"EnableNodeOUs"`
CA NodeSpec `yaml:"CA"`
Template NodeTemplate `yaml:"Template"`
Specs []NodeSpec `yaml:"Specs"`
Users UsersSpec `yaml:"Users"`
}
type Config struct {
OrdererOrgs []OrgSpec `yaml:"OrdererOrgs"`
PeerOrgs []OrgSpec `yaml:"PeerOrgs"`
}
var defaultConfig = `
# ---------------------------------------------------------------------------
# "OrdererOrgs" - Definition of organizations managing orderer nodes
# ---------------------------------------------------------------------------
OrdererOrgs:
# ---------------------------------------------------------------------------
# Orderer
# ---------------------------------------------------------------------------
- Name: Orderer
Domain: example.com
# ---------------------------------------------------------------------------
# "Specs" - See PeerOrgs below for complete description
# ---------------------------------------------------------------------------
Specs:
- Hostname: orderer
# ---------------------------------------------------------------------------
# "PeerOrgs" - Definition of organizations managing peer nodes
# ---------------------------------------------------------------------------
PeerOrgs:
# ---------------------------------------------------------------------------
# Org1
# ---------------------------------------------------------------------------
- Name: Org1
Domain: org1.example.com
EnableNodeOUs: false
# ---------------------------------------------------------------------------
# "CA"
# ---------------------------------------------------------------------------
# Uncomment this section to enable the explicit definition of the CA for this
# organization. This entry is a Spec. See "Specs" section below for details.
# ---------------------------------------------------------------------------
# CA:
# Hostname: ca # implicitly ca.org1.example.com
# Country: US
# Province: California
# Locality: San Francisco
# OrganizationalUnit: Hyperledger Fabric
# StreetAddress: address for org # default nil
# PostalCode: postalCode for org # default nil
# ---------------------------------------------------------------------------
# "Specs"
# ---------------------------------------------------------------------------
# Uncomment this section to enable the explicit definition of hosts in your
# configuration. Most users will want to use Template, below
#
# Specs is an array of Spec entries. Each Spec entry consists of two fields:
# - Hostname: (Required) The desired hostname, sans the domain.
# - CommonName: (Optional) Specifies the template or explicit override for
# the CN. By default, this is the template:
#
# "{{.Hostname}}.{{.Domain}}"
#
# which obtains its values from the Spec.Hostname and
# Org.Domain, respectively.
# - SANS: (Optional) Specifies one or more Subject Alternative Names
# to be set in the resulting x509. Accepts template
# variables {{.Hostname}}, {{.Domain}}, {{.CommonName}}. IP
# addresses provided here will be properly recognized. Other
# values will be taken as DNS names.
# NOTE: Two implicit entries are created for you:
# - {{ .CommonName }}
# - {{ .Hostname }}
# ---------------------------------------------------------------------------
# Specs:
# - Hostname: foo # implicitly "foo.org1.example.com"
# CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above
# SANS:
# - "bar.{{.Domain}}"
# - "altfoo.{{.Domain}}"
# - "{{.Hostname}}.org6.net"
# - 172.16.10.31
# - Hostname: bar
# - Hostname: baz
# ---------------------------------------------------------------------------
# "Template"
# ---------------------------------------------------------------------------
# Allows for the definition of 1 or more hosts that are created sequentially
# from a template. By default, this looks like "peer%d" from 0 to Count-1.
# You may override the number of nodes (Count), the starting index (Start)
# or the template used to construct the name (Hostname).
#
# Note: Template and Specs are not mutually exclusive. You may define both
# sections and the aggregate nodes will be created for you. Take care with
# name collisions
# ---------------------------------------------------------------------------
Template:
Count: 1
# Start: 5
# Hostname: {{.Prefix}}{{.Index}} # default
# SANS:
# - "{{.Hostname}}.alt.{{.Domain}}"
# ---------------------------------------------------------------------------
# "Users"
# ---------------------------------------------------------------------------
# Count: The number of user accounts _in addition_ to Admin
# ---------------------------------------------------------------------------
Users:
Count: 1
# ---------------------------------------------------------------------------
# Org2: See "Org1" for full specification
# ---------------------------------------------------------------------------
- Name: Org2
Domain: org2.example.com
EnableNodeOUs: false
Template:
Count: 1
Users:
Count: 1
`
//command line flags
var (
app = kingpin.New("cryptogen", "Utility for generating Hyperledger Fabric key material")
gen = app.Command("generate", "Generate key material")
outputDir = gen.Flag("output", "The output directory in which to place artifacts").Default("crypto-config").String()
genConfigFile = gen.Flag("config", "The configuration template to use").File()
showtemplate = app.Command("showtemplate", "Show the default configuration template")
version = app.Command("version", "Show version information")
ext = app.Command("extend", "Extend existing network")
inputDir = ext.Flag("input", "The input directory in which existing network place").Default("crypto-config").String()
extConfigFile = ext.Flag("config", "The configuration template to use").File()
)
func main() {
kingpin.Version("0.0.1")
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
// "generate" command
case gen.FullCommand():
generate()
case ext.FullCommand():
extend()
// "showtemplate" command
case showtemplate.FullCommand():
fmt.Print(defaultConfig)
os.Exit(0)
// "version" command
case version.FullCommand():
printVersion()
}
}
func getConfig() (*Config, error) {
var configData string
if *genConfigFile != nil {
data, err := ioutil.ReadAll(*genConfigFile)
if err != nil {
return nil, fmt.Errorf("Error reading configuration: %s", err)
}
configData = string(data)
} else if *extConfigFile != nil {
data, err := ioutil.ReadAll(*extConfigFile)
if err != nil {
return nil, fmt.Errorf("Error reading configuration: %s", err)
}
configData = string(data)
} else {
configData = defaultConfig
}
config := &Config{}
err := yaml.Unmarshal([]byte(configData), &config)
if err != nil {
return nil, fmt.Errorf("Error Unmarshaling YAML: %s", err)
}
return config, nil
}
func extend() {
config, err := getConfig()
if err != nil {
fmt.Printf("Error reading config: %s", err)
os.Exit(-1)
}
for _, orgSpec := range config.PeerOrgs {
err = renderOrgSpec(&orgSpec, "peer")
if err != nil {
fmt.Printf("Error processing peer configuration: %s", err)
os.Exit(-1)
}
extendPeerOrg(orgSpec)
}
for _, orgSpec := range config.OrdererOrgs {
renderOrgSpec(&orgSpec, "orderer")
if err != nil {
fmt.Printf("Error processing orderer configuration: %s", err)
os.Exit(-1)
}
extendOrdererOrg(orgSpec)
}
}
func extendPeerOrg(orgSpec OrgSpec) {
orgName := orgSpec.Domain
orgDir := filepath.Join(*inputDir, "peerOrganizations", orgName)
if _, err := os.Stat(orgDir); os.IsNotExist(err) {
generatePeerOrg(*inputDir, orgSpec)
return
}
peersDir := filepath.Join(orgDir, "peers")
usersDir := filepath.Join(orgDir, "users")
caDir := filepath.Join(orgDir, "ca")
tlscaDir := filepath.Join(orgDir, "tlsca")
signCA := getCA(caDir, orgSpec, orgSpec.CA.CommonName)
tlsCA := getCA(tlscaDir, orgSpec, "tls"+orgSpec.CA.CommonName)
generateNodes(peersDir, orgSpec.Specs, signCA, tlsCA, msp.PEER, orgSpec.EnableNodeOUs)
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}
// copy the admin cert to each of the org's peer's MSP admincerts
for _, spec := range orgSpec.Specs {
err := copyAdminCert(usersDir,
filepath.Join(peersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s peer %s:\n%v\n",
orgName, spec.CommonName, err)
os.Exit(1)
}
}
// TODO: add ability to specify usernames
users := []NodeSpec{}
for j := 1; j <= orgSpec.Users.Count; j++ {
user := NodeSpec{
CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName),
}
users = append(users, user)
}
generateNodes(usersDir, users, signCA, tlsCA, msp.CLIENT, orgSpec.EnableNodeOUs)
}
func extendOrdererOrg(orgSpec OrgSpec) {
orgName := orgSpec.Domain
orgDir := filepath.Join(*inputDir, "ordererOrganizations", orgName)
caDir := filepath.Join(orgDir, "ca")
usersDir := filepath.Join(orgDir, "users")
tlscaDir := filepath.Join(orgDir, "tlsca")
orderersDir := filepath.Join(orgDir, "orderers")
if _, err := os.Stat(orgDir); os.IsNotExist(err) {
generateOrdererOrg(*inputDir, orgSpec)
return
}
signCA := getCA(caDir, orgSpec, orgSpec.CA.CommonName)
tlsCA := getCA(tlscaDir, orgSpec, "tls"+orgSpec.CA.CommonName)
generateNodes(orderersDir, orgSpec.Specs, signCA, tlsCA, msp.ORDERER, false)
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}
for _, spec := range orgSpec.Specs {
err := copyAdminCert(usersDir,
filepath.Join(orderersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s orderer %s:\n%v\n",
orgName, spec.CommonName, err)
os.Exit(1)
}
}
}
func generate() {
config, err := getConfig()
if err != nil {
fmt.Printf("Error reading config: %s", err)
os.Exit(-1)
}
for _, orgSpec := range config.PeerOrgs {
err = renderOrgSpec(&orgSpec, "peer")
if err != nil {
fmt.Printf("Error processing peer configuration: %s", err)
os.Exit(-1)
}
generatePeerOrg(*outputDir, orgSpec)
}
for _, orgSpec := range config.OrdererOrgs {
renderOrgSpec(&orgSpec, "orderer")
if err != nil {
fmt.Printf("Error processing orderer configuration: %s", err)
os.Exit(-1)
}
generateOrdererOrg(*outputDir, orgSpec)
}
}
func parseTemplate(input string, data interface{}) (string, error) {
t, err := template.New("parse").Parse(input)
if err != nil {
return "", fmt.Errorf("Error parsing template: %s", err)
}
output := new(bytes.Buffer)
err = t.Execute(output, data)
if err != nil {
return "", fmt.Errorf("Error executing template: %s", err)
}
return output.String(), nil
}
func parseTemplateWithDefault(input, defaultInput string, data interface{}) (string, error) {
// Use the default if the input is an empty string
if len(input) == 0 {
input = defaultInput
}
return parseTemplate(input, data)
}
func renderNodeSpec(domain string, spec *NodeSpec) error {
data := SpecData{
Hostname: spec.Hostname,
Domain: domain,
}
// Process our CommonName
cn, err := parseTemplateWithDefault(spec.CommonName, defaultCNTemplate, data)
if err != nil {
return err
}
spec.CommonName = cn
data.CommonName = cn
// Save off our original, unprocessed SANS entries
origSANS := spec.SANS
// Set our implicit SANS entries for CN/Hostname
spec.SANS = []string{cn, spec.Hostname}
// Finally, process any remaining SANS entries
for _, _san := range origSANS {
san, err := parseTemplate(_san, data)
if err != nil {
return err
}
spec.SANS = append(spec.SANS, san)
}
return nil
}
func renderOrgSpec(orgSpec *OrgSpec, prefix string) error {
// First process all of our templated nodes
for i := 0; i < orgSpec.Template.Count; i++ {
data := HostnameData{
Prefix: prefix,
Index: i + orgSpec.Template.Start,
Domain: orgSpec.Domain,
}
hostname, err := parseTemplateWithDefault(orgSpec.Template.Hostname, defaultHostnameTemplate, data)
if err != nil {
return err
}
spec := NodeSpec{
Hostname: hostname,
SANS: orgSpec.Template.SANS,
}
orgSpec.Specs = append(orgSpec.Specs, spec)
}
// Touch up all general node-specs to add the domain
for idx, spec := range orgSpec.Specs {
err := renderNodeSpec(orgSpec.Domain, &spec)
if err != nil {
return err
}
orgSpec.Specs[idx] = spec
}
// Process the CA node-spec in the same manner
if len(orgSpec.CA.Hostname) == 0 {
orgSpec.CA.Hostname = "ca"
}
err := renderNodeSpec(orgSpec.Domain, &orgSpec.CA)
if err != nil {
return err
}
return nil
}
func generatePeerOrg(baseDir string, orgSpec OrgSpec) {
orgName := orgSpec.Domain
fmt.Println(orgName)
// generate CAs
orgDir := filepath.Join(baseDir, "peerOrganizations", orgName)
caDir := filepath.Join(orgDir, "ca")
tlsCADir := filepath.Join(orgDir, "tlsca")
mspDir := filepath.Join(orgDir, "msp")
peersDir := filepath.Join(orgDir, "peers")
usersDir := filepath.Join(orgDir, "users")
adminCertsDir := filepath.Join(mspDir, "admincerts")
// generate signing CA
signCA, err := ca.NewCA(caDir, orgName, orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
if err != nil {
fmt.Printf("Error generating signCA for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
// generate TLS CA
tlsCA, err := ca.NewCA(tlsCADir, orgName, "tls"+orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
if err != nil {
fmt.Printf("Error generating tlsCA for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
err = msp.GenerateVerifyingMSP(mspDir, signCA, tlsCA, orgSpec.EnableNodeOUs)
if err != nil {
fmt.Printf("Error generating MSP for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
generateNodes(peersDir, orgSpec.Specs, signCA, tlsCA, msp.PEER, orgSpec.EnableNodeOUs)
// TODO: add ability to specify usernames
users := []NodeSpec{}
for j := 1; j <= orgSpec.Users.Count; j++ {
user := NodeSpec{
CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName),
}
users = append(users, user)
}
// add an admin user
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}
users = append(users, adminUser)
generateNodes(usersDir, users, signCA, tlsCA, msp.CLIENT, orgSpec.EnableNodeOUs)
// copy the admin cert to the org's MSP admincerts
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
orgName, err)
os.Exit(1)
}
// copy the admin cert to each of the org's peer's MSP admincerts
for _, spec := range orgSpec.Specs {
err = copyAdminCert(usersDir,
filepath.Join(peersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s peer %s:\n%v\n",
orgName, spec.CommonName, err)
os.Exit(1)
}
}
}
func copyAdminCert(usersDir, adminCertsDir, adminUserName string) error {
if _, err := os.Stat(filepath.Join(adminCertsDir,
adminUserName+"-cert.pem")); err == nil {
return nil
}
// delete the contents of admincerts
err := os.RemoveAll(adminCertsDir)
if err != nil {
return err
}
// recreate the admincerts directory
err = os.MkdirAll(adminCertsDir, 0755)
if err != nil {
return err
}
err = copyFile(filepath.Join(usersDir, adminUserName, "msp", "signcerts",
adminUserName+"-cert.pem"), filepath.Join(adminCertsDir,
adminUserName+"-cert.pem"))
if err != nil {
return err
}
return nil
}
func generateNodes(baseDir string, nodes []NodeSpec, signCA *ca.CA, tlsCA *ca.CA, nodeType int, nodeOUs bool) {
for _, node := range nodes {
nodeDir := filepath.Join(baseDir, node.CommonName)
if _, err := os.Stat(nodeDir); os.IsNotExist(err) {
err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, signCA, tlsCA, nodeType, nodeOUs)
if err != nil {
fmt.Printf("Error generating local MSP for %s:\n%v\n", node, err)
os.Exit(1)
}
}
}
}
func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {
orgName := orgSpec.Domain
// generate CAs
orgDir := filepath.Join(baseDir, "ordererOrganizations", orgName)
caDir := filepath.Join(orgDir, "ca")
tlsCADir := filepath.Join(orgDir, "tlsca")
mspDir := filepath.Join(orgDir, "msp")
orderersDir := filepath.Join(orgDir, "orderers")
usersDir := filepath.Join(orgDir, "users")
adminCertsDir := filepath.Join(mspDir, "admincerts")
// generate signing CA
signCA, err := ca.NewCA(caDir, orgName, orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
if err != nil {
fmt.Printf("Error generating signCA for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
// generate TLS CA
tlsCA, err := ca.NewCA(tlsCADir, orgName, "tls"+orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
if err != nil {
fmt.Printf("Error generating tlsCA for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
err = msp.GenerateVerifyingMSP(mspDir, signCA, tlsCA, false)
if err != nil {
fmt.Printf("Error generating MSP for org %s:\n%v\n", orgName, err)
os.Exit(1)
}
generateNodes(orderersDir, orgSpec.Specs, signCA, tlsCA, msp.ORDERER, false)
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}
// generate an admin for the orderer org
users := []NodeSpec{}
// add an admin user
users = append(users, adminUser)
generateNodes(usersDir, users, signCA, tlsCA, msp.CLIENT, false)
// copy the admin cert to the org's MSP admincerts
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
orgName, err)
os.Exit(1)
}
// copy the admin cert to each of the org's orderers's MSP admincerts
for _, spec := range orgSpec.Specs {
err = copyAdminCert(usersDir,
filepath.Join(orderersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s orderer %s:\n%v\n",
orgName, spec.CommonName, err)
os.Exit(1)
}
}
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
cerr := out.Close()
if err != nil {
return err
}
return cerr
}
func printVersion() {
fmt.Println(metadata.GetVersionInfo())
}
func getCA(caDir string, spec OrgSpec, name string) *ca.CA {
_, signer, _ := csp.LoadPrivateKey(caDir)
cert, _ := ca.LoadCertificateECDSA(caDir)
return &ca.CA{
Name: name,
Signer: signer,
SignCert: cert,
Country: spec.CA.Country,
Province: spec.CA.Province,
Locality: spec.CA.Locality,
OrganizationalUnit: spec.CA.OrganizationalUnit,
StreetAddress: spec.CA.StreetAddress,
PostalCode: spec.CA.PostalCode,
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/mirrors/hyperledger-fabric.git
git@gitee.com:mirrors/hyperledger-fabric.git
mirrors
hyperledger-fabric
hyperledger-fabric
v1.1.0

搜索帮助