add cataloger for rust crates from Cargo.lock files

Signed-off-by: Weston Steimel <weston.steimel@gmail.com>
This commit is contained in:
Weston Steimel 2021-02-28 01:29:37 +00:00
parent a83d79f330
commit ba81bfe529
No known key found for this signature in database
GPG key ID: 61849329106F14D6
13 changed files with 313 additions and 0 deletions

View file

@ -14,6 +14,7 @@ import (
"github.com/anchore/syft/syft/cataloger/python"
"github.com/anchore/syft/syft/cataloger/rpmdb"
"github.com/anchore/syft/syft/cataloger/ruby"
"github.com/anchore/syft/syft/cataloger/rust"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
@ -39,6 +40,7 @@ func ImageCatalogers() []Cataloger {
java.NewJavaCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModCataloger(),
rust.NewCargoLockCataloger(),
}
}
@ -54,5 +56,6 @@ func DirectoryCatalogers() []Cataloger {
java.NewJavaCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModCataloger(),
rust.NewCargoLockCataloger(),
}
}

View file

@ -115,6 +115,14 @@ func TestPackageURL(t *testing.T) {
},
expected: "pkg:deb/name@v0.1.0",
},
{
pkg: pkg.Package{
Name: "name",
Version: "v0.1.0",
Type: pkg.RustPkg,
},
expected: "pkg:cargo/name@v0.1.0",
},
}
for _, test := range tests {

View file

@ -0,0 +1,18 @@
package rust
import "github.com/anchore/syft/syft/pkg"
type CargoMetadata struct {
Packages []CargoMetadataPackage `toml:"package"`
}
// Pkgs returns all of the packages referenced within the Cargo.lock metadata.
func (m CargoMetadata) Pkgs() []pkg.Package {
pkgs := make([]pkg.Package, 0)
for _, p := range m.Packages {
pkgs = append(pkgs, p.Pkg())
}
return pkgs
}

View file

@ -0,0 +1,21 @@
package rust
import "github.com/anchore/syft/syft/pkg"
type CargoMetadataPackage struct {
Name string `toml:"name"`
Version string `toml:"version"`
Source string `toml:"source"`
Checksum string `toml:"checksum"`
Dependencies []string `toml:"dependencies"`
}
// Pkg returns the standard `pkg.Package` representation of the package referenced within the Cargo.lock metadata.
func (p CargoMetadataPackage) Pkg() pkg.Package {
return pkg.Package{
Name: p.Name,
Version: p.Version,
Language: pkg.Rust,
Type: pkg.RustPkg,
}
}

View file

@ -0,0 +1,17 @@
/*
Package rust provides a concrete Cataloger implementation for Cargo.lock files.
*/
package rust
import (
"github.com/anchore/syft/syft/cataloger/common"
)
// NewCargoLockCataloger returns a new Rust Cargo lock file cataloger object.
func NewCargoLockCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/Cargo.lock": parseCargoLock,
}
return common.NewGenericCataloger(nil, globParsers, "rust-cataloger")
}

View file

@ -0,0 +1,29 @@
package rust
import (
"fmt"
"io"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/pelletier/go-toml"
)
// integrity check
var _ common.ParserFn = parseCargoLock
// parseCargoLock is a parser function for Cargo.lock contents, returning all rust cargo crates discovered.
func parseCargoLock(_ string, reader io.Reader) ([]pkg.Package, error) {
tree, err := toml.LoadReader(reader)
if err != nil {
return nil, fmt.Errorf("unable to load Cargo.lock for parsing: %v", err)
}
metadata := CargoMetadata{}
err = tree.Unmarshal(&metadata)
if err != nil {
return nil, fmt.Errorf("unable to parse Cargo.lock: %v", err)
}
return metadata.Pkgs(), nil
}

View file

@ -0,0 +1,99 @@
package rust
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParseCargoLock(t *testing.T) {
expected := []pkg.Package{
{
Name: "ansi_term",
Version: "0.12.1",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "matches",
Version: "0.1.8",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "memchr",
Version: "2.3.3",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "natord",
Version: "1.0.9",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "nom",
Version: "4.2.3",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "unicode-bidi",
Version: "0.3.4",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "version_check",
Version: "0.1.5",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "winapi",
Version: "0.3.9",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
{
Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0",
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: nil,
},
}
fixture, err := os.Open("test-fixtures/Cargo.lock")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parseCargoLock(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}

View file

@ -0,0 +1,76 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "natord"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [
"matches",
]
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -11,6 +11,7 @@ const (
Python Language = "python"
Ruby Language = "ruby"
Go Language = "go"
Rust Language = "rust"
)
// AllLanguages is a set of all programming languages detected by syft.
@ -20,6 +21,7 @@ var AllLanguages = []Language{
Python,
Ruby,
Go,
Rust,
}
// String returns the string representation of the language.

View file

@ -13,4 +13,5 @@ const (
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
RpmdbMetadataType MetadataType = "RpmdbMetadata"
PythonPackageMetadataType MetadataType = "PythonPackageMetadata"
RustCrateMetadataType MetadataType = "RustCrateMetadata"
)

View file

@ -17,6 +17,7 @@ const (
JavaPkg Type = "java-archive"
JenkinsPluginPkg Type = "jenkins-plugin"
GoModulePkg Type = "go-module"
RustPkg Type = "rust-crate"
)
// AllPkgs represents all supported package types
@ -30,6 +31,7 @@ var AllPkgs = []Type{
JavaPkg,
JenkinsPluginPkg,
GoModulePkg,
RustPkg,
}
// PackageURLType returns the PURL package type for the current package.
@ -51,6 +53,8 @@ func (t Type) PackageURLType() string {
return packageurl.TypeRPM
case GoModulePkg:
return packageurl.TypeGolang
case RustPkg:
return "cargo"
default:
// TODO: should this be a "generic" purl type instead?
return ""

View file

@ -184,4 +184,14 @@ var commonTestCases = []testCase{
"github.com/bmatcuk/doublestar": "v1.3.1",
},
},
{
name: "find rust crates",
pkgType: pkg.RustPkg,
pkgLanguage: pkg.Rust,
pkgInfo: map[string]string{
"memchr": "2.3.3",
"nom": "4.2.3",
"version_check": "0.1.5",
},
},
}

View file

@ -0,0 +1,25 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"