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 }