From 373dc6d9dca91497d928088b81823a1f6c3c3544 Mon Sep 17 00:00:00 2001 From: David Enyeart Date: Wed, 21 Feb 2018 19:36:12 -0500 Subject: [PATCH] [FAB-8446] Add couchdb index validation to LSCC The existing validation for packaged couchdb indexes in chaincode was only getting called if peer CLI packaged the index during chaincode install (client side). Need the same validation logic called on server side of chaincocde install, so that the same validation logic will be in effect regardless of whether a peer CLI client or an SDK client performed the chaincode install. This implies making the same validation logic available in the LSCC chaincode install function. The benefit is that each client does not need to perform their own validation, they can rely on the server side LSCC validation, and any validation errors will get returned to them on the chaincode install failure response. This change ensures that the same validation is called during peer CLI packaging and during LSCC install chaincode. Change-Id: I44692141f6efe430fd5e298c8df7d59e519ce028 Signed-off-by: David Enyeart --- core/chaincode/platforms/golang/platform.go | 22 ++++--- .../cc_statedb_artifacts_provider.go | 40 +++++++++++ core/common/ccprovider/metadata/validators.go | 27 +++----- .../ccprovider/metadata/validators_test.go | 66 ++++++------------- core/container/util/writer.go | 39 ++++++----- .../statedb/statecouchdb/statecouchdb.go | 60 ++++++----------- core/scc/lscc/errors.go | 7 ++ core/scc/lscc/lscc.go | 32 +++++++++ core/scc/lscc/lscc_test.go | 43 +++++++----- 9 files changed, 192 insertions(+), 144 deletions(-) diff --git a/core/chaincode/platforms/golang/platform.go b/core/chaincode/platforms/golang/platform.go index c765af95832..4a16377d385 100644 --- a/core/chaincode/platforms/golang/platform.go +++ b/core/chaincode/platforms/golang/platform.go @@ -22,6 +22,7 @@ import ( "compress/gzip" "errors" "fmt" + "io/ioutil" "net/url" "os" "path/filepath" @@ -428,22 +429,21 @@ func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte for _, file := range files { + // file.Path represents os localpath + // file.Name represents tar packagepath + // If the file is metadata rather than golang code, remove the leading go code path, for example: // original file.Name: src/github.com/hyperledger/fabric/examples/chaincode/go/marbles02/META-INF/statedb/couchdb/indexes/indexOwner.json // updated file.Name: META-INF/statedb/couchdb/indexes/indexOwner.json if file.IsMetadata { - // Ensure META-INF directory can be found, then grab the META-INF relative path to use for packaging - if !strings.HasPrefix(file.Name, filepath.Join("src", code.Pkg, "META-INF")) { - return nil, fmt.Errorf("Could not find META-INF directory in metadata file %s.", file.Name) - } file.Name, err = filepath.Rel(filepath.Join("src", code.Pkg), file.Name) if err != nil { - return nil, fmt.Errorf("Could not get relative path for META-INF directory %s. Error:%s", file.Name, err) + return nil, fmt.Errorf("This error was caused by bad packaging of the metadata. The file [%s] is marked as MetaFile, however not located under META-INF Error:[%s]", file.Name, err) } - // Split the filename itself from its path - _, filename := filepath.Split(file.Name) + // Split the tar location (file.Name) into a tar package directory and filename + packageDir, filename := filepath.Split(file.Name) // Hidden files are not supported as metadata, therefore ignore them. // User often doesn't know that hidden files are there, and may not be able to delete them, therefore warn user rather than error out. @@ -452,9 +452,15 @@ func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte continue } + fileBytes, err := ioutil.ReadFile(file.Path) + if err != nil { + return nil, err + } + // Validate metadata file for inclusion in tar // Validation is based on the passed metadata directory, e.g. META-INF/statedb/couchdb/indexes - err = ccmetadata.ValidateMetadataFile(file.Path, filepath.Dir(file.Name)) + // Clean metadata directory to remove trailing slash + err = ccmetadata.ValidateMetadataFile(filename, fileBytes, filepath.Clean(packageDir)) if err != nil { return nil, err } diff --git a/core/common/ccprovider/cc_statedb_artifacts_provider.go b/core/common/ccprovider/cc_statedb_artifacts_provider.go index f19a13efa77..b145fb8c446 100644 --- a/core/common/ccprovider/cc_statedb_artifacts_provider.go +++ b/core/common/ccprovider/cc_statedb_artifacts_provider.go @@ -11,6 +11,8 @@ import ( "bytes" "compress/gzip" "io" + "io/ioutil" + "path/filepath" "strings" ) @@ -18,6 +20,12 @@ const ( ccPackageStatedbDir = "META-INF/statedb/" ) +// tarFileEntry encapsulates a file entry and it's contents inside a tar +type TarFileEntry struct { + FileHeader *tar.Header + FileContent []byte +} + // ExtractStatedbArtifactsAsTarbytes extracts the statedb artifacts from the code package tar and create a statedb artifact tar. // The state db artifacts are expected to contain state db specific artifacts such as index specification in the case of couchdb. // This function is intented to be used during chaincode instantiate/upgrade so that statedb artifacts can be created. @@ -83,3 +91,35 @@ func ExtractStatedbArtifactsFromCCPackage(ccpackage CCPackage) (statedbArtifacts ccproviderLogger.Debug("Created statedb artifact tar") return statedbTarBuffer.Bytes(), nil } + +// ExtractFileEntries extract file entries from the given `tarBytes`. A file entry is included in the +// returned results only if it is located in the dir specified in the `filterDirs` parameter +func ExtractFileEntries(tarBytes []byte, filterDirs map[string]bool) ([]*TarFileEntry, error) { + var fileEntries []*TarFileEntry + //initialize a tar reader + tarReader := tar.NewReader(bytes.NewReader(tarBytes)) + for { + //read the next header from the tar + tarHeader, err := tarReader.Next() + //if the EOF is detected, then exit + if err == io.EOF { + // end of tar archive + break + } + if err != nil { + return nil, err + } + ccproviderLogger.Debugf("Processing entry from tar: %s", tarHeader.Name) + //Ensure that this is a file located in the dir present in the 'filterDirs' + if !tarHeader.FileInfo().IsDir() && filterDirs[filepath.Dir(tarHeader.Name)] { + ccproviderLogger.Debugf("Selecting file entry from tar: %s", tarHeader.Name) + //read the tar entry into a byte array + fileContent, err := ioutil.ReadAll(tarReader) + if err != nil { + return nil, err + } + fileEntries = append(fileEntries, &TarFileEntry{tarHeader, fileContent}) + } + } + return fileEntries, nil +} diff --git a/core/common/ccprovider/metadata/validators.go b/core/common/ccprovider/metadata/validators.go index 92cc684fe51..bb6473cca87 100644 --- a/core/common/ccprovider/metadata/validators.go +++ b/core/common/ccprovider/metadata/validators.go @@ -9,7 +9,6 @@ package metadata import ( "encoding/json" "fmt" - "io/ioutil" "path/filepath" "reflect" "strings" @@ -20,7 +19,7 @@ import ( var logger = flogging.MustGetLogger("metadata") // fileValidators are used as handlers to validate specific metadata directories -type fileValidator func(srcPath string) error +type fileValidator func(fileName string, fileBytes []byte) error // Currently, the only metadata expected and allowed is for META-INF/statedb/couchdb/indexes. var fileValidators = map[string]fileValidator{ @@ -56,8 +55,7 @@ func (e *InvalidIndexContentError) Error() string { // ValidateMetadataFile checks that metadata files are valid // according to the validation rules of the metadata directory (metadataType) -func ValidateMetadataFile(srcPath, metadataType string) error { - +func ValidateMetadataFile(fileName string, fileBytes []byte, metadataType string) error { // Get the validator handler for the metadata directory fileValidator, ok := fileValidators[metadataType] @@ -66,8 +64,8 @@ func ValidateMetadataFile(srcPath, metadataType string) error { return &UnhandledDirectoryError{fmt.Sprintf("Metadata not supported in directory: %s", metadataType)} } - // If the file is not valid for the given metadata directory, return an error - err := fileValidator(srcPath) + // If the file is not valid for the given metadata directory, return the corresponding error + err := fileValidator(fileName, fileBytes) if err != nil { return err } @@ -77,30 +75,25 @@ func ValidateMetadataFile(srcPath, metadataType string) error { } // couchdbIndexFileValidator implements fileValidator -func couchdbIndexFileValidator(srcPath string) error { +func couchdbIndexFileValidator(fileName string, fileBytes []byte) error { - ext := filepath.Ext(srcPath) + ext := filepath.Ext(fileName) // if the file does not have a .json extension, then return as error if ext != ".json" { - return &BadExtensionError{fmt.Sprintf("Index metadata file [%s] does not have a .json extension", srcPath)} - } - - fileBytes, err := ioutil.ReadFile(srcPath) - if err != nil { - return err + return &BadExtensionError{fmt.Sprintf("Index metadata file [%s] does not have a .json extension", fileName)} } // if the content does not validate as JSON, return err to invalidate the file boolIsJSON, indexDefinition := isJSON(fileBytes) if !boolIsJSON { - return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid JSON", srcPath)} + return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid JSON", fileName)} } // validate the index definition - err = validateIndexJSON(indexDefinition) + err := validateIndexJSON(indexDefinition) if err != nil { - return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid index definition: %s", srcPath, err)} + return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid index definition: %s", fileName, err)} } return nil diff --git a/core/common/ccprovider/metadata/validators_test.go b/core/common/ccprovider/metadata/validators_test.go index fcf4b08f287..abee675caef 100644 --- a/core/common/ccprovider/metadata/validators_test.go +++ b/core/common/ccprovider/metadata/validators_test.go @@ -22,13 +22,11 @@ func TestGoodIndexJSON(t *testing.T) { cleanupDir(testDir) defer cleanupDir(testDir) - filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json") - filebytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) + fileName := "myIndex.json" + fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) + metadataType := "META-INF/statedb/couchdb/indexes" - err := writeToFile(filename, filebytes) - assert.NoError(t, err, "Error writing to file") - - err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") + err := ValidateMetadataFile(fileName, fileBytes, metadataType) assert.NoError(t, err, "Error validating a good index") } @@ -37,13 +35,11 @@ func TestBadIndexJSON(t *testing.T) { cleanupDir(testDir) defer cleanupDir(testDir) - filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json") - filebytes := []byte("invalid json") - - err := writeToFile(filename, filebytes) - assert.NoError(t, err, "Error writing to file") + fileName := "myIndex.json" + fileBytes := []byte("invalid json") + metadataType := "META-INF/statedb/couchdb/indexes" - err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") + err := ValidateMetadataFile(fileName, fileBytes, metadataType) assert.Error(t, err, "Should have received an InvalidIndexContentError") @@ -59,14 +55,12 @@ func TestIndexWrongLocation(t *testing.T) { cleanupDir(testDir) defer cleanupDir(testDir) + fileName := "myIndex.json" + fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) // place the index one directory too high - filename := filepath.Join(testDir, "META-INF/statedb/couchdb", "myIndex.json") - filebytes := []byte("invalid json") + metadataType := "META-INF/statedb/couchdb" - err := writeToFile(filename, filebytes) - assert.NoError(t, err, "Error writing to file") - - err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb") + err := ValidateMetadataFile(fileName, fileBytes, metadataType) assert.Error(t, err, "Should have received an UnhandledDirectoryError") // Type assertion on UnhandledDirectoryError @@ -81,13 +75,11 @@ func TestInvalidMetadataType(t *testing.T) { cleanupDir(testDir) defer cleanupDir(testDir) - filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json") - filebytes := []byte("invalid json") - - err := writeToFile(filename, filebytes) - assert.NoError(t, err, "Error writing to file") + fileName := "myIndex.json" + fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) + metadataType := "Invalid metadata type" - err = ValidateMetadataFile(filename, "Invalid metadata type") + err := ValidateMetadataFile(fileName, fileBytes, metadataType) assert.Error(t, err, "Should have received an UnhandledDirectoryError") // Type assertion on UnhandledDirectoryError @@ -95,34 +87,16 @@ func TestInvalidMetadataType(t *testing.T) { assert.True(t, ok, "Should have received an UnhandledDirectoryError") } -func TestCantReadFile(t *testing.T) { - testDir := filepath.Join(packageTestDir, "CantReadFile") - cleanupDir(testDir) - defer cleanupDir(testDir) - - filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json") - - // Don't write the file - test for can't read file - // err := writeToFile(filename, filebytes) - // assert.NoError(t, err, "Error writing to file") - - err := ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") - assert.Error(t, err, "Should have received error reading file") - -} - func TestBadMetadataExtension(t *testing.T) { testDir := filepath.Join(packageTestDir, "BadMetadataExtension") cleanupDir(testDir) defer cleanupDir(testDir) - filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.go") - filebytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) - - err := writeToFile(filename, filebytes) - assert.NoError(t, err, "Error writing to file") + fileName := "myIndex.go" + fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`) + metadataType := "META-INF/statedb/couchdb/indexes" - err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") + err := ValidateMetadataFile(fileName, fileBytes, metadataType) assert.Error(t, err, "Should have received an BadExtensionError") // Type assertion on BadExtensionError diff --git a/core/container/util/writer.go b/core/container/util/writer.go index c0a76aaa902..a4be85e3ed7 100644 --- a/core/container/util/writer.go +++ b/core/container/util/writer.go @@ -11,13 +11,14 @@ import ( "bufio" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" "time" "github.com/hyperledger/fabric/common/flogging" - ccmetadata "github.com/hyperledger/fabric/core/common/ccprovider/metadata" + "github.com/hyperledger/fabric/core/common/ccprovider/metadata" "github.com/pkg/errors" ) @@ -49,10 +50,10 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, } rootDirLen := len(rootDirectory) - walkFn := func(path string, info os.FileInfo, err error) error { + walkFn := func(localpath string, info os.FileInfo, err error) error { - // If path includes .git, ignore - if strings.Contains(path, ".git") { + // If localpath includes .git, ignore + if strings.Contains(localpath, ".git") { return nil } @@ -61,15 +62,15 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, } //exclude any files with excludeDir prefix. They should already be in the tar - if excludeDir != "" && strings.Index(path, excludeDir) == rootDirLen+1 { + if excludeDir != "" && strings.Index(localpath, excludeDir) == rootDirLen+1 { //1 for "/" return nil } // Because of scoping we can reference the external rootDirectory variable - if len(path[rootDirLen:]) == 0 { + if len(localpath[rootDirLen:]) == 0 { return nil } - ext := filepath.Ext(path) + ext := filepath.Ext(localpath) if includeFileTypeMap != nil { // we only want 'fileTypes' source files at this point @@ -85,35 +86,41 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, } } - var newPath string + var packagepath string // if file is metadata, keep the /META-INF directory, e.g: META-INF/statedb/couchdb/indexes/indexOwner.json // otherwise file is source code, put it in /src dir, e.g: src/marbles_chaincode.js - if strings.HasPrefix(path, filepath.Join(rootDirectory, "META-INF")) { - newPath = path[rootDirLen+1:] + if strings.HasPrefix(localpath, filepath.Join(rootDirectory, "META-INF")) { + packagepath = localpath[rootDirLen+1:] - // Split the filename itself from its path - _, filename := filepath.Split(newPath) + // Split the tar packagepath into a tar package directory and filename + packageDir, filename := filepath.Split(packagepath) // Hidden files are not supported as metadata, therefore ignore them. // User often doesn't know that hidden files are there, and may not be able to delete them, therefore warn user rather than error out. if strings.HasPrefix(filename, ".") { - vmLogger.Warningf("Ignoring hidden file in metadata directory: %s", newPath) + vmLogger.Warningf("Ignoring hidden file in metadata directory: %s", packagepath) return nil } + fileBytes, err := ioutil.ReadFile(localpath) + if err != nil { + return err + } + // Validate metadata file for inclusion in tar // Validation is based on the passed metadata directory, e.g. META-INF/statedb/couchdb/indexes - err = ccmetadata.ValidateMetadataFile(path, filepath.Dir(newPath)) + // Clean metadata directory to remove trailing slash + err = metadata.ValidateMetadataFile(filename, fileBytes, filepath.Clean(packageDir)) if err != nil { return err } } else { // file is not metadata, include in src - newPath = fmt.Sprintf("src%s", path[rootDirLen:]) + packagepath = fmt.Sprintf("src%s", localpath[rootDirLen:]) } - err = WriteFileToPackage(path, newPath, tw) + err = WriteFileToPackage(localpath, packagepath, tw) if err != nil { return fmt.Errorf("Error writing file to package: %s", err) } diff --git a/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go b/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go index 92e86a4f04e..61fc47444b6 100644 --- a/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go +++ b/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go @@ -6,20 +6,17 @@ SPDX-License-Identifier: Apache-2.0 package statecouchdb import ( - "archive/tar" "bytes" "encoding/json" "errors" "fmt" - "io" - "io/ioutil" - "path/filepath" "strconv" "strings" "sync" "unicode/utf8" "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/core/common/ccprovider" "github.com/hyperledger/fabric/core/ledger/cceventmgmt" "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" @@ -40,6 +37,8 @@ const ( var reservedFields = []string{idField, revField, versionField, deletedField} +var dbArtifactsDirFilter = map[string]bool{"META-INF/statedb/couchdb/indexes": true} + // querySkip is implemented for future use by query paging // currently defaulted to 0 and is not used const querySkip = 0 @@ -98,45 +97,26 @@ func (vdb *VersionedDB) HandleChaincodeDeploy(chaincodeDefinition *cceventmgmt.C if err != nil { return err } - //initialize a reader for the artifacts tar file - artifactReader := bytes.NewReader(dbArtifactsTar) - tarReader := tar.NewReader(artifactReader) - for { - //read the next header from the tar - tarHeader, err := tarReader.Next() - //if the EOF is detected, then exit - if err == io.EOF { - // end of tar archive - return nil - } - if err != nil { - logger.Errorf("Error during reading db artifacts from tar file for chaincode=[%s] on chain=[%s]. Error=%s", - chaincodeDefinition, vdb.chainName, err) - return nil - } - logger.Debugf("Reading artifact from file: %s", tarHeader.Name) - //Ensure that this is not a directory - if !tarHeader.FileInfo().IsDir() { - //split the filename into directory and file name - dir, file := filepath.Split(tarHeader.Name) - if dir == "META-INF/statedb/couchdb/indexes/" { - logger.Debugf("Creating index from file: %s", file) - //read the tar entry into a byte array - indexData, err := ioutil.ReadAll(tarReader) - if err != nil { - logger.Errorf("Error during extracting db artifacts file=[%s] from tar for chaincode=[%s] on chain=[%s]. Error=%s", - tarHeader.Name, chaincodeDefinition, vdb.chainName, err) - return nil - } - //create the index from the tar entry - if _, err := db.CreateIndex(string(indexData)); err != nil { - logger.Errorf("Error during creation of index from file=[%s] for chaincode=[%s] on chain=[%s]. Error=%s", - tarHeader.Name, chaincodeDefinition, vdb.chainName, err) - } - } + fileEntries, err := ccprovider.ExtractFileEntries(dbArtifactsTar, dbArtifactsDirFilter) + if err != nil { + logger.Errorf("Error during extracting db artifacts from tar for chaincode=[%s] on chain=[%s]. Error=%s", + chaincodeDefinition, vdb.chainName, err) + return nil + } + + for _, fileEntry := range fileEntries { + indexData := fileEntry.FileContent + filename := fileEntry.FileHeader.Name + _, err = db.CreateIndex(string(indexData)) + if err != nil { + logger.Errorf("Error during creation of index from file=[%s] for chaincode=[%s] on chain=[%s]. Error=%s", + filename, chaincodeDefinition, vdb.chainName, err) } } + + return nil + } // GetDBHandle gets the handle to a named database diff --git a/core/scc/lscc/errors.go b/core/scc/lscc/errors.go index 5b896b5ea67..3a6aa3197a0 100644 --- a/core/scc/lscc/errors.go +++ b/core/scc/lscc/errors.go @@ -78,6 +78,13 @@ func (f InvalidVersionErr) Error() string { return fmt.Sprintf("invalid chaincode version '%s'. Versions can only consist of alphanumerics, '_', '-', '+', and '.'", string(f)) } +//InvalidStatedbArtifactsErr invalid state database artifacts error +type InvalidStatedbArtifactsErr string + +func (f InvalidStatedbArtifactsErr) Error() string { + return fmt.Sprintf("invalid state database artifact: %s", string(f)) +} + //ChaincodeMismatchErr chaincode name from two places don't match type ChaincodeMismatchErr string diff --git a/core/scc/lscc/lscc.go b/core/scc/lscc/lscc.go index 13cb8e6ae0f..71cc53dc8f4 100644 --- a/core/scc/lscc/lscc.go +++ b/core/scc/lscc/lscc.go @@ -18,6 +18,7 @@ package lscc import ( "fmt" + "path/filepath" "regexp" "github.com/golang/protobuf/proto" @@ -27,6 +28,7 @@ import ( "github.com/hyperledger/fabric/core/aclmgmt/resources" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/common/ccprovider" + ccmetadata "github.com/hyperledger/fabric/core/common/ccprovider/metadata" "github.com/hyperledger/fabric/core/common/privdata" "github.com/hyperledger/fabric/core/common/sysccprovider" "github.com/hyperledger/fabric/core/ledger/cceventmgmt" @@ -363,6 +365,32 @@ func isValidCCNameOrVersion(ccNameOrVersion string, regExp string) bool { return true } +func isValidStatedbArtifactsTar(statedbArtifactsTar []byte) error { + + var dbArtifactsDirFilter = map[string]bool{"META-INF/statedb/couchdb/indexes": true} + + // Extract the metadata files from the archive + fileEntries, err := ccprovider.ExtractFileEntries(statedbArtifactsTar, dbArtifactsDirFilter) + if err != nil { + return err + } + + // iterate through the files and validate + for _, fileEntry := range fileEntries { + indexData := fileEntry.FileContent + tarDir, filename := filepath.Split(fileEntry.FileHeader.Name) + + // Validation is based on the passed metadata directory, e.g. META-INF/statedb/couchdb/indexes + // Clean metadata directory to remove trailing slash + err = ccmetadata.ValidateMetadataFile(filename, indexData, filepath.Clean(tarDir)) + if err != nil { + return err + } + } + + return nil +} + // executeInstall implements the "install" Invoke transaction func (lscc *lifeCycleSysCC) executeInstall(stub shim.ChaincodeStubInterface, ccbytes []byte) error { ccpack, err := ccprovider.GetCCPackage(ccbytes) @@ -390,6 +418,10 @@ func (lscc *lifeCycleSysCC) executeInstall(stub shim.ChaincodeStubInterface, ccb return err } + if err = isValidStatedbArtifactsTar(statedbArtifactsTar); err != nil { + return InvalidStatedbArtifactsErr(err.Error()) + } + chaincodeDefinition := &cceventmgmt.ChaincodeDefinition{ Name: ccpack.GetChaincodeData().Name, Version: ccpack.GetChaincodeData().Version, diff --git a/core/scc/lscc/lscc_test.go b/core/scc/lscc/lscc_test.go index f3f11dac9ec..e53536f3fe7 100644 --- a/core/scc/lscc/lscc_test.go +++ b/core/scc/lscc/lscc_test.go @@ -42,7 +42,7 @@ import ( "github.com/stretchr/testify/assert" ) -func constructDeploymentSpec(name string, path string, version string, initArgs [][]byte, createFS bool, scc *lifeCycleSysCC) (*pb.ChaincodeDeploymentSpec, error) { +func constructDeploymentSpec(name string, path string, version string, initArgs [][]byte, createInvalidIndex bool, createFS bool, scc *lifeCycleSysCC) (*pb.ChaincodeDeploymentSpec, error) { spec := &pb.ChaincodeSpec{Type: 1, ChaincodeId: &pb.ChaincodeID{Name: name, Path: path, Version: version}, Input: &pb.ChaincodeInput{Args: initArgs}} codePackageBytes := bytes.NewBuffer(nil) @@ -54,6 +54,14 @@ func constructDeploymentSpec(name string, path string, version string, initArgs return nil, err } + // create an invalid couchdb index definition for negative testing + if createInvalidIndex { + err = cutil.WriteBytesToPackage("META-INF/statedb/couchdb/indexes/badIndex.json", []byte("invalid index definition"), tw) + if err != nil { + return nil, err + } + } + tw.Close() gz.Close() @@ -106,21 +114,22 @@ func TestInstall(t *testing.T) { path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" - testInstall(t, "example02", "0", path, "", "Alice", scc, stub) - testInstall(t, "example02-2", "1.0", path, "", "Alice", scc, stub) - testInstall(t, "example02.go", "0", path, InvalidChaincodeNameErr("example02.go").Error(), "Alice", scc, stub) - testInstall(t, "", "0", path, EmptyChaincodeNameErr("").Error(), "Alice", scc, stub) - testInstall(t, "example02", "1{}0", path, InvalidVersionErr("1{}0").Error(), "Alice", scc, stub) - testInstall(t, "example02", "0", path, "Authorization for INSTALL has been denied", "Bob", scc, stub) - testInstall(t, "example02-2", "1.0-alpha+001", path, "", "Alice", scc, stub) - testInstall(t, "example02-2", "1.0+sha.c0ffee", path, "", "Alice", scc, stub) + testInstall(t, "example02", "0", path, false, "", "Alice", scc, stub) + testInstall(t, "example02-2", "1.0", path, false, "", "Alice", scc, stub) + testInstall(t, "example02.go", "0", path, false, InvalidChaincodeNameErr("example02.go").Error(), "Alice", scc, stub) + testInstall(t, "", "0", path, false, EmptyChaincodeNameErr("").Error(), "Alice", scc, stub) + testInstall(t, "example02", "1{}0", path, false, InvalidVersionErr("1{}0").Error(), "Alice", scc, stub) + testInstall(t, "example02", "0", path, true, InvalidStatedbArtifactsErr("").Error(), "Alice", scc, stub) + testInstall(t, "example02", "0", path, false, "Authorization for INSTALL has been denied", "Bob", scc, stub) + testInstall(t, "example02-2", "1.0-alpha+001", path, false, "", "Alice", scc, stub) + testInstall(t, "example02-2", "1.0+sha.c0ffee", path, false, "", "Alice", scc, stub) scc.support.(*lscc.MockSupport).PutChaincodeToLocalStorageErr = errors.New("barf") - testInstall(t, "example02", "0", path, "barf", "Alice", scc, stub) + testInstall(t, "example02", "0", path, false, "barf", "Alice", scc, stub) } -func testInstall(t *testing.T, ccname string, version string, path string, expectedErrorMsg string, caller string, scc *lifeCycleSysCC, stub *shim.MockStub) { +func testInstall(t *testing.T, ccname string, version string, path string, createInvalidIndex bool, expectedErrorMsg string, caller string, scc *lifeCycleSysCC, stub *shim.MockStub) { identityDeserializer := &policymocks.MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")} policyManagerGetter := &policymocks.MockChannelPolicyManagerGetter{ Managers: map[string]policies.Manager{ @@ -133,7 +142,7 @@ func testInstall(t *testing.T, ccname string, version string, path string, expec &policymocks.MockMSPPrincipalGetter{Principal: []byte("Alice")}, ) - cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, scc) + cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, createInvalidIndex, false, scc) assert.NoError(t, err) b := utils.MarshalOrPanic(cds) @@ -238,7 +247,7 @@ func testDeploy(t *testing.T, ccname string, version string, path string, forceB identityDeserializer.Msg = sProp.ProposalBytes sProp.Signature = sProp.ProposalBytes - cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, install, scc) + cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, install, scc) assert.NoError(t, err) if forceBlankCCName { @@ -350,7 +359,7 @@ func testUpgrade(t *testing.T, ccname string, version string, newccname string, scc.support.(*lscc.MockSupport).GetInstantiationPolicyRv = []byte("instantiation policy") } - cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true, scc) + cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, true, scc) assert.NoError(t, err) b := utils.MarshalOrPanic(cds) @@ -365,7 +374,7 @@ func testUpgrade(t *testing.T, ccname string, version string, newccname string, scc.support.(*lscc.MockSupport).GetInstantiationPolicyErr = saved1 scc.support.(*lscc.MockSupport).CheckInstantiationPolicyMap = saved2 - newCds, err := constructDeploymentSpec(newccname, path, newversion, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true, scc) + newCds, err := constructDeploymentSpec(newccname, path, newversion, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, true, scc) assert.NoError(t, err) newb := utils.MarshalOrPanic(newCds) @@ -495,13 +504,13 @@ func TestGETINSTALLEDCHAINCODES(t *testing.T) { res = stub.MockInvokeWithSignedProposal("1", [][]byte{[]byte(GETINSTALLEDCHAINCODES)}, sProp) assert.NotEqual(t, res.Status, int32(shim.OK), res.Message) - _, err := constructDeploymentSpec("ccname", "path", "version", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, scc) + _, err := constructDeploymentSpec("ccname", "path", "version", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, false, scc) assert.NoError(t, err) res = stub.MockInvokeWithSignedProposal("1", [][]byte{[]byte(GETINSTALLEDCHAINCODES)}, sProp) assert.NotEqual(t, res.Status, int32(shim.OK), res.Message) - _, err = constructDeploymentSpec("ccname", "path", "version", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true, scc) + _, err = constructDeploymentSpec("ccname", "path", "version", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false, true, scc) assert.NoError(t, err) res = stub.MockInvokeWithSignedProposal("1", [][]byte{[]byte(GETINSTALLEDCHAINCODES)}, sProp)