Skip to content

Commit 38b35dd

Browse files
authored
fix(c): don't skip conan files from file-patterns and scan .conan2 cache dir (#6949)
1 parent eb6d0d9 commit 38b35dd

File tree

6 files changed

+934
-21
lines changed

6 files changed

+934
-21
lines changed

docs/docs/coverage/language/c.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ In order to detect dependencies, Trivy searches for `conan.lock`[^1].
2323

2424
### Licenses
2525
The Conan lock file doesn't contain any license information.
26-
To obtain licenses we parse the `conanfile.py` files from the [conan cache directory][conan-cache-dir].
26+
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].
2727
To correctly detection licenses, ensure that the cache directory contains all dependencies used.
2828

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

3233
[^1]: The local cache should contain the dependencies used. See [licenses](#licenses).

pkg/fanal/analyzer/language/c/conan/conan.go

+38-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sort"
1212
"strings"
1313

14+
"github.com/samber/lo"
1415
"golang.org/x/xerrors"
1516

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

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

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

8789
func licensesFromCache() (map[string]string, error) {
88-
required := func(filePath string, d fs.DirEntry) bool {
89-
return filepath.Base(filePath) == "conanfile.py"
90-
}
91-
92-
// cf. https://docs.conan.io/1/mastering/custom_cache.html
93-
cacheDir := os.Getenv("CONAN_USER_HOME")
94-
if cacheDir == "" {
95-
cacheDir, _ = os.UserHomeDir()
90+
cacheDir, err := detectCacheDir()
91+
if err != nil {
92+
return nil, err
9693
}
97-
cacheDir = path.Join(cacheDir, ".conan", "data")
9894

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

10399
licenses := make(map[string]string)
@@ -154,6 +150,36 @@ func detectAttribute(attributeName, line string) string {
154150
return ""
155151
}
156152

153+
func detectCacheDir() (string, error) {
154+
home, _ := os.UserHomeDir()
155+
dirs := []string{
156+
// conan v2 uses `CONAN_HOME` env
157+
// cf. https://docs.conan.io/2/reference/environment.html#conan-home
158+
// `.conan2` dir is omitted for this env
159+
lo.Ternary(os.Getenv("CONAN_HOME") != "", path.Join(os.Getenv("CONAN_HOME"), "p"), ""),
160+
// conan v1 uses `CONAN_USER_HOME` env
161+
// cf. https://docs.conan.io/en/1.64/reference/env_vars.html#conan-user-home
162+
// `.conan` dir is used for this env
163+
lo.Ternary(os.Getenv("CONAN_USER_HOME") != "", path.Join(os.Getenv("CONAN_USER_HOME"), ".conan", "data"), ""),
164+
// `<username>/.conan2` is default directory for conan v2
165+
// cf. https://docs.conan.io/2/reference/environment.html#conan-home
166+
path.Join(home, ".conan2", "p"),
167+
// `<username>/.conan` is default directory for conan v1
168+
// cf. https://docs.conan.io/1/mastering/custom_cache.html
169+
path.Join(home, ".conan", "data"),
170+
}
171+
172+
for _, dir := range dirs {
173+
if dir != "" {
174+
if fsutils.DirExists(dir) {
175+
return dir, nil
176+
}
177+
}
178+
}
179+
180+
return "", xerrors.Errorf("the Conan cache directory was not found.")
181+
}
182+
157183
func (a conanLockAnalyzer) Required(filePath string, _ os.FileInfo) bool {
158184
// Lock file name can be anything
159185
// cf. https://docs.conan.io/1/versioning/lockfiles/introduction.html#locking-dependencies

pkg/fanal/analyzer/language/c/conan/conan_test.go

+98-7
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
1616
tests := []struct {
1717
name string
1818
dir string
19-
cacheDir string
19+
cacheDir map[string]string
2020
want *analyzer.AnalysisResult
2121
}{
2222
{
23-
name: "happy path",
23+
name: "happy path V1",
2424
dir: "testdata/happy",
2525
want: &analyzer.AnalysisResult{
2626
Applications: []types.Application{
@@ -62,9 +62,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
6262
},
6363
},
6464
{
65-
name: "happy path with cache dir",
66-
dir: "testdata/happy",
67-
cacheDir: "testdata/cacheDir",
65+
name: "happy path V1 with cache dir",
66+
dir: "testdata/happy",
67+
cacheDir: map[string]string{
68+
"CONAN_USER_HOME": "testdata/cacheDir",
69+
},
6870
want: &analyzer.AnalysisResult{
6971
Applications: []types.Application{
7072
{
@@ -110,6 +112,92 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
110112
},
111113
},
112114
},
115+
{
116+
name: "happy path V2",
117+
dir: "testdata/happy_v2",
118+
want: &analyzer.AnalysisResult{
119+
Applications: []types.Application{
120+
{
121+
Type: types.Conan,
122+
FilePath: "release.lock",
123+
Packages: types.Packages{
124+
{
125+
ID: "openssl/3.2.2",
126+
Name: "openssl",
127+
Version: "3.2.2",
128+
Relationship: types.RelationshipUnknown,
129+
Locations: []types.Location{
130+
{
131+
StartLine: 5,
132+
EndLine: 5,
133+
},
134+
},
135+
},
136+
{
137+
ID: "zlib/1.3.1",
138+
Name: "zlib",
139+
Version: "1.3.1",
140+
Relationship: types.RelationshipUnknown,
141+
Locations: []types.Location{
142+
{
143+
StartLine: 4,
144+
EndLine: 4,
145+
},
146+
},
147+
},
148+
},
149+
},
150+
},
151+
},
152+
},
153+
{
154+
name: "happy path V2 with cache dir",
155+
dir: "testdata/happy_v2",
156+
cacheDir: map[string]string{
157+
"CONAN_HOME": "testdata/cacheDir_v2",
158+
},
159+
want: &analyzer.AnalysisResult{
160+
Applications: []types.Application{
161+
{
162+
Type: types.Conan,
163+
FilePath: "release.lock",
164+
Packages: types.Packages{
165+
166+
{
167+
ID: "openssl/3.2.2",
168+
Name: "openssl",
169+
Version: "3.2.2",
170+
Relationship: types.RelationshipUnknown,
171+
Locations: []types.Location{
172+
{
173+
StartLine: 5,
174+
EndLine: 5,
175+
},
176+
},
177+
Licenses: []string{
178+
"Apache-2.0",
179+
},
180+
},
181+
{
182+
ID: "zlib/1.3.1",
183+
Name: "zlib",
184+
Version: "1.3.1",
185+
Relationship: types.RelationshipUnknown,
186+
Locations: []types.Location{
187+
{
188+
StartLine: 4,
189+
EndLine: 4,
190+
},
191+
},
192+
Licenses: []string{
193+
"Zlib",
194+
},
195+
},
196+
},
197+
},
198+
},
199+
},
200+
},
113201
{
114202
name: "empty file",
115203
dir: "testdata/empty",
@@ -119,8 +207,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
119207

120208
for _, tt := range tests {
121209
t.Run(tt.name, func(t *testing.T) {
122-
if tt.cacheDir != "" {
123-
t.Setenv("CONAN_USER_HOME", tt.cacheDir)
210+
if len(tt.cacheDir) > 0 {
211+
for env, path := range tt.cacheDir {
212+
t.Setenv(env, path)
213+
break
214+
}
124215
}
125216
a, err := newConanLockAnalyzer(analyzer.AnalyzerOptions{})
126217
require.NoError(t, err)

0 commit comments

Comments
 (0)