Skip to content

Commit

Permalink
fix(c): don't skip conan files from file-patterns and scan `.conan2…
Browse files Browse the repository at this point in the history
…` cache dir (#6949)
  • Loading branch information
DmitriyLewen authored Jun 19, 2024
1 parent eb6d0d9 commit 38b35dd
Show file tree
Hide file tree
Showing 6 changed files with 934 additions and 21 deletions.
5 changes: 3 additions & 2 deletions docs/docs/coverage/language/c.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ In order to detect dependencies, Trivy searches for `conan.lock`[^1].

### Licenses
The Conan lock file doesn't contain any license information.
To obtain licenses we parse the `conanfile.py` files from the [conan cache directory][conan-cache-dir].
To obtain licenses we parse the `conanfile.py` files from the [conan v1 cache directory][conan-v1-cache-dir] and [conan v2 cache directory][conan-v2-cache-dir].
To correctly detection licenses, ensure that the cache directory contains all dependencies used.

[conan-cache-dir]: https://docs.conan.io/1/mastering/custom_cache.html
[conan-v1-cache-dir]: https://docs.conan.io/1/mastering/custom_cache.html
[conan-v2-cache-dir]: https://docs.conan.io/2/reference/environment.html#conan-home
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies

[^1]: The local cache should contain the dependencies used. See [licenses](#licenses).
Expand Down
50 changes: 38 additions & 12 deletions pkg/fanal/analyzer/language/c/conan/conan.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sort"
"strings"

"github.com/samber/lo"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan"
Expand Down Expand Up @@ -44,7 +45,8 @@ func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, er

func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) {
required := func(filePath string, d fs.DirEntry) bool {
return a.Required(filePath, nil)
// we need all file got from `a.Required` function (conan.lock files) and from file-patterns.
return true
}

licenses, err := licensesFromCache()
Expand Down Expand Up @@ -85,19 +87,13 @@ func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna
}

func licensesFromCache() (map[string]string, error) {
required := func(filePath string, d fs.DirEntry) bool {
return filepath.Base(filePath) == "conanfile.py"
}

// cf. https://docs.conan.io/1/mastering/custom_cache.html
cacheDir := os.Getenv("CONAN_USER_HOME")
if cacheDir == "" {
cacheDir, _ = os.UserHomeDir()
cacheDir, err := detectCacheDir()
if err != nil {
return nil, err
}
cacheDir = path.Join(cacheDir, ".conan", "data")

if !fsutils.DirExists(cacheDir) {
return nil, xerrors.Errorf("the Conan cache directory (%s) was not found.", cacheDir)
required := func(filePath string, d fs.DirEntry) bool {
return filepath.Base(filePath) == "conanfile.py"
}

licenses := make(map[string]string)
Expand Down Expand Up @@ -154,6 +150,36 @@ func detectAttribute(attributeName, line string) string {
return ""
}

func detectCacheDir() (string, error) {
home, _ := os.UserHomeDir()
dirs := []string{
// conan v2 uses `CONAN_HOME` env
// cf. https://docs.conan.io/2/reference/environment.html#conan-home
// `.conan2` dir is omitted for this env
lo.Ternary(os.Getenv("CONAN_HOME") != "", path.Join(os.Getenv("CONAN_HOME"), "p"), ""),
// conan v1 uses `CONAN_USER_HOME` env
// cf. https://docs.conan.io/en/1.64/reference/env_vars.html#conan-user-home
// `.conan` dir is used for this env
lo.Ternary(os.Getenv("CONAN_USER_HOME") != "", path.Join(os.Getenv("CONAN_USER_HOME"), ".conan", "data"), ""),
// `<username>/.conan2` is default directory for conan v2
// cf. https://docs.conan.io/2/reference/environment.html#conan-home
path.Join(home, ".conan2", "p"),
// `<username>/.conan` is default directory for conan v1
// cf. https://docs.conan.io/1/mastering/custom_cache.html
path.Join(home, ".conan", "data"),
}

for _, dir := range dirs {
if dir != "" {
if fsutils.DirExists(dir) {
return dir, nil
}
}
}

return "", xerrors.Errorf("the Conan cache directory was not found.")
}

func (a conanLockAnalyzer) Required(filePath string, _ os.FileInfo) bool {
// Lock file name can be anything
// cf. https://docs.conan.io/1/versioning/lockfiles/introduction.html#locking-dependencies
Expand Down
105 changes: 98 additions & 7 deletions pkg/fanal/analyzer/language/c/conan/conan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
dir string
cacheDir string
cacheDir map[string]string
want *analyzer.AnalysisResult
}{
{
name: "happy path",
name: "happy path V1",
dir: "testdata/happy",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
Expand Down Expand Up @@ -62,9 +62,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
},
},
{
name: "happy path with cache dir",
dir: "testdata/happy",
cacheDir: "testdata/cacheDir",
name: "happy path V1 with cache dir",
dir: "testdata/happy",
cacheDir: map[string]string{
"CONAN_USER_HOME": "testdata/cacheDir",
},
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Expand Down Expand Up @@ -110,6 +112,92 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
},
},
},
{
name: "happy path V2",
dir: "testdata/happy_v2",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Conan,
FilePath: "release.lock",
Packages: types.Packages{
{
ID: "openssl/3.2.2",
Name: "openssl",
Version: "3.2.2",
Relationship: types.RelationshipUnknown,
Locations: []types.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
{
ID: "zlib/1.3.1",
Name: "zlib",
Version: "1.3.1",
Relationship: types.RelationshipUnknown,
Locations: []types.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
},
},
},
},
},
{
name: "happy path V2 with cache dir",
dir: "testdata/happy_v2",
cacheDir: map[string]string{
"CONAN_HOME": "testdata/cacheDir_v2",
},
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Conan,
FilePath: "release.lock",
Packages: types.Packages{

{
ID: "openssl/3.2.2",
Name: "openssl",
Version: "3.2.2",
Relationship: types.RelationshipUnknown,
Locations: []types.Location{
{
StartLine: 5,
EndLine: 5,
},
},
Licenses: []string{
"Apache-2.0",
},
},
{
ID: "zlib/1.3.1",
Name: "zlib",
Version: "1.3.1",
Relationship: types.RelationshipUnknown,
Locations: []types.Location{
{
StartLine: 4,
EndLine: 4,
},
},
Licenses: []string{
"Zlib",
},
},
},
},
},
},
},
{
name: "empty file",
dir: "testdata/empty",
Expand All @@ -119,8 +207,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.cacheDir != "" {
t.Setenv("CONAN_USER_HOME", tt.cacheDir)
if len(tt.cacheDir) > 0 {
for env, path := range tt.cacheDir {
t.Setenv(env, path)
break
}
}
a, err := newConanLockAnalyzer(analyzer.AnalyzerOptions{})
require.NoError(t, err)
Expand Down
Loading

0 comments on commit 38b35dd

Please sign in to comment.