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 }