Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(c): don't skip conan files from file-patterns and scan .conan2 cache dir #6949

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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