github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/core/chaincode/platforms/golang/platform.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package golang
    18  
    19  import (
    20  	"archive/tar"
    21  	"bytes"
    22  	"compress/gzip"
    23  	"errors"
    24  	"fmt"
    25  	"net/url"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"sort"
    32  
    33  	"github.com/hyperledger/fabric/core/chaincode/platforms/util"
    34  	cutil "github.com/hyperledger/fabric/core/container/util"
    35  	pb "github.com/hyperledger/fabric/protos/peer"
    36  )
    37  
    38  // Platform for chaincodes written in Go
    39  type Platform struct {
    40  }
    41  
    42  // Returns whether the given file or directory exists or not
    43  func pathExists(path string) (bool, error) {
    44  	_, err := os.Stat(path)
    45  	if err == nil {
    46  		return true, nil
    47  	}
    48  	if os.IsNotExist(err) {
    49  		return false, nil
    50  	}
    51  	return true, err
    52  }
    53  
    54  func decodeUrl(spec *pb.ChaincodeSpec) (string, error) {
    55  	var urlLocation string
    56  	if strings.HasPrefix(spec.ChaincodeId.Path, "http://") {
    57  		urlLocation = spec.ChaincodeId.Path[7:]
    58  	} else if strings.HasPrefix(spec.ChaincodeId.Path, "https://") {
    59  		urlLocation = spec.ChaincodeId.Path[8:]
    60  	} else {
    61  		urlLocation = spec.ChaincodeId.Path
    62  	}
    63  
    64  	if len(urlLocation) < 2 {
    65  		return "", errors.New("ChaincodeSpec's path/URL invalid")
    66  	}
    67  
    68  	if strings.LastIndex(urlLocation, "/") == len(urlLocation)-1 {
    69  		urlLocation = urlLocation[:len(urlLocation)-1]
    70  	}
    71  
    72  	return urlLocation, nil
    73  }
    74  
    75  func getGopath() (string, error) {
    76  	env, err := getGoEnv()
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  	// Only take the first element of GOPATH
    81  	splitGoPath := filepath.SplitList(env["GOPATH"])
    82  	if len(splitGoPath) == 0 {
    83  		return "", fmt.Errorf("invalid GOPATH environment variable value:[%s]", env["GOPATH"])
    84  	}
    85  	return splitGoPath[0], nil
    86  }
    87  
    88  func filter(vs []string, f func(string) bool) []string {
    89  	vsf := make([]string, 0)
    90  	for _, v := range vs {
    91  		if f(v) {
    92  			vsf = append(vsf, v)
    93  		}
    94  	}
    95  	return vsf
    96  }
    97  
    98  // ValidateSpec validates Go chaincodes
    99  func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error {
   100  	path, err := url.Parse(spec.ChaincodeId.Path)
   101  	if err != nil || path == nil {
   102  		return fmt.Errorf("invalid path: %s", err)
   103  	}
   104  
   105  	//we have no real good way of checking existence of remote urls except by downloading and testin
   106  	//which we do later anyway. But we *can* - and *should* - test for existence of local paths.
   107  	//Treat empty scheme as a local filesystem path
   108  	if path.Scheme == "" {
   109  		gopath, err := getGopath()
   110  		if err != nil {
   111  			return err
   112  		}
   113  		pathToCheck := filepath.Join(gopath, "src", spec.ChaincodeId.Path)
   114  		exists, err := pathExists(pathToCheck)
   115  		if err != nil {
   116  			return fmt.Errorf("error validating chaincode path: %s", err)
   117  		}
   118  		if !exists {
   119  			return fmt.Errorf("path to chaincode does not exist: %s", spec.ChaincodeId.Path)
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  func (goPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error {
   126  
   127  	if cds.CodePackage == nil || len(cds.CodePackage) == 0 {
   128  		// Nothing to validate if no CodePackage was included
   129  		return nil
   130  	}
   131  
   132  	// FAB-2122: Scan the provided tarball to ensure it only contains source-code under
   133  	// /src/$packagename.  We do not want to allow something like ./pkg/shady.a to be installed under
   134  	// $GOPATH within the container.  Note, we do not look deeper than the path at this time
   135  	// with the knowledge that only the go/cgo compiler will execute for now.  We will remove the source
   136  	// from the system after the compilation as an extra layer of protection.
   137  	//
   138  	// It should be noted that we cannot catch every threat with these techniques.  Therefore,
   139  	// the container itself needs to be the last line of defense and be configured to be
   140  	// resilient in enforcing constraints. However, we should still do our best to keep as much
   141  	// garbage out of the system as possible.
   142  	re := regexp.MustCompile(`(/)?src/.*`)
   143  	is := bytes.NewReader(cds.CodePackage)
   144  	gr, err := gzip.NewReader(is)
   145  	if err != nil {
   146  		return fmt.Errorf("failure opening codepackage gzip stream: %s", err)
   147  	}
   148  	tr := tar.NewReader(gr)
   149  
   150  	for {
   151  		header, err := tr.Next()
   152  		if err != nil {
   153  			// We only get here if there are no more entries to scan
   154  			break
   155  		}
   156  
   157  		// --------------------------------------------------------------------------------------
   158  		// Check name for conforming path
   159  		// --------------------------------------------------------------------------------------
   160  		if !re.MatchString(header.Name) {
   161  			return fmt.Errorf("illegal file detected in payload: \"%s\"", header.Name)
   162  		}
   163  
   164  		// --------------------------------------------------------------------------------------
   165  		// Check that file mode makes sense
   166  		// --------------------------------------------------------------------------------------
   167  		// Acceptable flags:
   168  		//      ISREG      == 0100000
   169  		//      -rw-rw-rw- == 0666
   170  		//
   171  		// Anything else is suspect in this context and will be rejected
   172  		// --------------------------------------------------------------------------------------
   173  		if header.Mode&^0100666 != 0 {
   174  			return fmt.Errorf("illegal file mode detected for file %s: %o", header.Name, header.Mode)
   175  		}
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // Vendor any packages that are not already within our chaincode's primary package
   182  // or vendored by it.  We take the name of the primary package and a list of files
   183  // that have been previously determined to comprise the package's dependencies.
   184  // For anything that needs to be vendored, we simply update its path specification.
   185  // Everything else, we pass through untouched.
   186  func vendorDependencies(pkg string, files Sources) {
   187  
   188  	exclusions := make([]string, 0)
   189  	elements := strings.Split(pkg, "/")
   190  
   191  	// --------------------------------------------------------------------------------------
   192  	// First, add anything already vendored somewhere within our primary package to the
   193  	// "exclusions".  For a package "foo/bar/baz", we want to ensure we don't auto-vendor
   194  	// any of the following:
   195  	//
   196  	//     [ "foo/vendor", "foo/bar/vendor", "foo/bar/baz/vendor"]
   197  	//
   198  	// and we therefore employ a recursive path building process to form this list
   199  	// --------------------------------------------------------------------------------------
   200  	prev := filepath.Join("src")
   201  	for _, element := range elements {
   202  		curr := filepath.Join(prev, element)
   203  		vendor := filepath.Join(curr, "vendor")
   204  		exclusions = append(exclusions, vendor)
   205  		prev = curr
   206  	}
   207  
   208  	// --------------------------------------------------------------------------------------
   209  	// Next add our primary package to the list of "exclusions"
   210  	// --------------------------------------------------------------------------------------
   211  	exclusions = append(exclusions, filepath.Join("src", pkg))
   212  
   213  	count := len(files)
   214  	sem := make(chan bool, count)
   215  
   216  	// --------------------------------------------------------------------------------------
   217  	// Now start a parallel process which checks each file in files to see if it matches
   218  	// any of the excluded patterns.  Any that match are renamed such that they are vendored
   219  	// under src/$pkg/vendor.
   220  	// --------------------------------------------------------------------------------------
   221  	vendorPath := filepath.Join("src", pkg, "vendor")
   222  	for i, file := range files {
   223  		go func(i int, file SourceDescriptor) {
   224  			excluded := false
   225  
   226  			for _, exclusion := range exclusions {
   227  				if strings.HasPrefix(file.Name, exclusion) == true {
   228  					excluded = true
   229  					break
   230  				}
   231  			}
   232  
   233  			if excluded == false {
   234  				origName := file.Name
   235  				file.Name = strings.Replace(origName, "src", vendorPath, 1)
   236  				logger.Debugf("vendoring %s -> %s", origName, file.Name)
   237  			}
   238  
   239  			files[i] = file
   240  			sem <- true
   241  		}(i, file)
   242  	}
   243  
   244  	for i := 0; i < count; i++ {
   245  		<-sem
   246  	}
   247  }
   248  
   249  // Generates a deployment payload for GOLANG as a series of src/$pkg entries in .tar.gz format
   250  func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
   251  
   252  	var err error
   253  
   254  	// --------------------------------------------------------------------------------------
   255  	// retrieve a CodeDescriptor from either HTTP or the filesystem
   256  	// --------------------------------------------------------------------------------------
   257  	code, err := getCode(spec)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	if code.Cleanup != nil {
   262  		defer code.Cleanup()
   263  	}
   264  
   265  	// --------------------------------------------------------------------------------------
   266  	// Update our environment for the purposes of executing go-list directives
   267  	// --------------------------------------------------------------------------------------
   268  	env, err := getGoEnv()
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	gopaths := splitEnvPaths(env["GOPATH"])
   273  	goroots := splitEnvPaths(env["GOROOT"])
   274  	gopaths[code.Gopath] = true
   275  	env["GOPATH"] = flattenEnvPaths(gopaths)
   276  
   277  	// --------------------------------------------------------------------------------------
   278  	// Retrieve the list of first-order imports referenced by the chaincode
   279  	// --------------------------------------------------------------------------------------
   280  	imports, err := listImports(env, code.Pkg)
   281  	if err != nil {
   282  		return nil, fmt.Errorf("Error obtaining imports: %s", err)
   283  	}
   284  
   285  	// --------------------------------------------------------------------------------------
   286  	// Remove any imports that are provided by the ccenv or system
   287  	// --------------------------------------------------------------------------------------
   288  	var provided = map[string]bool{
   289  		"github.com/hyperledger/fabric/core/chaincode/shim": true,
   290  		"github.com/hyperledger/fabric/protos/peer":         true,
   291  	}
   292  
   293  	imports = filter(imports, func(pkg string) bool {
   294  		// Drop if provided by CCENV
   295  		if _, ok := provided[pkg]; ok == true {
   296  			logger.Debugf("Discarding provided package %s", pkg)
   297  			return false
   298  		}
   299  
   300  		// Drop if provided by GOROOT
   301  		for goroot := range goroots {
   302  			fqp := filepath.Join(goroot, "src", pkg)
   303  			exists, err := pathExists(fqp)
   304  			if err == nil && exists {
   305  				logger.Debugf("Discarding GOROOT package %s", pkg)
   306  				return false
   307  			}
   308  		}
   309  
   310  		// Else, we keep it
   311  		logger.Debugf("Accepting import: %s", pkg)
   312  		return true
   313  	})
   314  
   315  	// --------------------------------------------------------------------------------------
   316  	// Assemble the fully resolved list of direct and transitive dependencies based on the
   317  	// imports that remain after filtering
   318  	// --------------------------------------------------------------------------------------
   319  	deps := make(map[string]bool)
   320  
   321  	for _, pkg := range imports {
   322  		// ------------------------------------------------------------------------------
   323  		// Resolve direct import's transitives
   324  		// ------------------------------------------------------------------------------
   325  		transitives, err := listDeps(env, pkg)
   326  		if err != nil {
   327  			return nil, fmt.Errorf("Error obtaining dependencies for %s: %s", pkg, err)
   328  		}
   329  
   330  		// ------------------------------------------------------------------------------
   331  		// Merge all results with our top list
   332  		// ------------------------------------------------------------------------------
   333  
   334  		// Merge direct dependency...
   335  		deps[pkg] = true
   336  
   337  		// .. and then all transitives
   338  		for _, dep := range transitives {
   339  			deps[dep] = true
   340  		}
   341  	}
   342  
   343  	// cull "" if it exists
   344  	delete(deps, "")
   345  
   346  	// --------------------------------------------------------------------------------------
   347  	// Find the source from our first-order code package ...
   348  	// --------------------------------------------------------------------------------------
   349  	fileMap, err := findSource(code.Gopath, code.Pkg)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	// --------------------------------------------------------------------------------------
   355  	// ... followed by the source for any non-system dependencies that our code-package has
   356  	// from the filtered list
   357  	// --------------------------------------------------------------------------------------
   358  	for dep := range deps {
   359  
   360  		logger.Debugf("processing dep: %s", dep)
   361  
   362  		// Each dependency should either be in our GOPATH or GOROOT.  We are not interested in packaging
   363  		// any of the system packages.  However, the official way (go-list) to make this determination
   364  		// is too expensive to run for every dep.  Therefore, we cheat.  We assume that any packages that
   365  		// cannot be found must be system packages and silently skip them
   366  		for gopath := range gopaths {
   367  			fqp := filepath.Join(gopath, "src", dep)
   368  			exists, err := pathExists(fqp)
   369  
   370  			logger.Debugf("checking: %s exists: %v", fqp, exists)
   371  
   372  			if err == nil && exists {
   373  
   374  				// We only get here when we found it, so go ahead and load its code
   375  				files, err := findSource(gopath, dep)
   376  				if err != nil {
   377  					return nil, err
   378  				}
   379  
   380  				// Merge the map manually
   381  				for _, file := range files {
   382  					fileMap[file.Name] = file
   383  				}
   384  			}
   385  		}
   386  	}
   387  
   388  	logger.Debugf("done")
   389  
   390  	// --------------------------------------------------------------------------------------
   391  	// Reprocess into a list for easier handling going forward
   392  	// --------------------------------------------------------------------------------------
   393  	files := make(Sources, 0)
   394  	for _, file := range fileMap {
   395  		files = append(files, file)
   396  	}
   397  
   398  	// --------------------------------------------------------------------------------------
   399  	// Remap non-package dependencies to package/vendor
   400  	// --------------------------------------------------------------------------------------
   401  	vendorDependencies(code.Pkg, files)
   402  
   403  	// --------------------------------------------------------------------------------------
   404  	// Sort on the filename so the tarball at least looks sane in terms of package grouping
   405  	// --------------------------------------------------------------------------------------
   406  	sort.Sort(files)
   407  
   408  	// --------------------------------------------------------------------------------------
   409  	// Write out our tar package
   410  	// --------------------------------------------------------------------------------------
   411  	payload := bytes.NewBuffer(nil)
   412  	gw := gzip.NewWriter(payload)
   413  	tw := tar.NewWriter(gw)
   414  
   415  	for _, file := range files {
   416  		err = cutil.WriteFileToPackage(file.Path, file.Name, tw)
   417  		if err != nil {
   418  			return nil, fmt.Errorf("Error writing %s to tar: %s", file.Name, err)
   419  		}
   420  	}
   421  
   422  	tw.Close()
   423  	gw.Close()
   424  
   425  	return payload.Bytes(), nil
   426  }
   427  
   428  func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec) (string, error) {
   429  
   430  	var buf []string
   431  
   432  	buf = append(buf, "FROM "+cutil.GetDockerfileFromConfig("chaincode.golang.runtime"))
   433  	buf = append(buf, "ADD binpackage.tar /usr/local/bin")
   434  
   435  	dockerFileContents := strings.Join(buf, "\n")
   436  
   437  	return dockerFileContents, nil
   438  }
   439  
   440  func (goPlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error {
   441  	spec := cds.ChaincodeSpec
   442  
   443  	pkgname, err := decodeUrl(spec)
   444  	if err != nil {
   445  		return fmt.Errorf("could not decode url: %s", err)
   446  	}
   447  
   448  	const ldflags = "-linkmode external -extldflags '-static'"
   449  
   450  	codepackage := bytes.NewReader(cds.CodePackage)
   451  	binpackage := bytes.NewBuffer(nil)
   452  	err = util.DockerBuild(util.DockerBuildOptions{
   453  		Cmd:          fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -ldflags \"%s\" -o /chaincode/output/chaincode %s", ldflags, pkgname),
   454  		InputStream:  codepackage,
   455  		OutputStream: binpackage,
   456  	})
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	return cutil.WriteBytesToPackage("binpackage.tar", binpackage.Bytes(), tw)
   462  }