Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gitlab-org/security-products/analyzers/gemnasium
  • introspectdata/public/gemnasium
  • fcatteau/gemnasium
  • test-196697/gemnasium
  • tbonci/gemnasium
  • alexandervaneck/gemnasium
  • WebDevAdminAlpha/gemnasium
  • feistel/gemnasium
  • geksin/gemnasium
  • weyert-tapico/gemnasium
  • bschwehn/gemnasium
  • rpandini_wh/gemnasium
  • AntoineD/gemnasium
  • evan853/gemnasium
  • candrews/gemnasium
  • emersonalmeidax/gemnasium
  • gitlab-community/security-products/analyzers/gemnasium
  • christopher.white2/gemnasium
  • ani.from.govtech/gemnasium
  • bridley/gemnasium
  • akuamiaofezike/gemnasium
  • KilianHaag/gemnasium
  • gitlab-renovate-forks/gemnasium
  • miwow.tester/gemnasium
  • olawrenceovery/gemnasium
  • nraj0408/gemnasium
  • alkhmaj/gemnasium
  • jlm-works/gemnasium
  • zjkyz8/gemnasium
  • omarodeh/gemnasium
  • gitlab-org/cloud-native/distroless/gemnasium
  • gulcandeniztaysun/gemnasium
  • heysujal/gemnasium
  • ikelax/gemnasium
  • corteva-oss/gemnasium
  • sebglon1/gemnasium
