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 }