github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/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  	"strings"
    29  
    30  	"regexp"
    31  
    32  	"github.com/hyperledger/fabric/core/chaincode/platforms/util"
    33  	cutil "github.com/hyperledger/fabric/core/container/util"
    34  	pb "github.com/hyperledger/fabric/protos/peer"
    35  )
    36  
    37  // Platform for chaincodes written in Go
    38  type Platform struct {
    39  }
    40  
    41  // Returns whether the given file or directory exists or not
    42  func pathExists(path string) (bool, error) {
    43  	_, err := os.Stat(path)
    44  	if err == nil {
    45  		return true, nil
    46  	}
    47  	if os.IsNotExist(err) {
    48  		return false, nil
    49  	}
    50  	return true, err
    51  }
    52  
    53  func decodeUrl(spec *pb.ChaincodeSpec) (string, error) {
    54  	var urlLocation string
    55  	if strings.HasPrefix(spec.ChaincodeId.Path, "http://") {
    56  		urlLocation = spec.ChaincodeId.Path[7:]
    57  	} else if strings.HasPrefix(spec.ChaincodeId.Path, "https://") {
    58  		urlLocation = spec.ChaincodeId.Path[8:]
    59  	} else {
    60  		urlLocation = spec.ChaincodeId.Path
    61  	}
    62  
    63  	if len(urlLocation) < 2 {
    64  		return "", errors.New("ChaincodeSpec's path/URL invalid")
    65  	}
    66  
    67  	if strings.LastIndex(urlLocation, "/") == len(urlLocation)-1 {
    68  		urlLocation = urlLocation[:len(urlLocation)-1]
    69  	}
    70  
    71  	return urlLocation, nil
    72  }
    73  
    74  // ValidateSpec validates Go chaincodes
    75  func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error {
    76  	path, err := url.Parse(spec.ChaincodeId.Path)
    77  	if err != nil || path == nil {
    78  		return fmt.Errorf("invalid path: %s", err)
    79  	}
    80  
    81  	//we have no real good way of checking existence of remote urls except by downloading and testin
    82  	//which we do later anyway. But we *can* - and *should* - test for existence of local paths.
    83  	//Treat empty scheme as a local filesystem path
    84  	if path.Scheme == "" {
    85  		gopath := os.Getenv("GOPATH")
    86  		// Only take the first element of GOPATH
    87  		gopath = filepath.SplitList(gopath)[0]
    88  		pathToCheck := filepath.Join(gopath, "src", spec.ChaincodeId.Path)
    89  		exists, err := pathExists(pathToCheck)
    90  		if err != nil {
    91  			return fmt.Errorf("Error validating chaincode path: %s", err)
    92  		}
    93  		if !exists {
    94  			return fmt.Errorf("Path to chaincode does not exist: %s", spec.ChaincodeId.Path)
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  func (goPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSpec) error {
   101  
   102  	if cds.CodePackage == nil || len(cds.CodePackage) == 0 {
   103  		// Nothing to validate if no CodePackage was included
   104  		return nil
   105  	}
   106  
   107  	// FAB-2122: Scan the provided tarball to ensure it only contains source-code under
   108  	// /src/$packagename.  We do not want to allow something like ./pkg/shady.a to be installed under
   109  	// $GOPATH within the container.  Note, we do not look deeper than the path at this time
   110  	// with the knowledge that only the go/cgo compiler will execute for now.  We will remove the source
   111  	// from the system after the compilation as an extra layer of protection.
   112  	//
   113  	// It should be noted that we cannot catch every threat with these techniques.  Therefore,
   114  	// the container itself needs to be the last line of defense and be configured to be
   115  	// resilient in enforcing constraints. However, we should still do our best to keep as much
   116  	// garbage out of the system as possible.
   117  	re := regexp.MustCompile(`(/)?src/.*`)
   118  	is := bytes.NewReader(cds.CodePackage)
   119  	gr, err := gzip.NewReader(is)
   120  	if err != nil {
   121  		return fmt.Errorf("failure opening codepackage gzip stream: %s", err)
   122  	}
   123  	tr := tar.NewReader(gr)
   124  
   125  	for {
   126  		header, err := tr.Next()
   127  		if err != nil {
   128  			// We only get here if there are no more entries to scan
   129  			break
   130  		}
   131  
   132  		// --------------------------------------------------------------------------------------
   133  		// Check name for conforming path
   134  		// --------------------------------------------------------------------------------------
   135  		if !re.MatchString(header.Name) {
   136  			return fmt.Errorf("Illegal file detected in payload: \"%s\"", header.Name)
   137  		}
   138  
   139  		// --------------------------------------------------------------------------------------
   140  		// Check that file mode makes sense
   141  		// --------------------------------------------------------------------------------------
   142  		// Acceptable flags:
   143  		//      ISREG      == 0100000
   144  		//      -rw-rw-rw- == 0666
   145  		//
   146  		// Anything else is suspect in this context and will be rejected
   147  		// --------------------------------------------------------------------------------------
   148  		if header.Mode&^0100666 != 0 {
   149  			return fmt.Errorf("Illegal file mode detected for file %s: %o", header.Name, header.Mode)
   150  		}
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  // WritePackage writes the Go chaincode package
   157  func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
   158  
   159  	var err error
   160  
   161  	inputbuf := bytes.NewBuffer(nil)
   162  	gw := gzip.NewWriter(inputbuf)
   163  	tw := tar.NewWriter(gw)
   164  
   165  	//ignore the generated hash. Just use the tw
   166  	//The hash could be used in a future enhancement
   167  	//to check, warn of duplicate installs etc.
   168  	_, err = collectChaincodeFiles(spec, tw)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	err = writeChaincodePackage(spec, tw)
   174  
   175  	tw.Close()
   176  	gw.Close()
   177  
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	payload := inputbuf.Bytes()
   183  
   184  	return payload, nil
   185  }
   186  
   187  func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec) (string, error) {
   188  
   189  	var buf []string
   190  
   191  	buf = append(buf, "FROM "+cutil.GetDockerfileFromConfig("chaincode.golang.runtime"))
   192  	buf = append(buf, "ADD binpackage.tar /usr/local/bin")
   193  
   194  	dockerFileContents := strings.Join(buf, "\n")
   195  
   196  	return dockerFileContents, nil
   197  }
   198  
   199  func (goPlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error {
   200  	spec := cds.ChaincodeSpec
   201  
   202  	pkgname, err := decodeUrl(spec)
   203  	if err != nil {
   204  		return fmt.Errorf("could not decode url: %s", err)
   205  	}
   206  
   207  	const ldflags = "-linkmode external -extldflags '-static'"
   208  
   209  	codepackage := bytes.NewReader(cds.CodePackage)
   210  	binpackage := bytes.NewBuffer(nil)
   211  	err = util.DockerBuild(util.DockerBuildOptions{
   212  		Cmd:          fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -ldflags \"%s\" -o /chaincode/output/chaincode %s", ldflags, pkgname),
   213  		InputStream:  codepackage,
   214  		OutputStream: binpackage,
   215  	})
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	return cutil.WriteBytesToPackage("binpackage.tar", binpackage.Bytes(), tw)
   221  }