36 results
Show changes
Commits on Source (37)
Showing
with 667 additions and 85 deletions
# Gemnasium analyzer changelog # Gemnasium analyzer changelog
## v2.3.0
- Use gemnasium-db git repo instead of the Gemnasium API (!25)
## v2.2.6 ## v2.2.6
- Fix The engine "node" is incompatible with this module. error (!22) - Fix The engine "node" is incompatible with this module. error (!22)
......
FROM node:11-alpine FROM node:11-alpine
RUN apk add --no-cache git
ENV PYTHON_PIP_VERSION 19.3
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/65986a26949050d26e6ec98915da4aade8d8679d/get-pip.py
ENV PYTHON_GET_PIP_SHA256 8d412752ae26b46a39a201ec618ef9ef7656c5b2d8529cdcbe60cd70dc94f40c
COPY vrange /vrange
ENV VRANGE_DIR="/vrange"
ARG GEMNASIUM_DB_LOCAL_PATH="/gemnasium-db"
ARG GEMNASIUM_DB_REMOTE_URL="https://gitlab.com/gitlab-org/security-products/gemnasium-db.git"
ARG GEMNASIUM_DB_REF_NAME="master"
ENV GEMNASIUM_DB_LOCAL_PATH $GEMNASIUM_DB_LOCAL_PATH
ENV GEMNASIUM_DB_REMOTE_URL $GEMNASIUM_DB_REMOTE_URL
ENV GEMNASIUM_DB_REF_NAME $GEMNASIUM_DB_REF_NAME
RUN \
# gemnasium-db
apk add --no-cache git && \
git clone --branch $GEMNASIUM_DB_REF_NAME $GEMNASIUM_DB_REMOTE_URL $GEMNASIUM_DB_LOCAL_PATH && \
\
# vrange/php dependencies
apk add --no-cache php7 php7-dom php7-ctype php7-tokenizer php7-xmlwriter php7-xml composer && \
composer install -d "$VRANGE_DIR/php" && \
\
# vrange/gem dependencies
apk add --no-cache git ruby ruby-json && \
\
# vrange/python dependencies
apk add --no-cache git python3 && \
wget -O get-pip.py "$PYTHON_GET_PIP_URL" && \
echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c - && \
python3 get-pip.py --disable-pip-version-check --no-cache-dir "pip==$PYTHON_PIP_VERSION" && \
pip install -r "$VRANGE_DIR/python/requirements.txt" && \
\
# vrange/npm dependencies
yarn --cwd "$VRANGE_DIR/npm/yarn.lock" && \
\
echo "done."
COPY analyzer / COPY analyzer /
ENTRYPOINT [] ENTRYPOINT []
CMD ["/analyzer", "run"] CMD ["/analyzer", "run"]
// Package advisory implements structs for manipulating
// security advisories affecting project dependencies
// as a collection of YAML files.
package advisory
// Advisory is a security advisory published for a package.
type Advisory struct {
// Identifier is CVE id (preferred) or any public identifier.
Identifier string `yaml:"identifier,omitempty" json:"identifier,omitempty"`
// Title is a short description of the security flaw.
Title string `yaml:"title" json:"title"`
// Description is a long description of the security flaw and the possible risks.
Description string `yaml:"description" json:"description"`
// DisclosureDate is the date on which the advisory was made public, in ISO-8601 format.
DisclosureDate string `yaml:"date,omitempty" json:"date,omitempty"`
// AffectedRange is the range of affected versions. Machine-readable syntax used by the package manager.
AffectedRange string `yaml:"affected_range" json:"affected_range"`
// FixedVersions are the versions fixing the vulnerability. The order is not relevant.
FixedVersions []string `yaml:"fixed_versions,omitempty" json:"fixed_versions,omitempty"`
// ImpactedVersions is the range of affected versions. Human-readable version for display.
ImpactedVersions string `yaml:"affected_versions,omitempty" json:"affected_versions,omitempty"`
// NotImpacted describes the environments not affected by the vulnerability.
NotImpacted string `yaml:"not_impacted,omitempty" json:"not_impacted,omitempty"`
// Solution tells how to remediate the vulnerability.
Solution string `yaml:"solution,omitempty" json:"solution,omitempty"`
// Credit gives the names of the people who reported the vulnerability or helped fixing it.
Credit string `yaml:"credit,omitempty" json:"credit,omitempty"`
// Links are the URLs of: detailed advisory, documented exploit, vulnerable source code, etc.
Links []string `yaml:"urls" json:"urls"`
// Package is the affected package.
Package Package `yaml:"package_slug" json:"package_slug"`
// UUID is the identifier in Gemnasium DB. It's no longer used.
UUID string `yaml:"uuid,omitempty" json:"uuid,omitempty"`
}
package advisory
import (
"os"
"reflect"
"testing"
"gopkg.in/yaml.v2"
)
func TestAdvisory_Decode(t *testing.T) {
tcs := []struct {
name string
path string
want Advisory
}{
{
"Minimal",
"testdata/gem/actionmovie/GMS-2019-minimal.yml",
Advisory{
Identifier: "GMS-2019-minimal",
Title: "Unsafe UPDATE query",
Description: "There is a vulnerability in some UPDATE query.",
DisclosureDate: "2019-10-30",
AffectedRange: "*",
Package: Package{
Type: "gem",
Name: "actionmovie",
},
},
},
{
"Full",
"testdata/gem/activerecord/CVE-2016-6317.yml",
Advisory{
Identifier: "CVE-2016-6317",
Title: "Unsafe Query Generation Risk",
Description: "There is a vulnerability when Active Record is used in conjunction with JSON\r\nparameter parsing. This vulnerability is similar to CVE-2012-2660,\r\nCVE-2012-2694 and CVE-2013-0155.",
DisclosureDate: "2016-08-11",
AffectedRange: ">=4.0.0.alpha <4.2.7.1",
FixedVersions: []string{"4.2.7.1"},
ImpactedVersions: "4.x",
NotImpacted: "5.x, 3.x and earlier",
Solution: "Upgrade to latest or use workaround; see provided link.",
Credit: "joernchen of Phenoelit",
Links: []string{
"https://groups.google.com/forum/#!topic/rubyonrails-security/rgO20zYW33s",
},
Package: Package{
Type: "gem",
Name: "activerecord",
},
UUID: "51b1a6d4-3eb3-48d4-8d25-ae62c2dbb3d4",
},
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
f, err := os.Open(tc.path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
got := Advisory{}
err = yaml.NewDecoder(f).Decode(&got)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tc.want, got) {
t.Errorf("Wrong result. Expected %#v but got %#v", tc.want, got)
}
})
}
}
package advisory
import "fmt"
// ErrNoAdvisoryForPackageType is the error returned when there are no advisories
// corresponding to a given package type.
type ErrNoAdvisoryForPackageType struct {
PackageType string
}
func (err ErrNoAdvisoryForPackageType) Error() string {
return fmt.Sprintf("no advisory for package type %s", err.PackageType)
}
// ErrNoPackageTypeDir is the error returned when the directory corresponding
// to a package type is missing, or cannot be read.
type ErrNoPackageTypeDir struct {
PackageType string
}
func (err ErrNoPackageTypeDir) Error() string {
return fmt.Sprintf("cannot read directory for package type %s", err.PackageType)
}
// ErrNoPackageDir is the error returned when the directory containing
// package advisories is missing, or cannot be read.
type ErrNoPackageDir struct {
Package Package
}
func (err ErrNoPackageDir) Error() string {
return fmt.Sprintf("cannot read directory for package %s", err.Package.Slug())
}
package advisory
import (
"strings"
)
// Package contains the information needed to resolve a package on the server side.
type Package struct {
Type string `json:"type"`
Name string `json:"name"`
}
// Slug returns the package slug/path.
func (p Package) Slug() string {
return p.Type + "/" + p.Name
}
// UnmarshalYAML decodes a package slug.
func (p *Package) UnmarshalYAML(unmarshal func(interface{}) error) error {
var slug string
if err := unmarshal(&slug); err != nil {
return err
}
parts := strings.SplitN(slug, "/", 2)
if len(parts) > 1 {
p.Name = parts[1]
}
p.Type = parts[0]
return nil
}
package advisory
import (
"errors"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"gopkg.in/yaml.v2"
)
// defaultRefName is the git ref that is checked out by default when updating the repo.
const defaultRefName = "master"
// Repo is a local git clone of the gemnasium-db repository.
// It is a collection of YAML files containing security advisories for packages.
type Repo struct {
Path string // Path of local git clone of gemnasium-db.
}
// SatisfyPackageTypes ensures that the repo provides advisories
// for all the given package types.
func (r Repo) SatisfyPackageTypes(pkgTypes ...string) error {
errAdvisoryFileFound := errors.New("advisory file found")
for _, pkgType := range pkgTypes {
// check directory corresponding to package type
pkgDir := filepath.Join(r.Path, pkgType)
info, err := os.Stat(pkgDir)
if err != nil || !info.IsDir() {
return ErrNoPackageTypeDir{pkgType}
}
// look for advisory file
err = filepath.Walk(pkgDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err // error accessing path
}
if !info.IsDir() && hasAdvisoryExt(info.Name()) {
return errAdvisoryFileFound // advisory found
}
return nil // keep searching for advisories
})
switch err {
case errAdvisoryFileFound:
continue // advisory found, move on to next package type
case nil:
return ErrNoAdvisoryForPackageType{pkgType}
default:
return err // error accessing path
}
}
return nil // all package types are satisfied
}
// PackageAdvisories returns the paths of the advisories affecting the given package.
// It excludes directories and files that don't match the file extension of the advisories.
// Paths are relative to the repository.
func (r Repo) PackageAdvisories(pkg Package) ([]string, error) {
pkgSlug := pkg.Slug()
files, err := ioutil.ReadDir(filepath.Join(r.Path, pkgSlug))
if err != nil {
if _, ok := err.(*os.PathError); ok {
return nil, ErrNoPackageDir{Package: pkg}
}
return nil, err
}
relPaths := []string{}
for _, file := range files {
if file.IsDir() {
continue
}
if !hasAdvisoryExt(file.Name()) {
continue
}
relPaths = append(relPaths, filepath.Join(pkgSlug, file.Name()))
}
return relPaths, nil
}
func hasAdvisoryExt(path string) bool {
switch strings.ToLower(filepath.Ext(path)) {
case ".yml", ".yaml":
return true
default:
return false
}
}
// Advisory decodes the given advisory.
// Advisory path is relative to the repository path.
func (r Repo) Advisory(relPath string) (*Advisory, error) {
f, err := os.Open(filepath.Join(r.Path, relPath))
if err != nil {
return nil, err
}
defer f.Close()
adv := Advisory{}
err = yaml.NewDecoder(f).Decode(&adv)
return &adv, err
}
// UpdateOptions configures the update of the local git repository of gemnasium-db.
type UpdateOptions struct {
RemoteURL string
RefName string
}
// Update updates the local gemnasium-db repository with a git remote.
func (r Repo) Update(opts UpdateOptions) error {
gitCommand := func(args ...string) *exec.Cmd {
args = append([]string{"-C", r.Path}, args...)
cmd := exec.Command("git", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd
}
// list of git commands
argsList := [][]string{}
// add "git remote set-url" command if needed
if remoteURL := opts.RemoteURL; remoteURL != "" {
argsList = append(argsList, []string{"remote", "set-url", "origin", remoteURL})
}
// set git ref
ref := opts.RefName
if ref == "" {
ref = defaultRefName
}
// add "git fetch", "git reset" commands
argsList = append(argsList,
[]string{"fetch", "origin", ref},
[]string{"reset", "--hard", "origin/" + ref},
)
// run all commands
for _, args := range argsList {
if err := gitCommand(args...).Run(); err != nil {
return err
}
}
return nil
}
package advisory
import (
"reflect"
"testing"
)
func TestRepo_SatisfyPackageTypes(t *testing.T) {
repo := Repo{Path: "testdata"}
t.Run("ok", func(t *testing.T) {
if err := repo.SatisfyPackageTypes("gem", "pypi"); err != nil {
t.Error(err)
}
})
t.Run("no advisory", func(t *testing.T) {
err := repo.SatisfyPackageTypes("gem", "pypi", "no_advisory")
want := ErrNoAdvisoryForPackageType{PackageType: "no_advisory"}
if !reflect.DeepEqual(want, err) {
t.Errorf("Wrong error. Expected %#v but got %#v", want, err)
}
})
t.Run("no directory", func(t *testing.T) {
err := repo.SatisfyPackageTypes("gem", "pypi", "missing")
want := ErrNoPackageTypeDir{PackageType: "missing"}
if !reflect.DeepEqual(want, err) {
t.Errorf("Wrong error. Expected %#v but got %#v", want, err)
}
})
}
func TestRepo_PackageAdvisories(t *testing.T) {
repo := Repo{Path: "testdata"}
t.Run("found", func(t *testing.T) {
pkg := Package{Type: "pypi", Name: "cryptography"}
got, err := repo.PackageAdvisories(pkg)
if err != nil {
t.Fatal(err)
}
want := []string{
"pypi/cryptography/CVE-2016-9243.YAML",
"pypi/cryptography/CVE-2018-10903.yml",
}
if !reflect.DeepEqual(want, got) {
t.Errorf("Wrong result. Expected %#v but got %#v", want, got)
}
})
t.Run("missing", func(t *testing.T) {
pkg := Package{Type: "pypi", Name: "xyz"}
_, err := repo.PackageAdvisories(pkg)
want := ErrNoPackageDir{Package: pkg}
if !reflect.DeepEqual(want, err) {
t.Errorf("Wrong error. Expected %#v but got %#v", want, err)
}
})
}
func TestRepo_Advisory(t *testing.T) {
repo := Repo{Path: "testdata"}
got, err := repo.Advisory("gem/actionmovie/GMS-2019-minimal.yml")
if err != nil {
t.Fatal(err)
}
want := &Advisory{
Identifier: "GMS-2019-minimal",
Title: "Unsafe UPDATE query",
Description: "There is a vulnerability in some UPDATE query.",
DisclosureDate: "2019-10-30",
AffectedRange: "*",
Package: Package{
Type: "gem",
Name: "actionmovie",
},
}
if !reflect.DeepEqual(want, got) {
t.Errorf("Wrong result. Expected %#v but got %#v", want, got)
}
}
identifier: GMS-2019-minimal
title: Unsafe UPDATE query
description: "There is a vulnerability in some UPDATE query."
date: "2019-10-30"
affected_range: '*'
package_slug: gem/actionmovie
identifier: CVE-2016-6317
title: Unsafe Query Generation Risk
description: "There is a vulnerability when Active Record is used in conjunction with
JSON\r\nparameter parsing. This vulnerability is similar to CVE-2012-2660,\r\nCVE-2012-2694
and CVE-2013-0155."
date: "2016-08-11"
affected_range: '>=4.0.0.alpha <4.2.7.1'
fixed_versions:
- 4.2.7.1
affected_versions: 4.x
not_impacted: 5.x, 3.x and earlier
solution: Upgrade to latest or use workaround; see provided link.
credit: joernchen of Phenoelit
urls:
- https://groups.google.com/forum/#!topic/rubyonrails-security/rgO20zYW33s
uuid: 51b1a6d4-3eb3-48d4-8d25-ae62c2dbb3d4
package_slug: gem/activerecord
This is a readme file.
identifier: CVE-2016-9243
title: HKDF might return an empty byte-string
description: There's a bug where HKDF would return an empty byte-string if used with
a length less than `algorithm.digest_size`.
date: "2016-11-05"
affected_range: <1.5.3
fixed_versions:
- 1.5.3
affected_versions: All versions
solution: Upgrade to latest version.
credit: Markus Döring
urls:
- http://seclists.org/oss-sec/2016/q4/360
- https://github.com/pyca/cryptography/commit/b924696b2e8731f39696584d12cceeb3aeb2d874
- https://github.com/pyca/cryptography/issues/3211
uuid: 8a342fb1-daed-4a91-a217-9ad6c19d7bc9
package_slug: pypi/cryptography
identifier: CVE-2018-10903
title: GCM tag forgery via truncated tag in finalize_with_tag API
description: The `finalize_with_tag` API did not enforce a minimum tag length. If
a user did not validate the input length prior to passing it to `finalize_with_tag`
an attacker could craft an invalid payload with a shortened tag (e.g. 1 byte) such
that they would have a 1 in 256 chance of passing the MAC check. GCM tag forgeries
can cause key leakage.
date: "2018-07-30"
affected_range: '>=1.9.0,<2.3'
fixed_versions:
- "2.3"
affected_versions: From 1.9 to 2.2.2
not_impacted: 1.8 and earlier
solution: Upgrade to latest version
urls:
- http://cwe.mitre.org/data/definitions/20.html
- https://access.redhat.com/errata/RHSA-2018:3600
- https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2018-10903
- https://github.com/pyca/cryptography/pull/4342/commits/688e0f673bfbf43fa898994326c6877f00ab19ef
- https://usn.ubuntu.com/3720-1/
uuid: 004b3de5-5aa8-4977-b6c5-3b9b31b28dcd
package_slug: pypi/cryptography
...@@ -20,9 +20,9 @@ const ( ...@@ -20,9 +20,9 @@ const (
gemnasiumURL = "https://deps.sec.gitlab.com" gemnasiumURL = "https://deps.sec.gitlab.com"
) )
// Convert converts the output of the Gemnasium client (list of affected sources) to a report. // Convert converts the output of the Gemnasium (list of dependency files) to a report.
func Convert(reader io.Reader, prependPath string) (*issue.Report, error) { func Convert(reader io.Reader, prependPath string) (*issue.Report, error) {
var result []scanner.AffectedSource var result []scanner.File
err := json.NewDecoder(reader).Decode(&result) err := json.NewDecoder(reader).Decode(&result)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -30,13 +30,13 @@ func Convert(reader io.Reader, prependPath string) (*issue.Report, error) { ...@@ -30,13 +30,13 @@ func Convert(reader io.Reader, prependPath string) (*issue.Report, error) {
return ToReport(result, prependPath), nil return ToReport(result, prependPath), nil
} }
// ToReport converts affected sources returned by the Gemnasium client to a report. // ToReport converts dependency files returned by the Gemnasium scanner to a report.
func ToReport(sources []scanner.AffectedSource, prependPath string) *issue.Report { func ToReport(scanFiles []scanner.File, prependPath string) *issue.Report {
issues := []issue.Issue{} issues := []issue.Issue{}
depfiles := make([]issue.DependencyFile, len(sources)) depfiles := make([]issue.DependencyFile, len(scanFiles))
for i, source := range sources { for i, scanFile := range scanFiles {
issues = append(issues, toIssues(source, prependPath)...) issues = append(issues, toIssues(scanFile, prependPath)...)
depfiles[i] = toDependencyFile(source, prependPath) depfiles[i] = toDependencyFile(scanFile, prependPath)
} }
report := issue.NewReport() report := issue.NewReport()
report.Vulnerabilities = issues report.Vulnerabilities = issues
...@@ -44,11 +44,11 @@ func ToReport(sources []scanner.AffectedSource, prependPath string) *issue.Repor ...@@ -44,11 +44,11 @@ func ToReport(sources []scanner.AffectedSource, prependPath string) *issue.Repor
return &report return &report
} }
func toDependencyFile(source scanner.AffectedSource, prependPath string) issue.DependencyFile { func toDependencyFile(scanFile scanner.File, prependPath string) issue.DependencyFile {
return issue.DependencyFile{ return issue.DependencyFile{
Path: filepath.Join(prependPath, source.FilePath), Path: filepath.Join(prependPath, scanFile.Path),
Dependencies: toDependencies(source.Deps), Dependencies: toDependencies(scanFile.Deps),
PackageManager: issue.PackageManager(source.PackageManager), PackageManager: issue.PackageManager(scanFile.PackageManager),
} }
} }
...@@ -61,12 +61,12 @@ func toDependencies(in []parser.Dependency) []issue.Dependency { ...@@ -61,12 +61,12 @@ func toDependencies(in []parser.Dependency) []issue.Dependency {
return out return out
} }
func toIssues(source scanner.AffectedSource, prependPath string) []issue.Issue { func toIssues(scanFile scanner.File, prependPath string) []issue.Issue {
issues := make([]issue.Issue, len(source.Affections)) issues := make([]issue.Issue, len(scanFile.Affections))
for i, affection := range source.Affections { for i, affection := range scanFile.Affections {
issues[i] = VulnerabilityConverter{ issues[i] = VulnerabilityConverter{
PrependPath: prependPath, PrependPath: prependPath,
Source: source, File: scanFile,
Advisory: affection.Advisory, Advisory: affection.Advisory,
Dependency: affection.Dependency, Dependency: affection.Dependency,
}.Issue() }.Issue()
......
...@@ -7,36 +7,33 @@ import ( ...@@ -7,36 +7,33 @@ import (
"testing" "testing"
"gitlab.com/gitlab-org/security-products/analyzers/common/v2/issue" "gitlab.com/gitlab-org/security-products/analyzers/common/v2/issue"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/advisory"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser"
"gopkg.in/guregu/null.v3"
) )
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
// advisories // advisories
pgAdvisory := scanner.Advisory{ pgAdvisory := advisory.Advisory{
UUID: "7d9ba955-fd99-4503-936e-f6833768f76e", UUID: "7d9ba955-fd99-4503-936e-f6833768f76e",
Package: scanner.Package{Type: "gem", Name: "pg"}, Package: advisory.Package{Type: "gem", Name: "pg"},
AffectedVersions: []string{"0.8.0"}, Identifier: "CVE-1234",
Identifier: null.StringFrom("CVE-1234"), Title: "Regular Expression Denial of Service",
Title: "Regular Expression Denial of Service", Description: "Xyz is vulnerable to ReDoS in the Xyz parameter.",
Description: "Xyz is vulnerable to ReDoS in the Xyz parameter.", Solution: "Upgrade to latest version.",
Solution: null.StringFrom("Upgrade to latest version."), Links: []string{"https://security.io/advisories/119", "https://security.io/advisories/117", "https://security.io/advisories/118"},
Links: []string{"https://security.io/advisories/119", "https://security.io/advisories/117", "https://security.io/advisories/118"},
} }
// analyzer output // analyzer output
affected := []scanner.AffectedSource{ affected := []scanner.File{
{ {
Source: scanner.Source{ Path: "rails/Gemfile.lock",
FilePath: "rails/Gemfile.lock", PackageManager: issue.PackageManagerBundler,
PackageManager: issue.PackageManagerBundler, PackageType: "gem",
PackageType: "gem", Deps: []parser.Dependency{
Deps: []parser.Dependency{ {Name: "pg", Version: "0.8.0"},
{Name: "pg", Version: "0.8.0"}, {Name: "puma", Version: "2.16.0"},
{Name: "puma", Version: "2.16.0"},
},
}, },
Affections: []scanner.Affection{ Affections: []scanner.Affection{
{ {
...@@ -46,16 +43,14 @@ func TestConvert(t *testing.T) { ...@@ -46,16 +43,14 @@ func TestConvert(t *testing.T) {
}, },
}, },
{ {
Source: scanner.Source{ Path: "node/yarn.lock",
FilePath: "node/yarn.lock", PackageManager: issue.PackageManagerYarn,
PackageManager: issue.PackageManagerYarn, PackageType: "npm",
PackageType: "npm", Deps: []parser.Dependency{
Deps: []parser.Dependency{ {Name: "acorn", Version: "4.0.4"},
{Name: "acorn", Version: "4.0.4"}, {Name: "acorn", Version: "3.3.0"},
{Name: "acorn", Version: "3.3.0"}, {Name: "acorn", Version: "4.0.11"},
{Name: "acorn", Version: "4.0.11"}, {Name: "@angular/animations", Version: "4.4.6"},
{Name: "@angular/animations", Version: "4.4.6"},
},
}, },
}, },
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"sort" "sort"
"gitlab.com/gitlab-org/security-products/analyzers/common/v2/issue" "gitlab.com/gitlab-org/security-products/analyzers/common/v2/issue"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/advisory"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser"
) )
...@@ -14,8 +15,8 @@ import ( ...@@ -14,8 +15,8 @@ import (
// It also converts information relative to the affected source file. // It also converts information relative to the affected source file.
type VulnerabilityConverter struct { type VulnerabilityConverter struct {
PrependPath string PrependPath string
Source scanner.AffectedSource File scanner.File
Advisory scanner.Advisory Advisory advisory.Advisory
Dependency parser.Dependency Dependency parser.Dependency
} }
...@@ -32,7 +33,7 @@ func (c VulnerabilityConverter) Issue() issue.Issue { ...@@ -32,7 +33,7 @@ func (c VulnerabilityConverter) Issue() issue.Issue {
Name: c.Advisory.Title, Name: c.Advisory.Title,
Description: c.Advisory.Description, Description: c.Advisory.Description,
Severity: issue.SeverityLevelUnknown, Severity: issue.SeverityLevelUnknown,
Solution: c.Advisory.Solution.String, Solution: c.Advisory.Solution,
Identifiers: c.identifiers(), Identifiers: c.identifiers(),
Links: issue.NewLinks(c.Advisory.Links...), Links: issue.NewLinks(c.Advisory.Links...),
Location: issue.Location{ Location: issue.Location{
...@@ -48,13 +49,13 @@ func (c VulnerabilityConverter) Issue() issue.Issue { ...@@ -48,13 +49,13 @@ func (c VulnerabilityConverter) Issue() issue.Issue {
} }
func (c VulnerabilityConverter) filePath() string { func (c VulnerabilityConverter) filePath() string {
return filepath.Join(c.PrependPath, c.Source.FilePath) return filepath.Join(c.PrependPath, c.File.Path)
} }
func (c VulnerabilityConverter) identifiers() []issue.Identifier { func (c VulnerabilityConverter) identifiers() []issue.Identifier {
ids := []issue.Identifier{c.primaryIdentifier()} ids := []issue.Identifier{c.primaryIdentifier()}
if id := c.Advisory.Identifier; id.Valid { if id := c.Advisory.Identifier; id != "" {
if identifier, ok := issue.ParseIdentifierID(id.String); ok { if identifier, ok := issue.ParseIdentifierID(id); ok {
ids = append(ids, identifier) ids = append(ids, identifier)
} }
} }
...@@ -71,7 +72,7 @@ func (c VulnerabilityConverter) primaryIdentifier() issue.Identifier { ...@@ -71,7 +72,7 @@ func (c VulnerabilityConverter) primaryIdentifier() issue.Identifier {
} }
func (c VulnerabilityConverter) advisoriesURL() string { func (c VulnerabilityConverter) advisoriesURL() string {
ptype := string(c.Source.PackageType) ptype := string(c.File.PackageType)
name, version := c.Dependency.Name, c.Dependency.Version name, version := c.Dependency.Name, c.Dependency.Version
return fmt.Sprintf("%s/packages/%s/%s/versions/%s/advisories", gemnasiumURL, ptype, name, version) return fmt.Sprintf("%s/packages/%s/%s/versions/%s/advisories", gemnasiumURL, ptype, name, version)
} }
...@@ -3,5 +3,5 @@ module gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2 ...@@ -3,5 +3,5 @@ module gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2
require ( require (
github.com/urfave/cli v1.20.0 github.com/urfave/cli v1.20.0
gitlab.com/gitlab-org/security-products/analyzers/common/v2 v2.4.2 gitlab.com/gitlab-org/security-products/analyzers/common/v2 v2.4.2
gopkg.in/guregu/null.v3 v3.4.0 gopkg.in/yaml.v2 v2.2.4
) )
...@@ -10,6 +10,8 @@ import ( ...@@ -10,6 +10,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strings"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -20,8 +22,9 @@ import ( ...@@ -20,8 +22,9 @@ import (
"gitlab.com/gitlab-org/security-products/analyzers/common/v2/search" "gitlab.com/gitlab-org/security-products/analyzers/common/v2/search"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/convert" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/convert"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/plugin" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/plugin"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/remediate"
"gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner"
scannercli "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/cli" "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/vrange"
_ "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser/composer" _ "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser/composer"
_ "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser/gemfile" _ "gitlab.com/gitlab-org/security-products/analyzers/gemnasium/v2/scanner/parser/gemfile"
...@@ -37,6 +40,13 @@ const ( ...@@ -37,6 +40,13 @@ const (
flagRemediate = "remediate" flagRemediate = "remediate"
flagRemediateTimeout = "remediate-timeout" flagRemediateTimeout = "remediate-timeout"
flagVrangeDir = "vrange-dir"
flagVrangeGemCmd = "vrange-gem-cmd"
flagVrangeMavenCmd = "vrange-maven-cmd"
flagVrangeNpmCmd = "vrange-npm-cmd"
flagVrangePhpCmd = "vrange-php-cmd"
flagVrangePythonCmd = "vrange-python-cmd"
defaultTimeoutRemediate = 5 * time.Minute defaultTimeoutRemediate = 5 * time.Minute
) )
...@@ -75,10 +85,48 @@ func runCommand() cli.Command { ...@@ -75,10 +85,48 @@ func runCommand() cli.Command {
Usage: "Time limit for vulnerabilities auto-remediation", Usage: "Time limit for vulnerabilities auto-remediation",
Value: defaultTimeoutRemediate, Value: defaultTimeoutRemediate,
}, },
// vrange CLIs
cli.StringFlag{
Name: flagVrangeDir,
Usage: "Path of the vrange directory",
EnvVar: "VRANGE_DIR",
Value: "vrange",
},
cli.StringFlag{
Name: flagVrangeGemCmd,
Usage: "vrange command for Rubygem",
EnvVar: "VRANGE_GEM_CMD",
Value: "gem/vrange.rb",
},
cli.StringFlag{
Name: flagVrangeMavenCmd,
Usage: "vrange command for Maven",
EnvVar: "VRANGE_MAVEN_CMD",
Value: "semver/vrange-" + runtime.GOOS + " maven",
},
cli.StringFlag{
Name: flagVrangeNpmCmd,
Usage: "vrange command for npm",
EnvVar: "VRANGE_NPM_CMD",
Value: "npm/rangecheck.js",
},
cli.StringFlag{
Name: flagVrangePhpCmd,
Usage: "vrange command for PHP",
EnvVar: "VRANGE_PHP_CMD",
Value: "php/rangecheck.php",
},
cli.StringFlag{
Name: flagVrangePythonCmd,
Usage: "vrange command for Python",
EnvVar: "VRANGE_PYTHON_CMD",
Value: "python/rangecheck.py",
},
} }
flags = append(flags, search.NewFlags()...) flags = append(flags, search.NewFlags()...)
flags = append(flags, scannercli.ClientFlags()...) flags = append(flags, scanner.Flags()...)
flags = append(flags, scannercli.FinderFlags()...)
flags = append(flags, pathfilter.MakeFlags("DS_")...) flags = append(flags, pathfilter.MakeFlags("DS_")...)
return cli.Command{ return cli.Command{
...@@ -93,6 +141,11 @@ func runCommand() cli.Command { ...@@ -93,6 +141,11 @@ func runCommand() cli.Command {
return errors.New("Invalid number of arguments") return errors.New("Invalid number of arguments")
} }
// register version range resolvers
if err := registerResolvers(c); err != nil {
return err
}
// parse excluded paths // parse excluded paths
filter, err := pathfilter.NewFilter(c) filter, err := pathfilter.NewFilter(c)
if err != nil { if err != nil {
...@@ -113,33 +166,19 @@ func runCommand() cli.Command { ...@@ -113,33 +166,19 @@ func runCommand() cli.Command {
} }
fmt.Fprintln(c.App.Writer, "Found project in "+matchPath) fmt.Fprintln(c.App.Writer, "Found project in "+matchPath)
// find compatible files // scan target directory
finder := scannercli.NewFinder(c) scanner, err := scanner.NewScanner(c)
files, err := finder.FindFiles(targetDir)
if err != nil { if err != nil {
return err return err
} }
result, err := scanner.ScanDir(targetDir)
// get sources of dependencies
sources, err := scanner.ParseFiles(files)
if err != nil { if err != nil {
return err return err
} }
// get advisories // convert to generic report
client, err := scannercli.NewClient(c)
if err != nil {
return err
}
advisories, err := client.Advisories(sources.Packages())
if err != nil {
return err
}
// convert to vulnerabilities and dependency files
var affectedSources = scanner.AffectedSources(advisories, sources...)
var prependPath = "" // empty because the analyzer scans the root directory var prependPath = "" // empty because the analyzer scans the root directory
var report = convert.ToReport(affectedSources, prependPath) var report = convert.ToReport(result, prependPath)
// remediate vulnerabilities // remediate vulnerabilities
if c.BoolT(flagRemediate) { if c.BoolT(flagRemediate) {
...@@ -149,7 +188,7 @@ func runCommand() cli.Command { ...@@ -149,7 +188,7 @@ func runCommand() cli.Command {
var t = c.Duration(flagRemediateTimeout) var t = c.Duration(flagRemediateTimeout)
ctx, cancel := context.WithTimeout(context.Background(), t) ctx, cancel := context.WithTimeout(context.Background(), t)
defer cancel() defer cancel()
report.Remediations = remediations(ctx, affectedSources...) report.Remediations = remediations(ctx, result...)
} }
} }
...@@ -171,12 +210,31 @@ func runCommand() cli.Command { ...@@ -171,12 +210,31 @@ func runCommand() cli.Command {
} }
} }
// remediations attempts to cure affected sources and returns remediations. func registerResolvers(c *cli.Context) error {
func remediations(ctx context.Context, sources ...scanner.AffectedSource) []issue.Remediation { syntaxToFlag := map[string]string{
"gem": flagVrangeGemCmd,
"maven": flagVrangeMavenCmd,
"npm": flagVrangeNpmCmd,
"php": flagVrangePhpCmd,
"python": flagVrangePythonCmd,
}
for syntax, flag := range syntaxToFlag {
cmd := strings.SplitN(c.String(flag), " ", 2)
path := filepath.Join(c.String(flagVrangeDir), cmd[0])
args := cmd[1:]
if err := vrange.RegisterCmd(syntax, path, args...); err != nil {
return err
}
}
return nil
}
// remediations attempts to cure affected dependency files and returns remediations.
func remediations(ctx context.Context, scanFiles ...scanner.File) []issue.Remediation {
var allRems = make([]issue.Remediation, 0) var allRems = make([]issue.Remediation, 0)
for _, source := range sources { for _, file := range scanFiles {
// attempt to cure // attempt to cure
cures, err := cureSource(ctx, source) cures, err := remediate.Remediate(ctx, file)
switch err { switch err {
case nil: case nil:
// proceed // proceed
...@@ -191,14 +249,14 @@ func remediations(ctx context.Context, sources ...scanner.AffectedSource) []issu ...@@ -191,14 +249,14 @@ func remediations(ctx context.Context, sources ...scanner.AffectedSource) []issu
} }
// convert cures to remediations; // convert cures to remediations;
// it can't be extracted out of this loop because source is required. // it can't be extracted out of this loop because dependency file is required.
var rems = make([]issue.Remediation, len(cures)) var rems = make([]issue.Remediation, len(cures))
for i, cure := range cures { for i, cure := range cures {
var refs = make([]issue.IssueRef, len(cure.Affections)) var refs = make([]issue.IssueRef, len(cure.Affections))
for j, a := range cure.Affections { for j, a := range cure.Affections {
// HACK convert to an issue to get the exact compare key // HACK convert to an issue to get the exact compare key
var ckey = convert.VulnerabilityConverter{ var ckey = convert.VulnerabilityConverter{
Source: source, File: file,
Advisory: a.Advisory, Advisory: a.Advisory,
Dependency: a.Dependency, Dependency: a.Dependency,
}.Issue().CompareKey }.Issue().CompareKey
......