github.com/tenywen/fabric@v1.0.0-beta.0.20170620030522-a5b1ed380643/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  // Generates a deployment payload for GOLANG as a series of src/$pkg entries in .tar.gz format
   182  func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
   183  
   184  	var err error
   185  
   186  	// --------------------------------------------------------------------------------------
   187  	// retrieve a CodeDescriptor from either HTTP or the filesystem
   188  	// --------------------------------------------------------------------------------------
   189  	code, err := getCode(spec)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	if code.Cleanup != nil {
   194  		defer code.Cleanup()
   195  	}
   196  
   197  	// --------------------------------------------------------------------------------------
   198  	// Update our environment for the purposes of executing go-list directives
   199  	// --------------------------------------------------------------------------------------
   200  	env, err := getGoEnv()
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	gopaths := splitEnvPaths(env["GOPATH"])
   205  	goroots := splitEnvPaths(env["GOROOT"])
   206  	gopaths[code.Gopath] = true
   207  	env["GOPATH"] = flattenEnvPaths(gopaths)
   208  
   209  	// --------------------------------------------------------------------------------------
   210  	// Retrieve the list of first-order imports referenced by the chaincode
   211  	// --------------------------------------------------------------------------------------
   212  	imports, err := listImports(env, code.Pkg)
   213  	if err != nil {
   214  		return nil, fmt.Errorf("Error obtaining imports: %s", err)
   215  	}
   216  
   217  	// --------------------------------------------------------------------------------------
   218  	// Remove any imports that are provided by the ccenv or system
   219  	// --------------------------------------------------------------------------------------
   220  	var provided = map[string]bool{
   221  		"github.com/hyperledger/fabric/core/chaincode/shim": true,
   222  		"github.com/hyperledger/fabric/protos/peer":         true,
   223  	}
   224  
   225  	imports = filter(imports, func(pkg string) bool {
   226  		// Drop if provided by CCENV
   227  		if _, ok := provided[pkg]; ok == true {
   228  			logger.Debugf("Discarding provided package %s", pkg)
   229  			return false
   230  		}
   231  
   232  		// Drop if provided by GOROOT
   233  		for goroot := range goroots {
   234  			fqp := filepath.Join(goroot, "src", pkg)
   235  			exists, err := pathExists(fqp)
   236  			if err == nil && exists {
   237  				logger.Debugf("Discarding GOROOT package %s", pkg)
   238  				return false
   239  			}
   240  		}
   241  
   242  		// Else, we keep it
   243  		logger.Debugf("Accepting import: %s", pkg)
   244  		return true
   245  	})
   246  
   247  	// --------------------------------------------------------------------------------------
   248  	// Assemble the fully resolved list of direct and transitive dependencies based on the
   249  	// imports that remain after filtering
   250  	// --------------------------------------------------------------------------------------
   251  	deps := make(map[string]bool)
   252  
   253  	for _, pkg := range imports {
   254  		// ------------------------------------------------------------------------------
   255  		// Resolve direct import's transitives
   256  		// ------------------------------------------------------------------------------
   257  		transitives, err := listDeps(env, pkg)
   258  		if err != nil {
   259  			return nil, fmt.Errorf("Error obtaining dependencies for %s: %s", pkg, err)
   260  		}
   261  
   262  		// ------------------------------------------------------------------------------
   263  		// Merge all results with our top list
   264  		// ------------------------------------------------------------------------------
   265  
   266  		// Merge direct dependency...
   267  		deps[pkg] = true
   268  
   269  		// .. and then all transitives
   270  		for _, dep := range transitives {
   271  			deps[dep] = true
   272  		}
   273  	}
   274  
   275  	// cull "" if it exists
   276  	delete(deps, "")
   277  
   278  	// --------------------------------------------------------------------------------------
   279  	// Find the source from our first-order code package ...
   280  	// --------------------------------------------------------------------------------------
   281  	fileMap, err := findSource(code.Gopath, code.Pkg)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	// --------------------------------------------------------------------------------------
   287  	// ... followed by the source for any non-system dependencies that our code-package has
   288  	// from the filtered list
   289  	// --------------------------------------------------------------------------------------
   290  	for dep := range deps {
   291  
   292  		logger.Debugf("processing dep: %s", dep)
   293  
   294  		// Each dependency should either be in our GOPATH or GOROOT.  We are not interested in packaging
   295  		// any of the system packages.  However, the official way (go-list) to make this determination
   296  		// is too expensive to run for every dep.  Therefore, we cheat.  We assume that any packages that
   297  		// cannot be found must be system packages and silently skip them
   298  		for gopath := range gopaths {
   299  			fqp := filepath.Join(gopath, "src", dep)
   300  			exists, err := pathExists(fqp)
   301  
   302  			logger.Debugf("checking: %s exists: %v", fqp, exists)
   303  
   304  			if err == nil && exists {
   305  
   306  				// We only get here when we found it, so go ahead and load its code
   307  				files, err := findSource(gopath, dep)
   308  				if err != nil {
   309  					return nil, err
   310  				}
   311  
   312  				// Merge the map manually
   313  				for _, file := range files {
   314  					fileMap[file.Name] = file
   315  				}
   316  			}
   317  		}
   318  	}
   319  
   320  	logger.Debugf("done")
   321  
   322  	// --------------------------------------------------------------------------------------
   323  	// Reclassify and sort the files:
   324  	//
   325  	// Two goals:
   326  	//   * Remap non-package dependencies to package/vendor
   327  	//   * Sort the final filename so the tarball at least looks sane in terms of package grouping
   328  	// --------------------------------------------------------------------------------------
   329  	files := make(Sources, 0)
   330  	pkgPath := filepath.Join("src", code.Pkg)
   331  	vendorPath := filepath.Join(pkgPath, "vendor")
   332  	for _, file := range fileMap {
   333  		// Vendor any packages that are not already within our chaincode's primary package.  We
   334  		// detect this by checking the path-prefix.  Anything that is prefixed by "src/$pkg"
   335  		// (which includes the package itself and anything explicitly vendored in "src/$pkg/vendor")
   336  		// are left unperturbed.  Everything else is implicitly vendored under src/$pkg/vendor by
   337  		// simply remapping "src" -> "src/$pkg/vendor" in the tarball index.
   338  		if strings.HasPrefix(file.Name, pkgPath) == false {
   339  			origName := file.Name
   340  			file.Name = strings.Replace(origName, "src", vendorPath, 1)
   341  			logger.Debugf("vendoring %s -> %s", origName, file.Name)
   342  		}
   343  
   344  		files = append(files, file)
   345  	}
   346  
   347  	sort.Sort(files)
   348  
   349  	// --------------------------------------------------------------------------------------
   350  	// Write out our tar package
   351  	// --------------------------------------------------------------------------------------
   352  	payload := bytes.NewBuffer(nil)
   353  	gw := gzip.NewWriter(payload)
   354  	tw := tar.NewWriter(gw)
   355  
   356  	for _, file := range files {
   357  		err = cutil.WriteFileToPackage(file.Path, file.Name, tw)
   358  		if err != nil {
   359  			return nil, fmt.Errorf("Error writing %s to tar: %s", file.Name, err)
   360  		}
   361  	}
   362  
   363  	tw.Close()
   364  	gw.Close()
   365  
   366  	return payload.Bytes(), nil
   367  }
   368  
   369  func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec) (string, error) {
   370  
   371  	var buf []string
   372  
   373  	buf = append(buf, "FROM "+cutil.GetDockerfileFromConfig("chaincode.golang.runtime"))
   374  	buf = append(buf, "ADD binpackage.tar /usr/local/bin")
   375  
   376  	dockerFileContents := strings.Join(buf, "\n")
   377  
   378  	return dockerFileContents, nil
   379  }
   380  
   381  func (goPlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error {
   382  	spec := cds.ChaincodeSpec
   383  
   384  	pkgname, err := decodeUrl(spec)
   385  	if err != nil {
   386  		return fmt.Errorf("could not decode url: %s", err)
   387  	}
   388  
   389  	const ldflags = "-linkmode external -extldflags '-static'"
   390  
   391  	codepackage := bytes.NewReader(cds.CodePackage)
   392  	binpackage := bytes.NewBuffer(nil)
   393  	err = util.DockerBuild(util.DockerBuildOptions{
   394  		Cmd:          fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -ldflags \"%s\" -o /chaincode/output/chaincode %s", ldflags, pkgname),
   395  		InputStream:  codepackage,
   396  		OutputStream: binpackage,
   397  	})
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	return cutil.WriteBytesToPackage("binpackage.tar", binpackage.Bytes(), tw)
   403  }