Repository: sonatype-nexus-community/nancy
Pull request #2 (2019-02-01 23:25:57)

Add go mod support
This adds in the functionality to let people scan `go mod` projects

This pull request makes the following changes:
* New `mod.go` file with all the fixins
* New methods to parse a `go.sum` file
* Doc updates



Patch:
diff --git a/README.md b/README.md
index 55fddf9..9652d62 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,15 @@
 
 To use `nancy`, assuming you have a built version of it:
 
-`./nancy /path/to/your/Gopkg.lock`
+* `./nancy /path/to/your/Gopkg.lock`
+* `./nancy /path/to/your/go.sum`
 
-`nancy` currently works for projects that use `dep`, but we have plans to support `go mod` projects, as well.
+`nancy` currently works for projects that use `dep` or `go mod` for dependencies.
+
+### DISCLAIMER
+
+A portion of the golang ecosystem doesn't use proper versions, and instead uses a commit hash to resolve your dependency. Dependencies like this will not work with
+`nancy` quite yet, as we don't have a mechanism on OSS Index to lookup vulnerabilities in that manner. 
 
 ## Why Nancy?
 
diff --git a/main.go b/main.go
index d7829ec..c979448 100644
--- a/main.go
+++ b/main.go
@@ -23,6 +23,7 @@ import (
 	"github.com/sonatype-nexus-community/nancy/packages"
 	"github.com/sonatype-nexus-community/nancy/parse"
 	"os"
+	"strings"
 )
 
 var noColorPtr *bool
