Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-7715] Handle invalid couchdb index defs
Invalid CouchDB index definitions may be packaged into chaincode installation, causing runtime problems when the chaincode is installed/instantiated. This change - Adds metadata validation for golang and node.js chaincode packaging - Adds index JSON validation for files in META-INF/statedb/couchdb/indexes. - Ensures no metadata files are packaged from other META-INF directories. Change-Id: I6dc5f7cbb0ae48edfc29824813bf9f5ccaf17b1c Signed-off-by: David Enyeart <enyeart@us.ibm.com>
- Loading branch information
Showing
12 changed files
with
417 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package metadata | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
|
||
"github.com/hyperledger/fabric/common/flogging" | ||
) | ||
|
||
var logger = flogging.MustGetLogger("metadata") | ||
|
||
// fileValidators are used as handlers to validate specific metadata directories | ||
type fileValidator func(srcPath string) error | ||
|
||
// Currently, the only metadata expected and allowed is for META-INF/statedb/couchdb/indexes. | ||
var fileValidators = map[string]fileValidator{ | ||
"META-INF/statedb/couchdb/indexes": couchdbIndexFileValidator, | ||
} | ||
|
||
// UnhandledDirectoryError is returned for metadata files in unhandled directories | ||
type UnhandledDirectoryError struct { | ||
err string | ||
} | ||
|
||
func (e *UnhandledDirectoryError) Error() string { | ||
return e.err | ||
} | ||
|
||
// InvalidFileError is returned for invalid metadata files | ||
type InvalidFileError struct { | ||
err string | ||
} | ||
|
||
func (e *InvalidFileError) Error() string { | ||
return e.err | ||
} | ||
|
||
// ValidateMetadataFile checks that metadata files are valid | ||
// according to the validation rules of the metadata directory (metadataType) | ||
func ValidateMetadataFile(srcPath, metadataType string) error { | ||
// Get the validator handler for the metadata directory | ||
fileValidator, ok := fileValidators[metadataType] | ||
|
||
// If there is no validator handler for metadata directory, return UnhandledDirectoryError | ||
if !ok { | ||
return &UnhandledDirectoryError{fmt.Sprintf("Metadata not supported in directory: %s", metadataType)} | ||
} | ||
|
||
// If the file is not valid for the given metadata directory, return InvalidFileError | ||
err := fileValidator(srcPath) | ||
if err != nil { | ||
return &InvalidFileError{fmt.Sprintf("Metadata file [%s] failed validation: %s", srcPath, err)} | ||
} | ||
|
||
// file is valid, return nil error | ||
return nil | ||
} | ||
|
||
// couchdbIndexFileValidator implements fileValidator | ||
func couchdbIndexFileValidator(srcPath string) error { | ||
fileBytes, err := ioutil.ReadFile(srcPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// if the content does not validate as JSON, return err to invalidate the file | ||
if !isJSON(string(fileBytes)) { | ||
return errors.New("File is not valid JSON") | ||
} | ||
|
||
// TODO Additional validation to ensure the JSON represents a valid couchdb index definition | ||
|
||
// file is a valid couchdb index definition, return nil error | ||
return nil | ||
} | ||
|
||
// isJSON tests a string to determine if it can be parsed as valid JSON | ||
func isJSON(s string) bool { | ||
var js map[string]interface{} | ||
return json.Unmarshal([]byte(s), &js) == nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package metadata | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var packageTestDir = filepath.Join(os.TempDir(), "ccmetadata-validator-test") | ||
|
||
func TestGoodIndexJSON(t *testing.T) { | ||
testDir := filepath.Join(packageTestDir, "GoodIndexJSON") | ||
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"}`) | ||
|
||
err := writeToFile(filename, filebytes) | ||
assert.NoError(t, err, "Error writing to file") | ||
|
||
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") | ||
assert.NoError(t, err, "Error validating a good index") | ||
} | ||
|
||
func TestBadIndexJSON(t *testing.T) { | ||
testDir := filepath.Join(packageTestDir, "BadIndexJSON") | ||
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") | ||
|
||
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes") | ||
|
||
assert.Error(t, err, "Should have received an InvalidFileError") | ||
|
||
// Type assertion on InvalidFileError | ||
_, ok := err.(*InvalidFileError) | ||
assert.True(t, ok, "Should have received an InvalidFileError") | ||
|
||
t.Log("SAMPLE ERROR STRING:", err.Error()) | ||
} | ||
|
||
func TestIndexWrongLocation(t *testing.T) { | ||
testDir := filepath.Join(packageTestDir, "IndexWrongLocation") | ||
cleanupDir(testDir) | ||
defer cleanupDir(testDir) | ||
|
||
// place the index one directory too high | ||
filename := filepath.Join(testDir, "META-INF/statedb/couchdb", "myIndex.json") | ||
filebytes := []byte("invalid json") | ||
|
||
err := writeToFile(filename, filebytes) | ||
assert.NoError(t, err, "Error writing to file") | ||
|
||
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb") | ||
assert.Error(t, err, "Should have received an UnhandledDirectoryError") | ||
|
||
// Type assertion on UnhandledDirectoryError | ||
_, ok := err.(*UnhandledDirectoryError) | ||
assert.True(t, ok, "Should have received an UnhandledDirectoryError") | ||
|
||
t.Log("SAMPLE ERROR STRING:", err.Error()) | ||
} | ||
|
||
func TestInvalidMetadataType(t *testing.T) { | ||
testDir := filepath.Join(packageTestDir, "InvalidMetadataType") | ||
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") | ||
|
||
err = ValidateMetadataFile(filename, "Invalid metadata type") | ||
assert.Error(t, err, "Should have received an UnhandledDirectoryError") | ||
|
||
// Type assertion on UnhandledDirectoryError | ||
_, ok := err.(*UnhandledDirectoryError) | ||
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 cleanupDir(dir string) error { | ||
// clean up any previous files | ||
err := os.RemoveAll(dir) | ||
if err != nil { | ||
return nil | ||
} | ||
return os.Mkdir(dir, os.ModePerm) | ||
} | ||
|
||
func writeToFile(filename string, bytes []byte) error { | ||
dir := filepath.Dir(filename) | ||
err := os.MkdirAll(dir, os.ModePerm) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return ioutil.WriteFile(filename, bytes, 0644) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.