@@ -35,7 +36,7 @@ func main() {
 	version := flag.Bool("version", false, "prints current nancy version")
 
 	flag.Usage = func() {
-		_, _ = fmt.Fprintf(os.Stderr, "Usage: nancy [options] <Gopkg.lock>\n\nOptions:\n")
+		_, _ = fmt.Fprintf(os.Stderr, "Usage: \nnancy [options] </path/to/Gopkg.lock>\nnancy [options] </path/to/go.sum>\n\nOptions:\n")
 		flag.PrintDefaults()
 		os.Exit(2)
 	}
@@ -62,14 +63,29 @@ func main() {
 }
 
 func doCheckExistenceAndParse() {
-	dep := packages.Dep{}
-	dep.GopkgPath = path
-	if dep.CheckExistenceOfManifest() {
-		dep.ProjectList, _ = parse.GopkgLock(path)
-		var purls = processPackages(dep)
-		var packageCount = len(purls)
-
-		checkOSSIndex(purls, packageCount)
+	switch {
+	case strings.Contains(path, "Gopkg.lock"):
+		dep := packages.Dep{}
+		dep.GopkgPath = path
+		if dep.CheckExistenceOfManifest() {
+			dep.ProjectList, _ = parse.GopkgLock(path)
+			var purls = processPackages(dep)
+			var packageCount = len(purls)
+
+			checkOSSIndex(purls, packageCount)
+		}
+	case strings.Contains(path, "go.sum"):
+		mod := packages.Mod{}
+		mod.GoSumPath = path
+		if mod.CheckExistenceOfManifest() {
+			mod.ProjectList, _ = parse.GoSum(path)
+			var purls = processPackages(mod)
+			var packageCount = len(purls)
+
+			checkOSSIndex(purls, packageCount)
+		}
+	default:
+		os.Exit(3)
 	}
 }
 
diff --git a/packages/dep.go b/packages/dep.go
index 0f1d1e4..00d0833 100644
--- a/packages/dep.go
+++ b/packages/dep.go
@@ -27,20 +27,6 @@ type Dep struct {
 	GopkgPath   string
 }
 
-// convertGopkgNameToPurl will convert the Gopkg name into a Package URL
-//
-// FIXME: Research the various Gopkg name formats and convert them correctly
-func convertGopkgNameToPurl(name string) string {
-	switch {
-	case strings.Contains(name, "github.com"):
-		name = strings.Replace(name, "github.com", "github", 1)
-
-	case strings.Contains(name, "gopkg.in"):
-		name = strings.Replace(name, "gopkg.in", "github", 1)
-	}
-	return name
-}
-
 // ExtractPurlsFromManifest will convert Gopkg projects to Package URLs
 func (d Dep) ExtractPurlsFromManifest() []string {
 	var purls []string
diff --git a/packages/go.sum b/packages/go.sum
new file mode 100644
index 0000000..d8ee8c4
--- /dev/null
+++ b/packages/go.sum
@@ -0,0 +1,20 @@
+github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo=
+github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg=
+github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
+github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f h1:dDxpBYafY/GYpcl+LS4Bn3ziLPuEdGRkRjYAbSlWxSA=
+github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/packages/mod.go b/packages/mod.go
new file mode 100644
index 0000000..56797dc
--- /dev/null
+++ b/packages/mod.go
@@ -0,0 +1,45 @@
+// Copyright 2018 Sonatype Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package packages
+
+import "strings"
+import "github.com/sonatype-nexus-community/nancy/types"
+import "fmt"
+import "github.com/sonatype-nexus-community/nancy/customerrors"
+import "os"
+
+type Mod struct {
+	ProjectList types.ProjectList
+	GoSumPath   string
+}
+
+func (m Mod) ExtractPurlsFromManifest() (purls []string) {
+	for _, s := range m.ProjectList.Projects {
+		var version string
+		version = strings.Replace(s.Version, "v", "", -1)
+
+		if len(version) > 0 { // There must be a version we can use
+			var purl = "pkg:" + convertGopkgNameToPurl(s.Name) + "@" + version
+			purls = append(purls, purl)
+		}
+	}
+	return purls
+}
+
+func (m Mod) CheckExistenceOfManifest() bool {
+	if _, err := os.Stat(m.GoSumPath); os.IsNotExist(err) {
+		customerrors.Check(err, fmt.Sprint("No go.sum found at path: "+m.GoSumPath))
+	}
+	return true
+}
diff --git a/packages/packages.go b/packages/packages.go
index c3c97d6..615c8de 100644
--- a/packages/packages.go
+++ b/packages/packages.go
@@ -13,8 +13,27 @@
 // limitations under the License.
 package packages
 
+import (
+	"strings"
+)
+
 // Packages is meant to be implemented for any package format such as dep, go mod, etc..
 type Packages interface {
 	ExtractPurlsFromManifest() []string
 	CheckExistenceOfManifest() bool
 }
+
+// convertGopkgNameToPurl will convert the Gopkg name into a Package URL
+//
+// FIXME: Research the various Gopkg name formats and convert them correctly
+func convertGopkgNameToPurl(name string) string {
+	switch {
+	case strings.Contains(name, "github.com"):
+		name = strings.Replace(name, "github.com", "github", 1)
+	case strings.Contains(name, "gopkg.in"):
+		name = strings.Replace(name, "gopkg.in", "github", 1)
+	case strings.Contains(name, "golang.org"):
+		name = strings.Replace(name, "golang.org", "golang", 1)
+	}
+	return name
+}
diff --git a/parse/go.sum b/parse/go.sum
new file mode 100644
index 0000000..d8ee8c4
--- /dev/null
+++ b/parse/go.sum
@@ -0,0 +1,20 @@
+github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo=
+github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg=
+github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
+github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f h1:dDxpBYafY/GYpcl+LS4Bn3ziLPuEdGRkRjYAbSlWxSA=
+github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/parse/parse.go b/parse/parse.go
index b5c43fc..c7e51ad 100644
--- a/parse/parse.go
+++ b/parse/parse.go
@@ -14,8 +14,11 @@
 package parse
 
 import (
+	"bufio"
 	"github.com/BurntSushi/toml"
 	"github.com/sonatype-nexus-community/nancy/types"
+	"os"
+	"strings"
 )
 
 // GopkgLock parses the Gopkg file and returns an error if unsuccessful
@@ -27,3 +30,21 @@ func GopkgLock(path string) (deps types.ProjectList, err error) {
 	}
 	return deps, nil
 }
+
+// GoSum parses the go.sum file and returns an error if unsuccessful
+func GoSum(path string) (deps types.ProjectList, err error) {
+	file, err := os.Open(path)
+	if err != nil {
+		return deps, err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		s := strings.Split(scanner.Text(), " ")
+		if !strings.HasSuffix(s[1], "/go.mod") {
+			deps.Projects = append(deps.Projects, types.Projects{Name: s[0], Version: s[1]})
+		}
+	}
+	return deps, nil
+}


Test patch:
diff --git a/packages/mod_test.go b/packages/mod_test.go
new file mode 100644
index 0000000..793fff8
--- /dev/null
+++ b/packages/mod_test.go
@@ -0,0 +1,45 @@
+// Copyright 2018 Sonatype Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package packages
+
+import (
+	"testing"
+)
+
+var testGoSumName = "go.sum"
+
+func TestModCheckExistenceOfManifestExists(t *testing.T) {
+	mod := Mod{}
+	mod.GoSumPath = testGoSumName
+	exists := mod.CheckExistenceOfManifest()
+
+	if !exists {
+		t.Errorf("Expected existence of %s", testGoSumName)
+	}
+}
+
+func TestModExtractPurlsFromManifest(t *testing.T) {
+	var err error
+	mod := Mod{}
+	mod.GoSumPath = testGoSumName
+	mod.ProjectList = getProjectList()
+	if err != nil {
+		t.Error(err)
+	}
+
+	result := mod.ExtractPurlsFromManifest()
+	if len(result) != 5 {
+		t.Error(result)
+	}
+}
diff --git a/parse/parse_test.go b/parse/parse_test.go
index c92a342..7d56469 100644
--- a/parse/parse_test.go
+++ b/parse/parse_test.go
@@ -34,3 +34,21 @@ func TestGopkgLockError(t *testing.T) {
 		t.Error(err)
 	}
 }
+
+func TestGoSum(t *testing.T) {
+	deps, err := GoSum("go.sum")
+	if err != nil {
+		t.Error(err)
+	}
+
+	if len(deps.Projects) != 10 {
+		t.Error(deps)
+	}
+}
+
+func TestGoSumError(t *testing.T) {
+	_, err := GoSum("go.notsum")
+	if err == nil {
+		t.Error(err)
+	}
+}