Compare commits

...

14 Commits

Author SHA1 Message Date
chenk
eadc6fb641 fix: node-collector high and critical cves (#6707)
Signed-off-by: chenk <hen.keinan@gmail.com>
Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
2024-05-20 09:47:05 +04:00
Luke Young
cc489b1af5 Merge pull request from GHSA-xcq4-m2r3-cmrj
* Update azure.go

* Update ecr.go

* Update google.go

* Update ecr_test.go

* Update azure_test.go

* Update google_test.go
2024-05-20 07:19:14 +04:00
chenk
013f71a6a3 chore: auto-bump golang patch versions (#6711)
Signed-off-by: chenk <hen.keinan@gmail.com>
2024-05-20 07:19:07 +04:00
Nikita Pivkin
113a5b2162 fix(misconf): don't shift ignore rule related to code (#6708) 2024-05-20 07:18:52 +04:00
DmitriyLewen
733e5ac1fb fix(go): include only .version|.ver (no prefixes) ldflags for gobinaries (#6705) 2024-05-20 07:18:38 +04:00
DmitriyLewen
d311e49bc3 fix(go): add only non-empty root modules for gobinaries (#6710) 2024-05-20 07:18:31 +04:00
Teppei Fukuda
cf1a7bf30b refactor: unify package addition and vulnerability scanning (#6579)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
2024-05-20 07:18:09 +04:00
Luke Young
d465d9d1e0 fix: Golang version parsing from binaries w/GOEXPERIMENT (#6696) 2024-05-20 07:17:46 +04:00
DmitriyLewen
0af225ccf1 fix(conda): add support pip deps for environment.yml files (#6675) 2024-05-20 07:17:25 +04:00
Nikita Pivkin
6f64d55180 fix(misconf): skip Rego errors with a nil location (#6666) 2024-05-20 07:16:57 +04:00
Nikita Pivkin
8c27430a2f fix(misconf): skip Rego errors with a nil location (#6638) 2024-05-20 07:16:41 +04:00
Teppei Fukuda
c2b46d3c20 refactor: unify Library and Package structs (#6633)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
2024-05-20 07:15:54 +04:00
chenk
4368f11e0d fix: use of specified context to obtain cluster name (#6645)
Signed-off-by: chenk <hen.keinan@gmail.com>
2024-05-20 07:15:48 +04:00
Nikita Pivkin
5ec62f8636 docs: fix usage of image-config-scanners (#6635) 2024-05-20 07:14:20 +04:00
181 changed files with 4330 additions and 3881 deletions

View File

@@ -5,7 +5,8 @@ on:
- 'misc/triage/labels.yaml'
branches:
- main
env:
GO_VERSION: '1.22'
jobs:
deploy:
name: Auto-update labels
@@ -17,7 +18,8 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
# cf. https://github.com/aquasecurity/trivy/pull/6711
go-version: ${{ env.GO_VERSION }}
- name: Install aqua tools
uses: aquaproj/aqua-installer@v3.0.0

View File

@@ -14,6 +14,7 @@ on:
env:
GH_USER: "aqua-bot"
GO_VERSION: '1.22'
jobs:
release:
@@ -76,7 +77,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
cache: false # Disable cache to avoid free space issues during `Post Setup Go` step.
- name: Generate SBOM

View File

@@ -7,6 +7,8 @@ on:
- 'mkdocs.yml'
- 'LICENSE'
merge_group:
env:
GO_VERSION: '1.22'
jobs:
test:
name: Test
@@ -30,8 +32,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: go mod tidy
run: |
go mod tidy
@@ -84,7 +85,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: Install tools
uses: aquaproj/aqua-installer@v3.0.0
@@ -113,7 +114,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: Install tools
uses: aquaproj/aqua-installer@v3.0.0
@@ -133,7 +134,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: Install tools
uses: aquaproj/aqua-installer@v3.0.0
@@ -164,7 +165,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: Install tools
uses: aquaproj/aqua-installer@v3.0.0
with:
@@ -198,7 +199,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: ${{ env.GO_VERSION }}
- name: Determine GoReleaser ID
id: goreleaser_id

View File

@@ -71,7 +71,7 @@ trivy kubernetes [flags] [CONTEXT]
--list-all-pkgs enabling the option will output all packages regardless of vulnerability
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--no-progress suppress progress bar
--node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.0.9")
--node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.2.1")
--node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp")
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name

View File

@@ -107,7 +107,7 @@ The image config is converted into Dockerfile and Trivy handles it as Dockerfile
See [here](../scanner/misconfiguration/index.md) for the detail of Dockerfile scanning.
It is disabled by default.
You can enable it with `--image-config-scanners config`.
You can enable it with `--image-config-scanners misconfig`.
```
$ trivy image --image-config-scanners misconfig [YOUR_IMAGE_NAME]

26
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/aquasecurity/trivy
go 1.22
go 1.22.0
toolchain go1.22.0
toolchain go1.22.2
require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
@@ -27,7 +27,7 @@ require (
github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e
github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
@@ -115,7 +115,8 @@ require (
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
google.golang.org/protobuf v1.34.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.3
helm.sh/helm/v3 v3.14.2
k8s.io/api v0.30.0
k8s.io/utils v0.0.0-20231127182322-b307cd553661
modernc.org/sqlite v1.29.7
)
@@ -137,7 +138,6 @@ require (
github.com/zclconf/go-cty v1.14.4
github.com/zclconf/go-cty-yaml v1.0.3
golang.org/x/crypto v0.22.0
helm.sh/helm/v3 v3.14.2
sigs.k8s.io/yaml v1.4.0
)
@@ -177,7 +177,7 @@ require (
github.com/antchfx/xpath v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.51.16 // indirect
github.com/aws/aws-sdk-go v1.53.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
@@ -410,14 +410,14 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiextensions-apiserver v0.29.0 // indirect
k8s.io/apimachinery v0.29.3 // indirect
k8s.io/apimachinery v0.30.0 // indirect
k8s.io/apiserver v0.29.0 // indirect
k8s.io/cli-runtime v0.29.3 // indirect
k8s.io/client-go v0.29.3 // indirect
k8s.io/component-base v0.29.3 // indirect
k8s.io/klog/v2 v2.120.0 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/kubectl v0.29.3 // indirect
k8s.io/cli-runtime v0.30.0 // indirect
k8s.io/client-go v0.30.0 // indirect
k8s.io/component-base v0.30.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/kubectl v0.30.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.49.3 // indirect
modernc.org/mathutil v1.6.0 // indirect

48
go.sum
View File

@@ -781,8 +781,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU
github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb h1:U07awOdXGT8NMwTPVuXkL/cKZyvO4PuG+VX1oIvsuiQ=
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo=
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 h1:bLmh/xuC/7abvt9S/xnODTQRu8fW6BhFHS6Cmbn0RNU=
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7/go.mod h1:HSpAJE8Y5Cjjg0Aw/0lqd3vMihN/FxBEj/f/7yDi/Uc=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -796,8 +796,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI=
github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo=
github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
@@ -1853,15 +1853,15 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
github.com/open-policy-agent/opa v0.64.1 h1:n8IJTYlFWzqiOYx+JiawbErVxiqAyXohovcZxYbskxQ=
github.com/open-policy-agent/opa v0.64.1/go.mod h1:j4VeLorVpKipnkQ2TDjWshEuV3cvP/rHzQhYaraUXZY=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -3089,32 +3089,32 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0=
k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc=
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o=
k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM=
k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k=
k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM=
k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48=
k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg=
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo=
k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio=
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
@@ -3122,13 +3122,13 @@ k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8=
k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us=
k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk=
k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=

View File

@@ -21,6 +21,30 @@
"Class": "lang-pkgs",
"Type": "conan",
"Packages": [
{
"ID": "poco/1.9.4",
"Name": "poco",
"Identifier": {
"PURL": "pkg:conan/poco@1.9.4",
"UID": "312753cebe80c0eb"
},
"Version": "1.9.4",
"Relationship": "direct",
"DependsOn": [
"pcre/8.43",
"zlib/1.2.12",
"expat/2.4.8",
"sqlite3/3.39.2",
"openssl/1.1.1q"
],
"Layer": {},
"Locations": [
{
"StartLine": 12,
"EndLine": 25
}
]
},
{
"ID": "bzip2/1.0.8",
"Name": "bzip2",
@@ -97,30 +121,6 @@
}
]
},
{
"ID": "poco/1.9.4",
"Name": "poco",
"Identifier": {
"PURL": "pkg:conan/poco@1.9.4",
"UID": "312753cebe80c0eb"
},
"Version": "1.9.4",
"Relationship": "direct",
"DependsOn": [
"pcre/8.43",
"zlib/1.2.12",
"expat/2.4.8",
"sqlite3/3.39.2",
"openssl/1.1.1q"
],
"Layer": {},
"Locations": [
{
"StartLine": 12,
"EndLine": 25
}
]
},
{
"ID": "sqlite3/3.39.2",
"Name": "sqlite3",

View File

@@ -35,6 +35,17 @@
],
"Layer": {}
},
{
"ID": "werkzeug@0.14",
"Name": "werkzeug",
"Identifier": {
"PURL": "pkg:pypi/werkzeug@0.14",
"UID": "4176be111ad01070"
},
"Version": "0.14",
"Relationship": "direct",
"Layer": {}
},
{
"ID": "colorama@0.4.6",
"Name": "colorama",
@@ -46,17 +57,6 @@
"Indirect": true,
"Relationship": "indirect",
"Layer": {}
},
{
"ID": "werkzeug@0.14",
"Name": "werkzeug",
"Identifier": {
"PURL": "pkg:pypi/werkzeug@0.14",
"UID": "4176be111ad01070"
},
"Version": "0.14",
"Relationship": "direct",
"Layer": {}
}
],
"Vulnerabilities": [

View File

@@ -10,7 +10,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -44,27 +43,27 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("conan"),
}
}
func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
var deps []types.Dependency
func (p *Parser) parseV1(lock LockFile) ([]ftypes.Package, []ftypes.Dependency, error) {
var pkgs []ftypes.Package
var deps []ftypes.Dependency
var directDeps []string
if root, ok := lock.GraphLock.Nodes["0"]; ok {
directDeps = root.Requires
}
// Parse packages
parsed := make(map[string]types.Library)
parsed := make(map[string]ftypes.Package)
for i, node := range lock.GraphLock.Nodes {
if node.Ref == "" {
continue
}
lib, err := toLibrary(node.Ref, node.StartLine, node.EndLine)
pkg, err := toPackage(node.Ref, node.StartLine, node.EndLine)
if err != nil {
p.logger.Debug("Parse ref error", log.Err(err))
continue
@@ -72,14 +71,14 @@ func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, er
// Determine if the package is a direct dependency or not
direct := slices.Contains(directDeps, i)
lib.Relationship = lo.Ternary(direct, types.RelationshipDirect, types.RelationshipIndirect)
pkg.Relationship = lo.Ternary(direct, ftypes.RelationshipDirect, ftypes.RelationshipIndirect)
parsed[i] = lib
parsed[i] = pkg
}
// Parse dependency graph
for i, node := range lock.GraphLock.Nodes {
lib, ok := parsed[i]
pkg, ok := parsed[i]
if !ok {
continue
}
@@ -91,33 +90,33 @@ func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, er
}
}
if len(childDeps) != 0 {
deps = append(deps, types.Dependency{
ID: lib.ID,
deps = append(deps, ftypes.Dependency{
ID: pkg.ID,
DependsOn: childDeps,
})
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
}
return libs, deps, nil
return pkgs, deps, nil
}
func (p *Parser) parseV2(lock LockFile) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
func (p *Parser) parseV2(lock LockFile) ([]ftypes.Package, []ftypes.Dependency, error) {
var pkgs []ftypes.Package
for _, req := range lock.Requires {
lib, err := toLibrary(req.Dependency, req.StartLine, req.EndLine)
pkg, err := toPackage(req.Dependency, req.StartLine, req.EndLine)
if err != nil {
p.logger.Debug("Creating library entry from requirement failed", err)
p.logger.Debug("Creating package entry from requirement failed", err)
continue
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
}
return libs, []types.Dependency{}, nil
return pkgs, []ftypes.Dependency{}, nil
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lock LockFile
input, err := io.ReadAll(r)
@@ -153,16 +152,16 @@ func parsePackage(text string) (string, string, error) {
return ss[0], ss[1], nil
}
func toLibrary(pkg string, startLine, endLine int) (types.Library, error) {
func toPackage(pkg string, startLine, endLine int) (ftypes.Package, error) {
name, version, err := parsePackage(pkg)
if err != nil {
return types.Library{}, err
return ftypes.Package{}, err
}
return types.Library{
return ftypes.Package{
ID: dependency.ID(ftypes.Conan, name, version),
Name: name,
Version: version,
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: startLine,
EndLine: endLine,

View File

@@ -3,65 +3,64 @@ package conan_test
import (
"os"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
inputFile string // Test input file
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "happy path",
inputFile: "testdata/happy_v1_case1.lock",
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "pkga/0.0.1",
Name: "pkga",
Version: "0.0.1",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 13,
EndLine: 22,
},
},
},
{
ID: "pkgb/system",
Name: "pkgb",
Version: "system",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 23,
EndLine: 29,
},
},
},
{
ID: "pkgc/0.1.1",
Name: "pkgc",
Version: "0.1.1",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 30,
EndLine: 35,
},
},
},
{
ID: "pkgb/system",
Name: "pkgb",
Version: "system",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 23,
EndLine: 29,
},
},
},
},
wantDeps: []types.Dependency{
wantDeps: []ftypes.Dependency{
{
ID: "pkga/0.0.1",
DependsOn: []string{
@@ -73,13 +72,13 @@ func TestParse(t *testing.T) {
{
name: "happy path. lock file with revisions support",
inputFile: "testdata/happy_v1_case2.lock",
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "openssl/3.0.3",
Name: "openssl",
Version: "3.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 12,
EndLine: 22,
@@ -90,8 +89,8 @@ func TestParse(t *testing.T) {
ID: "zlib/1.2.12",
Name: "zlib",
Version: "1.2.12",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 23,
EndLine: 30,
@@ -99,7 +98,7 @@ func TestParse(t *testing.T) {
},
},
},
wantDeps: []types.Dependency{
wantDeps: []ftypes.Dependency{
{
ID: "openssl/3.0.3",
DependsOn: []string{
@@ -111,12 +110,12 @@ func TestParse(t *testing.T) {
{
name: "happy path conan v2",
inputFile: "testdata/happy_v2.lock",
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "matrix/1.3",
Name: "matrix",
Version: "1.3",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 5,
@@ -127,7 +126,7 @@ func TestParse(t *testing.T) {
ID: "sound32/1.0",
Name: "sound32",
Version: "1.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 4,
EndLine: 4,
@@ -135,7 +134,7 @@ func TestParse(t *testing.T) {
},
},
},
wantDeps: []types.Dependency{},
wantDeps: []ftypes.Dependency{},
},
{
name: "happy path. lock file without dependencies",
@@ -153,18 +152,12 @@ func TestParse(t *testing.T) {
require.NoError(t, err)
defer f.Close()
gotLibs, gotDeps, err := conan.NewParser().Parse(f)
gotPkgs, gotDeps, err := conan.NewParser().Parse(f)
require.NoError(t, err)
sort.Slice(gotLibs, func(i, j int) bool {
ret := strings.Compare(gotLibs[i].Name, gotLibs[j].Name)
if ret != 0 {
return ret < 0
}
return gotLibs[i].Version < gotLibs[j].Version
})
sort.Sort(ftypes.Packages(gotPkgs))
assert.Equal(t, tt.wantLibs, gotLibs)
assert.Equal(t, tt.wantPkgs, gotPkgs)
assert.Equal(t, tt.wantDeps, gotDeps)
})
}

View File

@@ -9,13 +9,17 @@ import (
"gopkg.in/yaml.v3"
"github.com/aquasecurity/go-version/pkg/version"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type environment struct {
Dependencies []Dependency `yaml:"dependencies"`
Entries []Entry `yaml:"dependencies"`
}
type Entry struct {
Dependencies []Dependency
}
type Dependency struct {
@@ -28,44 +32,46 @@ type Parser struct {
once sync.Once
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("conda"),
once: sync.Once{},
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var env environment
if err := yaml.NewDecoder(r).Decode(&env); err != nil {
return nil, nil, xerrors.Errorf("unable to decode conda environment.yml file: %w", err)
}
var libs []types.Library
for _, dep := range env.Dependencies {
lib := p.toLibrary(dep)
// Skip empty libs
if lib.Name == "" {
continue
var pkgs ftypes.Packages
for _, entry := range env.Entries {
for _, dep := range entry.Dependencies {
pkg := p.toPackage(dep)
// Skip empty pkgs
if pkg.Name == "" {
continue
}
pkgs = append(pkgs, pkg)
}
libs = append(libs, lib)
}
sort.Sort(types.Libraries(libs))
return libs, nil, nil
sort.Sort(pkgs)
return pkgs, nil, nil
}
func (p *Parser) toLibrary(dep Dependency) types.Library {
func (p *Parser) toPackage(dep Dependency) ftypes.Package {
name, ver := p.parseDependency(dep.Value)
if ver == "" {
p.once.Do(func() {
p.logger.Warn("Unable to detect the dependency versions from `environment.yml` as those versions are not pinned. Use `conda env export` to pin versions.")
})
}
return types.Library{
return ftypes.Package{
Name: name,
Version: ver,
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: dep.Line,
EndLine: dep.Line,
@@ -96,8 +102,40 @@ func (*Parser) parseDependency(line string) (string, string) {
return name, parts[1]
}
func (d *Dependency) UnmarshalYAML(node *yaml.Node) error {
d.Value = node.Value
d.Line = node.Line
func (e *Entry) UnmarshalYAML(node *yaml.Node) error {
var dependencies []Dependency
// cf. https://github.com/go-yaml/yaml/blob/f6f7691b1fdeb513f56608cd2c32c51f8194bf51/resolve.go#L70-L81
switch node.Tag {
case "!!str":
dependencies = append(dependencies, Dependency{
Value: node.Value,
Line: node.Line,
})
case "!!map":
if node.Content != nil {
// Map key is package manager (e.g. pip). So we need to store only map values (dependencies).
// e.g. dependencies:
// - pip:
// - pandas==2.1.4
if node.Content[1].Tag != "!!seq" { // Conda supports only map[string][]string format.
return xerrors.Errorf("unsupported dependency type %q on line %d", node.Content[1].Tag, node.Content[1].Line)
}
for _, depContent := range node.Content[1].Content {
if depContent.Tag != "!!str" {
return xerrors.Errorf("unsupported dependency type %q on line %d", depContent.Tag, depContent.Line)
}
dependencies = append(dependencies, Dependency{
Value: depContent.Value,
Line: depContent.Line,
})
}
}
default:
return xerrors.Errorf("unsupported dependency type %q on line %d", node.Tag, node.Line)
}
e.Dependencies = dependencies
return nil
}

View File

@@ -8,33 +8,43 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "happy path",
input: "testdata/happy.yaml",
want: []types.Library{
want: []ftypes.Package{
{
Name: "_openmp_mutex",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 6,
EndLine: 6,
},
},
},
{
Name: "asgiref",
Version: "3.8.1",
Locations: ftypes.Locations{
{
StartLine: 21,
EndLine: 21,
},
},
},
{
Name: "blas",
Version: "1.0",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 5,
EndLine: 5,
@@ -44,7 +54,7 @@ func TestParse(t *testing.T) {
{
Name: "bzip2",
Version: "1.0.8",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 19,
EndLine: 19,
@@ -54,16 +64,26 @@ func TestParse(t *testing.T) {
{
Name: "ca-certificates",
Version: "2024.2",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 7,
EndLine: 7,
},
},
},
{
Name: "django",
Version: "5.0.6",
Locations: ftypes.Locations{
{
StartLine: 22,
EndLine: 22,
},
},
},
{
Name: "ld_impl_linux-aarch64",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 8,
EndLine: 8,
@@ -72,7 +92,7 @@ func TestParse(t *testing.T) {
},
{
Name: "libblas",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 9,
EndLine: 9,
@@ -81,7 +101,7 @@ func TestParse(t *testing.T) {
},
{
Name: "libcblas",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 10,
EndLine: 10,
@@ -91,7 +111,7 @@ func TestParse(t *testing.T) {
{
Name: "libexpat",
Version: "2.6.2",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 11,
EndLine: 11,
@@ -101,7 +121,7 @@ func TestParse(t *testing.T) {
{
Name: "libffi",
Version: "3.4.2",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 12,
EndLine: 12,
@@ -110,7 +130,7 @@ func TestParse(t *testing.T) {
},
{
Name: "libgcc-ng",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 13,
EndLine: 13,
@@ -119,7 +139,7 @@ func TestParse(t *testing.T) {
},
{
Name: "libgfortran-ng",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 14,
EndLine: 14,
@@ -128,7 +148,7 @@ func TestParse(t *testing.T) {
},
{
Name: "libgfortran5",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 15,
EndLine: 15,
@@ -138,7 +158,7 @@ func TestParse(t *testing.T) {
{
Name: "libgomp",
Version: "13.2.0",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 16,
EndLine: 16,
@@ -147,7 +167,7 @@ func TestParse(t *testing.T) {
},
{
Name: "liblapack",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 17,
EndLine: 17,
@@ -157,7 +177,7 @@ func TestParse(t *testing.T) {
{
Name: "libnsl",
Version: "2.0.1",
Locations: types.Locations{
Locations: ftypes.Locations{
{
StartLine: 18,
EndLine: 18,
@@ -167,9 +187,24 @@ func TestParse(t *testing.T) {
},
},
{
name: "invalid_json",
name: "invalid yaml file",
input: "testdata/invalid.yaml",
wantErr: "unable to decode conda environment.yml file",
wantErr: "cannot unmarshal !!str `invalid` into environment.environment",
},
{
name: "`dependency` field uses unsupported type",
input: "testdata/wrong-deps-type.yaml",
wantErr: `unsupported dependency type "!!int" on line 5`,
},
{
name: "nested field uses unsupported type",
input: "testdata/wrong-nested-type.yaml",
wantErr: `unsupported dependency type "!!str" on line 5`,
},
{
name: "nested dependency uses unsupported type",
input: "testdata/wrong-nested-dep-type.yaml",
wantErr: `unsupported dependency type "!!map" on line 6`,
},
}
for _, tt := range tests {

View File

@@ -17,5 +17,8 @@ dependencies:
- liblapack=3.9.*=22_linuxaarch64_openblas
- libnsl=2.0.1=h31becfc_0
- bzip2=1.0.8=h998d150_5
- pip:
- asgiref==3.8.1
- django==5.0.6
prefix: /opt/conda/envs/test-env

View File

@@ -0,0 +1,7 @@
name: test-env
channels:
- defaults
dependencies:
- 1
prefix: /opt/conda/envs/test-env

View File

@@ -0,0 +1,9 @@
name: test-env
channels:
- defaults
dependencies:
- pip:
- wrongType:
- asgiref==3.8.1
prefix: /opt/conda/envs/test-env

View File

@@ -0,0 +1,7 @@
name: test-env
channels:
- defaults
dependencies:
- pip: asgiref==3.8.1
prefix: /opt/conda/envs/test-env

View File

@@ -3,9 +3,10 @@ package meta
import (
"encoding/json"
"github.com/samber/lo"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -17,14 +18,14 @@ type packageJSON struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
// Parse parses Anaconda (a.k.a. conda) environment metadata.
// e.g. <conda-root>/envs/<env>/conda-meta/<package>.json
// For details see https://conda.io/projects/conda/en/latest/user-guide/concepts/environments.html
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var data packageJSON
err := json.NewDecoder(r).Decode(&data)
if err != nil {
@@ -35,11 +36,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("unable to parse conda package")
}
return []types.Library{
return []ftypes.Package{
{
Name: data.Name,
Version: data.Version,
License: data.License, // can be empty
Name: data.Name,
Version: data.Version,
Licenses: lo.Ternary(data.License != "", []string{data.License}, nil),
},
}, nil, nil
}

View File

@@ -8,25 +8,36 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "_libgcc_mutex",
input: "testdata/_libgcc_mutex-0.1-main.json",
want: []types.Library{{Name: "_libgcc_mutex", Version: "0.1"}},
want: []ftypes.Package{
{
Name: "_libgcc_mutex",
Version: "0.1",
},
},
},
{
name: "libgomp",
input: "testdata/libgomp-11.2.0-h1234567_1.json",
want: []types.Library{{Name: "libgomp", Version: "11.2.0", License: "GPL-3.0-only WITH GCC-exception-3.1"}},
want: []ftypes.Package{
{
Name: "libgomp",
Version: "11.2.0",
Licenses: []string{"GPL-3.0-only WITH GCC-exception-3.1"},
},
},
},
{
name: "invalid_json",

View File

@@ -5,7 +5,6 @@ import (
"gopkg.in/yaml.v3"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -19,7 +18,7 @@ const (
// Parser is a parser for pubspec.lock
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
@@ -32,36 +31,36 @@ type Dep struct {
Version string `yaml:"version"`
}
func (p Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
l := &lock{}
if err := yaml.NewDecoder(r).Decode(&l); err != nil {
return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err)
}
var libs []types.Library
var pkgs []ftypes.Package
for name, dep := range l.Packages {
// We would like to exclude dev dependencies, but we cannot identify
// which indirect dependencies were introduced by dev dependencies
// as there are 3 dependency types, "direct main", "direct dev" and "transitive".
// It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies.
// We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev".
lib := types.Library{
pkg := ftypes.Package{
ID: dependency.ID(ftypes.Pub, name, dep.Version),
Name: name,
Version: dep.Version,
Relationship: p.relationship(dep.Dependency),
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
}
return libs, nil, nil
return pkgs, nil, nil
}
func (p Parser) relationship(dep string) types.Relationship {
func (p Parser) relationship(dep string) ftypes.Relationship {
switch dep {
case directMain, directDev:
return types.RelationshipDirect
return ftypes.RelationshipDirect
case transitiveDep:
return types.RelationshipIndirect
return ftypes.RelationshipIndirect
}
return types.RelationshipUnknown
return ftypes.RelationshipUnknown
}

View File

@@ -10,37 +10,37 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
wantErr assert.ErrorAssertionFunc
}{
{
name: "happy path",
inputFile: "testdata/happy.lock",
want: []types.Library{
want: []ftypes.Package{
{
ID: "crypto@3.0.2",
Name: "crypto",
Version: "3.0.2",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "flutter_test@0.0.0",
Name: "flutter_test",
Version: "0.0.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "uuid@3.0.6",
Name: "uuid",
Version: "3.0.6",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
},
wantErr: assert.NoError,
@@ -63,13 +63,13 @@ func TestParser_Parse(t *testing.T) {
require.NoError(t, err)
defer f.Close()
gotLibs, _, err := pub.NewParser().Parse(f)
gotPkgs, _, err := pub.NewParser().Parse(f)
if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) {
return
}
sort.Sort(types.Libraries(gotLibs))
assert.Equal(t, tt.want, gotLibs)
sort.Sort(ftypes.Packages(gotPkgs))
assert.Equal(t, tt.want, gotPkgs)
})
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/liamg/jfather"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -16,13 +16,13 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("dotnet"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var depsFile dotNetDependencies
input, err := io.ReadAll(r)
@@ -33,7 +33,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err)
}
var libraries []types.Library
var pkgs []ftypes.Package
for nameVer, lib := range depsFile.Libraries {
if !strings.EqualFold(lib.Type, "package") {
continue
@@ -46,10 +46,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
continue
}
libraries = append(libraries, types.Library{
pkgs = append(pkgs, ftypes.Package{
Name: split[0],
Version: split[1],
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: lib.StartLine,
EndLine: lib.EndLine,
@@ -58,7 +58,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
})
}
return libraries, nil, nil
return pkgs, nil, nil
}
type dotNetDependencies struct {

View File

@@ -4,25 +4,24 @@ import (
"os"
"path"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
vectors := []struct {
file string // Test input file
want []types.Library
want []ftypes.Package
wantErr string
}{
{
file: "testdata/ExampleApp1.deps.json",
want: []types.Library{
{Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []types.Location{{StartLine: 33, EndLine: 39}}},
want: []ftypes.Package{
{Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}},
},
},
{
@@ -47,21 +46,8 @@ func TestParse(t *testing.T) {
} else {
require.NoError(t, err)
sort.Slice(got, func(i, j int) bool {
ret := strings.Compare(got[i].Name, got[j].Name)
if ret == 0 {
return got[i].Version < got[j].Version
}
return ret < 0
})
sort.Slice(tt.want, func(i, j int) bool {
ret := strings.Compare(tt.want[i].Name, tt.want[j].Name)
if ret == 0 {
return tt.want[i].Version < tt.want[j].Version
}
return ret < 0
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(tt.want))
assert.Equal(t, tt.want, got)
}

View File

@@ -7,10 +7,10 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func Parse(r io.Reader) (lib types.Library, err error) {
func Parse(r io.Reader) (lib ftypes.Package, err error) {
// If wordpress file, open file and
// find line with content
@@ -68,10 +68,10 @@ func Parse(r io.Reader) (lib types.Library, err error) {
}
if err = scanner.Err(); err != nil || version == "" {
return types.Library{}, xerrors.New("version.php could not be parsed")
return ftypes.Package{}, xerrors.New("version.php could not be parsed")
}
return types.Library{
return ftypes.Package{
Name: "wordpress",
Version: version,
}, nil

View File

@@ -8,18 +8,18 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParseWordPress(t *testing.T) {
tests := []struct {
file string // Test input file
want types.Library
want ftypes.Package
wantErr string
}{
{
file: "testdata/version.php",
want: types.Library{
want: ftypes.Package{
Name: "wordpress",
Version: "4.9.4-alpha",
},

View File

@@ -11,7 +11,7 @@ import (
"golang.org/x/mod/semver"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -39,23 +39,36 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("gobinary"),
}
}
// Parse scans file to try to report the Go and module versions.
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
info, err := buildinfo.Read(r)
if err != nil {
return nil, nil, convertError(err)
}
// Ex: "go1.22.3 X:boringcrypto"
stdlibVersion := strings.TrimPrefix(info.GoVersion, "go")
stdlibVersion, _, _ = strings.Cut(stdlibVersion, " ")
ldflags := p.ldFlags(info.Settings)
libs := make([]types.Library, 0, len(info.Deps)+2)
libs = append(libs, []types.Library{
{
pkgs := make(ftypes.Packages, 0, len(info.Deps)+2)
pkgs = append(pkgs, ftypes.Package{
// Add the Go version used to build this binary.
Name: "stdlib",
Version: stdlibVersion,
Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages.
})
// There are times when gobinaries don't contain Main information.
// e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.)
if info.Main.Path != "" {
pkgs = append(pkgs, ftypes.Package{
// Add main module
Name: info.Main.Path,
// Only binaries installed with `go install` contain semver version of the main module.
@@ -64,15 +77,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// as a secondary source.
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)),
Relationship: types.RelationshipRoot,
},
{
// Add the Go version used to build this binary.
Name: "stdlib",
Version: strings.TrimPrefix(info.GoVersion, "go"),
Relationship: types.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages.
},
}...)
Relationship: ftypes.RelationshipRoot,
})
}
for _, dep := range info.Deps {
// binaries with old go version may incorrectly add module in Deps
@@ -87,14 +94,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
mod = dep.Replace
}
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
Name: mod.Path,
Version: p.checkVersion(mod.Path, mod.Version),
})
}
sort.Sort(types.Libraries(libs))
return libs, nil, nil
sort.Sort(pkgs)
return pkgs, nil, nil
}
// checkVersion detects `(devel)` versions, removes them and adds a debug message about it.
@@ -159,7 +166,7 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string {
func isValidXKey(key string) bool {
key = strings.ToLower(key)
// The check for a 'ver' prefix enables the parser to pick up Trivy's own version value that's set.
return strings.HasSuffix(key, "version") || strings.HasSuffix(key, "ver")
return strings.HasSuffix(key, ".version") || strings.HasSuffix(key, ".ver")
}
func isValidSemVer(ver string) bool {

View File

@@ -8,20 +8,20 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/golang/binary"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
wantLibs := []types.Library{
wantPkgs := []ftypes.Package{
{
Name: "github.com/aquasecurity/test",
Version: "",
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
},
{
Name: "stdlib",
Version: "1.15.2",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
Name: "github.com/aquasecurity/go-pep440-version",
@@ -40,37 +40,37 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "ELF",
inputFile: "testdata/test.elf",
want: wantLibs,
want: wantPkgs,
},
{
name: "PE",
inputFile: "testdata/test.exe",
want: wantLibs,
want: wantPkgs,
},
{
name: "Mach-O",
inputFile: "testdata/test.macho",
want: wantLibs,
want: wantPkgs,
},
{
name: "with replace directive",
inputFile: "testdata/replace.elf",
want: []types.Library{
want: []ftypes.Package{
{
Name: "github.com/ebati/trivy-mod-parse",
Version: "",
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
},
{
Name: "stdlib",
Version: "1.16.4",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
Name: "github.com/davecgh/go-spew",
@@ -85,32 +85,43 @@ func TestParse(t *testing.T) {
{
name: "with semver main module version",
inputFile: "testdata/semver-main-module-version.macho",
want: []types.Library{
want: []ftypes.Package{
{
Name: "go.etcd.io/bbolt",
Version: "v1.3.5",
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
},
{
Name: "stdlib",
Version: "1.20.6",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
},
},
{
name: "with -ldflags=\"-X main.version=v1.0.0\"",
inputFile: "testdata/main-version-via-ldflags.elf",
want: []types.Library{
want: []ftypes.Package{
{
Name: "github.com/aquasecurity/test",
Version: "v1.0.0",
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
},
{
Name: "stdlib",
Version: "1.22.1",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
},
},
{
name: "goexperiment",
inputFile: "testdata/goexperiment",
want: []ftypes.Package{
{
Name: "stdlib",
Version: "1.22.1",
Relationship: ftypes.RelationshipDirect,
},
},
},
@@ -227,6 +238,18 @@ func TestParser_ParseLDFlags(t *testing.T) {
},
want: "0.50.1",
},
{
name: "with version with extra prefix",
args: args{
name: "github.com/argoproj/argo-cd/v2",
flags: []string{
"-s",
"-w",
"-X='github.com/argoproj/argo-cd/v2/common.kubectlVersion=v0.26.11'",
},
},
want: "",
},
{
name: "with no flags",
args: args{
@@ -238,7 +261,7 @@ func TestParser_ParseLDFlags(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := binary.NewParser().(*binary.Parser)
p := binary.NewParser()
assert.Equal(t, tt.want, p.ParseLDFlags(tt.args.name, tt.args.flags))
})
}

Binary file not shown.

View File

@@ -12,7 +12,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -34,17 +33,17 @@ type Parser struct {
replace bool // 'replace' represents if the 'replace' directive should be taken into account.
}
func NewParser(replace bool) types.Parser {
func NewParser(replace bool) *Parser {
return &Parser{
replace: replace,
}
}
func (p *Parser) GetExternalRefs(path string) []types.ExternalRef {
func (p *Parser) GetExternalRefs(path string) []ftypes.ExternalRef {
if url := resolveVCSUrl(path); url != "" {
return []types.ExternalRef{
return []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: url,
},
}
@@ -67,8 +66,8 @@ func resolveVCSUrl(modulePath string) string {
}
// Parse parses a go.mod file
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
libs := make(map[string]types.Library)
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
pkgs := make(map[string]ftypes.Package)
goModData, err := io.ReadAll(r)
if err != nil {
@@ -88,12 +87,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// Main module
if m := modFileParsed.Module; m != nil {
ver := strings.TrimPrefix(m.Mod.Version, "v")
libs[m.Mod.Path] = types.Library{
pkgs[m.Mod.Path] = ftypes.Package{
ID: packageID(m.Mod.Path, ver),
Name: m.Mod.Path,
Version: ver,
ExternalReferences: p.GetExternalRefs(m.Mod.Path),
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
}
}
@@ -104,11 +103,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
continue
}
ver := strings.TrimPrefix(require.Mod.Version, "v")
libs[require.Mod.Path] = types.Library{
pkgs[require.Mod.Path] = ftypes.Package{
ID: packageID(require.Mod.Path, ver),
Name: require.Mod.Path,
Version: ver,
Relationship: lo.Ternary(require.Indirect, types.RelationshipIndirect, types.RelationshipDirect),
Relationship: lo.Ternary(require.Indirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect),
ExternalReferences: p.GetExternalRefs(require.Mod.Path),
}
}
@@ -116,8 +115,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// No need to evaluate the 'replace' directive for indirect dependencies
if p.replace {
for _, rep := range modFileParsed.Replace {
// Check if replaced path is actually in our libs.
old, ok := libs[rep.Old.Path]
// Check if replaced path is actually in our pkgs.
old, ok := pkgs[rep.Old.Path]
if !ok {
continue
}
@@ -130,16 +129,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// Only support replace directive with version on the right side.
// Directive without version is a local path.
if rep.New.Version == "" {
// Delete old lib, since it's a local path now.
delete(libs, rep.Old.Path)
// Delete old pkg, since it's a local path now.
delete(pkgs, rep.Old.Path)
continue
}
// Delete old lib, in case the path has changed.
delete(libs, rep.Old.Path)
// Delete old pkg, in case the path has changed.
delete(pkgs, rep.Old.Path)
// Add replaced library to library register.
libs[rep.New.Path] = types.Library{
// Add replaced package to package register.
pkgs[rep.New.Path] = ftypes.Package{
ID: packageID(rep.New.Path, rep.New.Version[1:]),
Name: rep.New.Path,
Version: rep.New.Version[1:],
@@ -149,7 +148,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
}
return maps.Values(libs), nil, nil
return maps.Values(pkgs), nil, nil
}
// Check if the Go version is less than 1.17

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
@@ -16,7 +16,7 @@ func TestParse(t *testing.T) {
name string
file string
replace bool
want []types.Library
want []ftypes.Package
}{
{
name: "normal",
@@ -88,12 +88,8 @@ func TestParse(t *testing.T) {
got, _, err := NewParser(tt.replace).Parse(f)
require.NoError(t, err)
sort.Slice(got, func(i, j int) bool {
return got[i].Name < got[j].Name
})
sort.Slice(tt.want, func(i, j int) bool {
return tt.want[i].Name < tt.want[j].Name
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(tt.want))
assert.Equal(t, tt.want, got)
})

View File

@@ -1,17 +1,17 @@
package mod
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// execute go mod tidy in normal folder
GoModNormal = []types.Library{
GoModNormal = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -20,10 +20,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -32,16 +32,16 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b",
Name: "gopkg.in/yaml.v3",
Version: "3.0.0-20210107192922-496545a6307b",
Relationship: types.RelationshipIndirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipIndirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/go-yaml/yaml",
},
},
@@ -49,14 +49,14 @@ var (
}
// execute go mod tidy in replaced folder
GoModReplaced = []types.Library{
GoModReplaced = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -65,10 +65,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20220406074731-71021a481237",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -77,19 +77,19 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
// execute go mod tidy in replaced folder
GoModUnreplaced = []types.Library{
GoModUnreplaced = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -98,10 +98,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211110174639-8257534ffed3",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -110,19 +110,19 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
// execute go mod tidy in replaced-with-version folder
GoModReplacedWithVersion = []types.Library{
GoModReplacedWithVersion = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -131,10 +131,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20220406074731-71021a481237",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -143,19 +143,19 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
// execute go mod tidy in replaced-with-version-mismatch folder
GoModReplacedWithVersionMismatch = []types.Library{
GoModReplacedWithVersionMismatch = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -164,10 +164,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -176,16 +176,16 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b",
Name: "gopkg.in/yaml.v3",
Version: "3.0.0-20210107192922-496545a6307b",
Relationship: types.RelationshipIndirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipIndirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/go-yaml/yaml",
},
},
@@ -193,14 +193,14 @@ var (
}
// execute go mod tidy in replaced-with-local-path folder
GoModReplacedWithLocalPath = []types.Library{
GoModReplacedWithLocalPath = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -209,10 +209,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -221,10 +221,10 @@ var (
ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b",
Name: "gopkg.in/yaml.v3",
Version: "3.0.0-20210107192922-496545a6307b",
Relationship: types.RelationshipIndirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipIndirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/go-yaml/yaml",
},
},
@@ -232,14 +232,14 @@ var (
}
// execute go mod tidy in replaced-with-local-path-and-version folder
GoModReplacedWithLocalPathAndVersion = []types.Library{
GoModReplacedWithLocalPathAndVersion = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -248,10 +248,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -260,10 +260,10 @@ var (
ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b",
Name: "gopkg.in/yaml.v3",
Version: "3.0.0-20210107192922-496545a6307b",
Relationship: types.RelationshipIndirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipIndirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/go-yaml/yaml",
},
},
@@ -271,14 +271,14 @@ var (
}
// execute go mod tidy in replaced-with-local-path-and-version-mismatch folder
GoModReplacedWithLocalPathAndVersionMismatch = []types.Library{
GoModReplacedWithLocalPathAndVersionMismatch = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -287,10 +287,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -299,16 +299,16 @@ var (
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
Name: "golang.org/x/xerrors",
Version: "0.0.0-20200804184101-5ec99f83aff1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b",
Name: "gopkg.in/yaml.v3",
Version: "3.0.0-20210107192922-496545a6307b",
Relationship: types.RelationshipIndirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipIndirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/go-yaml/yaml",
},
},
@@ -316,14 +316,14 @@ var (
}
// execute go mod tidy in go116 folder
GoMod116 = []types.Library{
GoMod116 = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -332,10 +332,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},
@@ -343,14 +343,14 @@ var (
}
// execute go mod tidy in no-go-version folder
GoModNoGoVersion = []types.Library{
GoModNoGoVersion = []ftypes.Package{
{
ID: "github.com/org/repo",
Name: "github.com/org/repo",
Relationship: types.RelationshipRoot,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipRoot,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/org/repo",
},
},
@@ -359,10 +359,10 @@ var (
ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff",
Name: "github.com/aquasecurity/go-dep-parser",
Version: "0.0.0-20211224170007-df43bca6b6ff",
Relationship: types.RelationshipDirect,
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipDirect,
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefVCS,
Type: ftypes.RefVCS,
URL: "https://github.com/aquasecurity/go-dep-parser",
},
},

View File

@@ -7,21 +7,20 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
// Parse parses a go.sum file
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
uniqueLibs := make(map[string]string)
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var pkgs []ftypes.Package
uniquePkgs := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
@@ -33,19 +32,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// go.sum records and sorts all non-major versions
// with the latest version as last entry
uniqueLibs[s[0]] = strings.TrimSuffix(strings.TrimPrefix(s[1], "v"), "/go.mod")
uniquePkgs[s[0]] = strings.TrimSuffix(strings.TrimPrefix(s[1], "v"), "/go.mod")
}
if err := scanner.Err(); err != nil {
return nil, nil, xerrors.Errorf("scan error: %w", err)
}
for k, v := range uniqueLibs {
libs = append(libs, types.Library{
for k, v := range uniquePkgs {
pkgs = append(pkgs, ftypes.Package{
ID: dependency.ID(ftypes.GoModule, k, v),
Name: k,
Version: v,
})
}
return libs, nil, nil
return pkgs, nil, nil
}

View File

@@ -9,13 +9,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
vectors := []struct {
file string
want []types.Library
want []ftypes.Package
}{
{
file: "testdata/gomod_normal.sum",
@@ -48,12 +48,8 @@ func TestParse(t *testing.T) {
got[i].ID = "" // Not compare IDs, tested in mod.TestModuleID()
}
sort.Slice(got, func(i, j int) bool {
return got[i].Name < got[j].Name
})
sort.Slice(v.want, func(i, j int) bool {
return v.want[i].Name < v.want[j].Name
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(v.want))
assert.Equal(t, v.want, got)
})

View File

@@ -1,6 +1,6 @@
package sum
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// docker run --name gomod --rm -it golang:1.15 bash
@@ -10,12 +10,12 @@ var (
// go mod init github.com/org/repo
// go get golang.org/x/xerrors
// go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}'
GoModNormal = []types.Library{
GoModNormal = []ftypes.Package{
{Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1"},
}
// https://github.com/uudashr/gopkgs/blob/616744904701ef01d868da4b66aad0e6856c361d/v2/go.sum
GoModEmptyLine = []types.Library{
GoModEmptyLine = []ftypes.Package{
{Name: "github.com/karrick/godirwalk", Version: "1.12.0"},
{Name: "github.com/pkg/errors", Version: "0.8.1"},
}
@@ -30,7 +30,7 @@ var (
// go get github.com/stretchr/testify
// go get github.com/BurntSushi/toml
// go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}'
GoModMany = []types.Library{
GoModMany = []ftypes.Package{
{Name: "github.com/BurntSushi/toml", Version: "0.3.1"},
{Name: "github.com/cpuguy83/go-md2man/v2", Version: "2.0.0-20190314233015-f79a8a8ca69d"},
{Name: "github.com/davecgh/go-spew", Version: "1.1.0"},
@@ -53,7 +53,7 @@ var (
// go mod init github.com/org/repo
// go get github.com/aquasecurity/trivy
// go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}'
GoModTrivy = []types.Library{
GoModTrivy = []ftypes.Package{
{Name: "cloud.google.com/go", Version: "0.65.0"},
{Name: "cloud.google.com/go/bigquery", Version: "1.8.0"},
{Name: "cloud.google.com/go/datastore", Version: "1.1.0"},

View File

@@ -6,19 +6,18 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var pkgs []ftypes.Package
scanner := bufio.NewScanner(r)
var lineNum int
for scanner.Scan() {
@@ -36,19 +35,19 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er
name := strings.Join(dep[:2], ":")
version := strings.Split(dep[2], "=")[0] // remove classPaths
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: dependency.ID(ftypes.Gradle, name, version),
Name: name,
Version: version,
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: lineNum,
EndLine: lineNum,
},
},
Relationship: types.RelationshipUnknown,
Relationship: ftypes.RelationshipUnknown,
})
}
return utils.UniqueLibraries(libs), nil, nil
return utils.UniquePackages(pkgs), nil, nil
}

View File

@@ -3,10 +3,9 @@ package lockfile
import (
"os"
"sort"
"strings"
"testing"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
)
@@ -14,17 +13,17 @@ func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
}{
{
name: "happy path",
inputFile: "testdata/happy.lockfile",
want: []types.Library{
want: []ftypes.Package{
{
ID: "cglib:cglib-nodep:2.1.2",
Name: "cglib:cglib-nodep",
Version: "2.1.2",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 4,
EndLine: 4,
@@ -35,7 +34,7 @@ func TestParser_Parse(t *testing.T) {
ID: "org.springframework:spring-asm:3.1.3.RELEASE",
Name: "org.springframework:spring-asm",
Version: "3.1.3.RELEASE",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 5,
@@ -46,7 +45,7 @@ func TestParser_Parse(t *testing.T) {
ID: "org.springframework:spring-beans:5.0.5.RELEASE",
Name: "org.springframework:spring-beans",
Version: "5.0.5.RELEASE",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 6,
EndLine: 6,
@@ -68,19 +67,9 @@ func TestParser_Parse(t *testing.T) {
f, err := os.Open(tt.inputFile)
assert.NoError(t, err)
libs, _, _ := parser.Parse(f)
sortLibs(libs)
assert.Equal(t, tt.want, libs)
pkgs, _, _ := parser.Parse(f)
sort.Sort(ftypes.Packages(pkgs))
assert.Equal(t, tt.want, pkgs)
})
}
}
func sortLibs(libs []types.Library) {
sort.Slice(libs, func(i, j int) bool {
ret := strings.Compare(libs[i].Name, libs[j].Name)
if ret == 0 {
return libs[i].Version < libs[j].Version
}
return ret < 0
})
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -18,14 +17,14 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("mix"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var pkgs []ftypes.Package
scanner := bufio.NewScanner(r)
var lineNumber int // It is used to save dependency location
for scanner.Scan() {
@@ -54,11 +53,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
continue
}
version := strings.Trim(ss[2], `"`)
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: dependency.ID(ftypes.Hex, name, version),
Name: name,
Version: version,
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: lineNumber,
EndLine: lineNumber,
@@ -67,5 +66,5 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
})
}
return utils.UniqueLibraries(libs), nil, nil
return utils.UniquePackages(pkgs), nil, nil
}

View File

@@ -3,10 +3,9 @@ package mix
import (
"os"
"sort"
"strings"
"testing"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
)
@@ -14,35 +13,55 @@ func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
}{
{
name: "happy path",
inputFile: "testdata/happy.mix.lock",
want: []types.Library{
want: []ftypes.Package{
{
ID: "bunt@0.2.0",
Name: "bunt",
Version: "0.2.0",
Locations: []types.Location{{StartLine: 2, EndLine: 2}},
ID: "bunt@0.2.0",
Name: "bunt",
Version: "0.2.0",
Locations: []ftypes.Location{
{
StartLine: 2,
EndLine: 2,
},
},
},
{
ID: "credo@1.6.6",
Name: "credo",
Version: "1.6.6",
Locations: []types.Location{{StartLine: 3, EndLine: 3}},
ID: "credo@1.6.6",
Name: "credo",
Version: "1.6.6",
Locations: []ftypes.Location{
{
StartLine: 3,
EndLine: 3,
},
},
},
{
ID: "file_system@0.2.10",
Name: "file_system",
Version: "0.2.10",
Locations: []types.Location{{StartLine: 4, EndLine: 4}},
ID: "file_system@0.2.10",
Name: "file_system",
Version: "0.2.10",
Locations: []ftypes.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
{
ID: "jason@1.3.0",
Name: "jason",
Version: "1.3.0",
Locations: []types.Location{{StartLine: 5, EndLine: 5}},
ID: "jason@1.3.0",
Name: "jason",
Version: "1.3.0",
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
},
},
@@ -59,19 +78,9 @@ func TestParser_Parse(t *testing.T) {
f, err := os.Open(tt.inputFile)
assert.NoError(t, err)
libs, _, _ := parser.Parse(f)
sortLibs(libs)
assert.Equal(t, tt.want, libs)
pkgs, _, _ := parser.Parse(f)
sort.Sort(ftypes.Packages(pkgs))
assert.Equal(t, tt.want, pkgs)
})
}
}
func sortLibs(libs []types.Library) {
sort.Slice(libs, func(i, j int) bool {
ret := strings.Compare(libs[i].Name, libs[j].Name)
if ret == 0 {
return libs[i].Version < libs[j].Version
}
return ret < 0
})
}

View File

@@ -17,7 +17,7 @@ import (
"github.com/samber/lo"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -61,7 +61,7 @@ func WithSize(size int64) Option {
}
}
func NewParser(c Client, opts ...Option) types.Parser {
func NewParser(c Client, opts ...Option) *Parser {
p := &Parser{
logger: log.WithPrefix("jar"),
client: c,
@@ -74,15 +74,15 @@ func NewParser(c Client, opts ...Option) types.Parser {
return p
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
libs, deps, err := p.parseArtifact(p.rootFilePath, p.size, r)
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
pkgs, deps, err := p.parseArtifact(p.rootFilePath, p.size, r)
if err != nil {
return nil, nil, xerrors.Errorf("unable to parse %s: %w", p.rootFilePath, err)
}
return removeLibraryDuplicates(libs), deps, nil
return removePackageDuplicates(pkgs), deps, nil
}
func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
p.logger.Debug("Parsing Java artifacts...", log.String("file", filePath))
// Try to extract artifactId and version from the file name
@@ -90,14 +90,14 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
fileName := filepath.Base(filePath)
fileProps := parseFileName(filePath)
libs, m, foundPomProps, err := p.traverseZip(filePath, size, r, fileProps)
pkgs, m, foundPomProps, err := p.traverseZip(filePath, size, r, fileProps)
if err != nil {
return nil, nil, xerrors.Errorf("zip error: %w", err)
}
// If pom.properties is found, it should be preferred than MANIFEST.MF.
if foundPomProps {
return libs, nil, nil
return pkgs, nil, nil
}
manifestProps := m.properties(filePath)
@@ -105,9 +105,9 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
// In offline mode, we will not check if the artifact information is correct.
if !manifestProps.Valid() {
p.logger.Debug("Unable to identify POM in offline mode", log.String("file", fileName))
return libs, nil, nil
return pkgs, nil, nil
}
return append(libs, manifestProps.Library()), nil, nil
return append(pkgs, manifestProps.Package()), nil, nil
}
if manifestProps.Valid() {
@@ -115,14 +115,14 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
// We have to make sure that the artifact exists actually.
if ok, _ := p.client.Exists(manifestProps.GroupID, manifestProps.ArtifactID); ok {
// If groupId and artifactId are valid, they will be returned.
return append(libs, manifestProps.Library()), nil, nil
return append(pkgs, manifestProps.Package()), nil, nil
}
}
// If groupId and artifactId are not found, call Maven Central's search API with SHA-1 digest.
props, err := p.searchBySHA1(r, filePath)
if err == nil {
return append(libs, props.Library()), nil, nil
return append(pkgs, props.Package()), nil, nil
} else if !errors.Is(err, ArtifactNotFoundErr) {
return nil, nil, xerrors.Errorf("failed to search by SHA1: %w", err)
}
@@ -131,7 +131,7 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
// Return when artifactId or version from the file name are empty
if fileProps.ArtifactID == "" || fileProps.Version == "" {
return libs, nil, nil
return pkgs, nil, nil
}
// Try to search groupId by artifactId via sonatype API
@@ -140,17 +140,17 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
if err == nil {
p.logger.Debug("POM was determined in a heuristic way", log.String("file", fileName),
log.String("artifact", fileProps.String()))
libs = append(libs, fileProps.Library())
pkgs = append(pkgs, fileProps.Package())
} else if !errors.Is(err, ArtifactNotFoundErr) {
return nil, nil, xerrors.Errorf("failed to search by artifact id: %w", err)
}
return libs, nil, nil
return pkgs, nil, nil
}
func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fileProps Properties) (
[]types.Library, manifest, bool, error) {
var libs []types.Library
[]ftypes.Package, manifest, bool, error) {
var pkgs []ftypes.Package
var m manifest
var foundPomProps bool
@@ -166,9 +166,9 @@ func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fi
if err != nil {
return nil, manifest{}, false, xerrors.Errorf("failed to parse %s: %w", fileInJar.Name, err)
}
// Validation of props to avoid getting libs with empty Name/Version
// Validation of props to avoid getting packages with empty Name/Version
if props.Valid() {
libs = append(libs, props.Library())
pkgs = append(pkgs, props.Package())
// Check if the pom.properties is for the original JAR/WAR/EAR
if fileProps.ArtifactID == props.ArtifactID && fileProps.Version == props.Version {
@@ -181,18 +181,18 @@ func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fi
return nil, manifest{}, false, xerrors.Errorf("failed to parse MANIFEST.MF: %w", err)
}
case isArtifact(fileInJar.Name):
innerLibs, _, err := p.parseInnerJar(fileInJar, filePath) // TODO process inner deps
innerPkgs, _, err := p.parseInnerJar(fileInJar, filePath) // TODO process inner deps
if err != nil {
p.logger.Debug("Failed to parse", log.String("file", fileInJar.Name), log.Err(err))
continue
}
libs = append(libs, innerLibs...)
pkgs = append(pkgs, innerPkgs...)
}
}
return libs, m, foundPomProps, nil
return pkgs, m, foundPomProps, nil
}
func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]types.Library, []types.Dependency, error) {
func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]ftypes.Package, []ftypes.Dependency, error) {
fr, err := zf.Open()
if err != nil {
return nil, nil, xerrors.Errorf("unable to open %s: %w", zf.Name, err)
@@ -221,12 +221,12 @@ func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]types.Library,
}
// Parse jar/war/ear recursively
innerLibs, innerDeps, err := p.parseArtifact(fullPath, int64(zf.UncompressedSize64), f)
innerPkgs, innerDeps, err := p.parseArtifact(fullPath, int64(zf.UncompressedSize64), f)
if err != nil {
return nil, nil, xerrors.Errorf("failed to parse %s: %w", zf.Name, err)
}
return innerLibs, innerDeps, nil
return innerPkgs, innerDeps, nil
}
func (p *Parser) searchBySHA1(r io.ReadSeeker, filePath string) (Properties, error) {
@@ -438,8 +438,8 @@ func (m manifest) determineVersion() (string, error) {
return strings.TrimSpace(version), nil
}
func removeLibraryDuplicates(libs []types.Library) []types.Library {
return lo.UniqBy(libs, func(lib types.Library) string {
return fmt.Sprintf("%s::%s::%s", lib.Name, lib.Version, lib.FilePath)
func removePackageDuplicates(pkgs []ftypes.Package) []ftypes.Package {
return lo.UniqBy(pkgs, func(pkg ftypes.Package) string {
return fmt.Sprintf("%s::%s::%s", pkg.Name, pkg.Version, pkg.FilePath)
})
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var (
@@ -23,7 +23,7 @@ var (
// mvn dependency:list
// mvn dependency:tree -Dscope=compile -Dscope=runtime | awk '/:tree/,/BUILD SUCCESS/' | awk 'NR > 1 { print }' | head -n -2 | awk '{print $NF}' | awk -F":" '{printf("{\""$1":"$2"\", \""$4 "\", \"\"},\n")}'
// paths filled in manually
wantMaven = []types.Library{
wantMaven = []ftypes.Package{
{
Name: "com.example:web-app",
Version: "1.0-SNAPSHOT",
@@ -70,7 +70,7 @@ var (
// docker run --rm --name test -it test bash
// gradle app:dependencies --configuration implementation | grep "[+\]---" | cut -d" " -f2 | awk -F":" '{printf("{\""$1":"$2"\", \""$3"\", \"\"},\n")}'
// paths filled in manually
wantGradle = []types.Library{
wantGradle = []ftypes.Package{
{
Name: "commons-dbcp:commons-dbcp",
Version: "1.4",
@@ -94,7 +94,7 @@ var (
}
// manually created
wantSHA1 = []types.Library{
wantSHA1 = []ftypes.Package{
{
Name: "org.springframework:spring-core",
Version: "5.3.3",
@@ -103,7 +103,7 @@ var (
}
// offline
wantOffline = []types.Library{
wantOffline = []ftypes.Package{
{
Name: "org.springframework:Spring Framework",
Version: "2.5.6.SEC03",
@@ -112,7 +112,7 @@ var (
}
// manually created
wantHeuristic = []types.Library{
wantHeuristic = []ftypes.Package{
{
Name: "com.example:heuristic",
Version: "1.0.0-SNAPSHOT",
@@ -121,7 +121,7 @@ var (
}
// manually created
wantFatjar = []types.Library{
wantFatjar = []ftypes.Package{
{
Name: "com.google.guava:failureaccess",
Version: "1.0.1",
@@ -150,7 +150,7 @@ var (
}
// manually created
wantNestedJar = []types.Library{
wantNestedJar = []ftypes.Package{
{
Name: "test:nested",
Version: "0.0.1",
@@ -169,7 +169,7 @@ var (
}
// manually created
wantDuplicatesJar = []types.Library{
wantDuplicatesJar = []ftypes.Package{
{
Name: "io.quarkus.gizmo:gizmo",
Version: "1.1.1.Final",
@@ -211,7 +211,7 @@ func TestParse(t *testing.T) {
name string
file string // Test input file
offline bool
want []types.Library
want []ftypes.Package
}{
{
name: "maven",
@@ -319,12 +319,8 @@ func TestParse(t *testing.T) {
got, _, err := p.Parse(f)
require.NoError(t, err)
sort.Slice(got, func(i, j int) bool {
return got[i].Name < got[j].Name
})
sort.Slice(v.want, func(i, j int) bool {
return v.want[i].Name < v.want[j].Name
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(v.want))
assert.Equal(t, v.want, got)
})

View File

@@ -5,7 +5,7 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var ArtifactNotFoundErr = xerrors.New("no artifact found")
@@ -17,8 +17,8 @@ type Properties struct {
FilePath string // path to file containing these props
}
func (p Properties) Library() types.Library {
return types.Library{
func (p Properties) Package() ftypes.Package {
return ftypes.Package{
Name: fmt.Sprintf("%s:%s", p.GroupID, p.ArtifactID),
Version: p.Version,
FilePath: p.FilePath,

View File

@@ -9,7 +9,7 @@ import (
"github.com/samber/lo"
"golang.org/x/exp/slices"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
)
@@ -26,9 +26,9 @@ type artifact struct {
Exclusions map[string]struct{}
Module bool
Relationship types.Relationship
Relationship ftypes.Relationship
Locations types.Locations
Locations ftypes.Locations
}
func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact {
@@ -37,7 +37,7 @@ func newArtifact(groupID, artifactID, version string, licenses []string, props m
ArtifactID: evaluateVariable(artifactID, props, nil),
Version: newVersion(evaluateVariable(version, props, nil)),
Licenses: licenses,
Relationship: types.RelationshipIndirect, // default
Relationship: ftypes.RelationshipIndirect, // default
}
}
@@ -49,10 +49,6 @@ func (a artifact) Equal(o artifact) bool {
return a.GroupID == o.GroupID || a.ArtifactID == o.ArtifactID || a.Version.String() == o.Version.String()
}
func (a artifact) JoinLicenses() string {
return strings.Join(a.Licenses, ", ")
}
func (a artifact) ToPOMLicenses() pomLicenses {
return pomLicenses{
License: lo.Map(a.Licenses, func(lic string, _ int) pomLicense {

View File

@@ -19,7 +19,6 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -49,7 +48,7 @@ func WithReleaseRemoteRepos(repos []string) option {
}
}
type parser struct {
type Parser struct {
logger *log.Logger
rootPath string
cache pomCache
@@ -60,7 +59,7 @@ type parser struct {
servers []Server
}
func NewParser(filePath string, opts ...option) types.Parser {
func NewParser(filePath string, opts ...option) *Parser {
o := &options{
offline: false,
releaseRemoteRepos: []string{centralURL}, // Maven doesn't use central repository for snapshot dependencies
@@ -77,7 +76,7 @@ func NewParser(filePath string, opts ...option) types.Parser {
localRepository = filepath.Join(homeDir, ".m2", "repository")
}
return &parser{
return &Parser{
logger: log.WithPrefix("pom"),
rootPath: filepath.Clean(filePath),
cache: newPOMCache(),
@@ -89,7 +88,7 @@ func NewParser(filePath string, opts ...option) types.Parser {
}
}
func (p *parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
content, err := parsePom(r)
if err != nil {
return nil, nil, xerrors.Errorf("failed to parse POM: %w", err)
@@ -112,18 +111,18 @@ func (p *parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return p.parseRoot(root.artifact(), make(map[string]struct{}))
}
func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]types.Library, []types.Dependency, error) {
func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ftypes.Package, []ftypes.Dependency, error) {
// Prepare a queue for dependencies
queue := newArtifactQueue()
// Enqueue root POM
root.Relationship = types.RelationshipRoot
root.Relationship = ftypes.RelationshipRoot
root.Module = false
queue.enqueue(root)
var (
libs []types.Library
deps []types.Dependency
pkgs ftypes.Packages
deps ftypes.Dependencies
rootDepManagement []pomDependency
uniqArtifacts = make(map[string]artifact)
uniqDeps = make(map[string][]string)
@@ -141,12 +140,12 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty
}
uniqModules[art.String()] = struct{}{}
moduleLibs, moduleDeps, err := p.parseRoot(art, uniqModules)
modulePkgs, moduleDeps, err := p.parseRoot(art, uniqModules)
if err != nil {
return nil, nil, err
}
libs = append(libs, moduleLibs...)
pkgs = append(pkgs, modulePkgs...)
if moduleDeps != nil {
deps = append(deps, moduleDeps...)
}
@@ -160,7 +159,7 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty
}
// mark artifact as Direct, if saved artifact is Direct
// take a look `hard requirement for the specified version` test
if uniqueArt.Relationship == types.RelationshipRoot || uniqueArt.Relationship == types.RelationshipDirect {
if uniqueArt.Relationship == ftypes.RelationshipRoot || uniqueArt.Relationship == ftypes.RelationshipDirect {
art.Relationship = uniqueArt.Relationship
}
// We don't need to overwrite dependency location for hard links
@@ -174,13 +173,13 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty
return nil, nil, xerrors.Errorf("resolve error (%s): %w", art, err)
}
if art.Relationship == types.RelationshipRoot {
if art.Relationship == ftypes.RelationshipRoot {
// Managed dependencies in the root POM affect transitive dependencies
rootDepManagement = p.resolveDepManagement(result.properties, result.dependencyManagement)
// mark its dependencies as "direct"
result.dependencies = lo.Map(result.dependencies, func(dep artifact, _ int) artifact {
dep.Relationship = types.RelationshipDirect
dep.Relationship = ftypes.RelationshipDirect
return dep
})
}
@@ -219,37 +218,37 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty
}
}
// Convert to []types.Library and []types.Dependency
// Convert to []ftypes.Package and []ftypes.Dependency
for name, art := range uniqArtifacts {
lib := types.Library{
pkg := ftypes.Package{
ID: packageID(name, art.Version.String()),
Name: name,
Version: art.Version.String(),
License: art.JoinLicenses(),
Licenses: art.Licenses,
Relationship: art.Relationship,
Locations: art.Locations,
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
// Convert dependency names into dependency IDs
dependsOn := lo.FilterMap(uniqDeps[lib.ID], func(dependOnName string, _ int) (string, bool) {
dependsOn := lo.FilterMap(uniqDeps[pkg.ID], func(dependOnName string, _ int) (string, bool) {
ver := depVersion(dependOnName, uniqArtifacts)
return packageID(dependOnName, ver), ver != ""
})
sort.Strings(dependsOn)
if len(dependsOn) > 0 {
deps = append(deps, types.Dependency{
ID: lib.ID,
deps = append(deps, ftypes.Dependency{
ID: pkg.ID,
DependsOn: dependsOn,
})
}
}
sort.Sort(types.Libraries(libs))
sort.Sort(types.Dependencies(deps))
sort.Sort(pkgs)
sort.Sort(deps)
return libs, deps, nil
return pkgs, deps, nil
}
// depVersion finds dependency in uniqArtifacts and return its version
@@ -260,7 +259,7 @@ func depVersion(depName string, uniqArtifacts map[string]artifact) string {
return ""
}
func (p *parser) parseModule(currentPath, relativePath string) (artifact, error) {
func (p *Parser) parseModule(currentPath, relativePath string) (artifact, error) {
// modulePath: "root/" + "module/" => "root/module"
module, err := p.openRelativePom(currentPath, relativePath)
if err != nil {
@@ -280,7 +279,7 @@ func (p *parser) parseModule(currentPath, relativePath string) (artifact, error)
return moduleArtifact, nil
}
func (p *parser) resolve(art artifact, rootDepManagement []pomDependency) (analysisResult, error) {
func (p *Parser) resolve(art artifact, rootDepManagement []pomDependency) (analysisResult, error) {
// If the artifact is found in cache, it is returned.
if result := p.cache.get(art); result != nil {
return *result, nil
@@ -319,7 +318,7 @@ type analysisOptions struct {
lineNumber bool // Save line numbers
}
func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) {
func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) {
if pom == nil || pom.content == nil {
return analysisResult{}, nil
}
@@ -362,7 +361,7 @@ func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error)
}, nil
}
func (p *parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency {
func (p *Parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency {
uniq := make(map[string]struct{})
var depManagement []pomDependency
// The preceding argument takes precedence.
@@ -378,7 +377,7 @@ func (p *parser) mergeDependencyManagements(depManagements ...[]pomDependency) [
return depManagement
}
func (p *parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency,
func (p *Parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency,
opts analysisOptions) []artifact {
// Imported POMs often have no dependencies, so dependencyManagement resolution can be skipped.
if len(deps) == 0 {
@@ -403,7 +402,7 @@ func (p *parser) parseDependencies(deps []pomDependency, props map[string]string
return dependencies
}
func (p *parser) resolveDepManagement(props map[string]string, depManagement []pomDependency) []pomDependency {
func (p *Parser) resolveDepManagement(props map[string]string, depManagement []pomDependency) []pomDependency {
var newDepManagement, imports []pomDependency
for _, dep := range depManagement {
// cf. https://howtodoinjava.com/maven/maven-dependency-scopes/#import
@@ -437,7 +436,7 @@ func (p *parser) resolveDepManagement(props map[string]string, depManagement []p
return newDepManagement
}
func (p *parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact {
func (p *Parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact {
var deps []artifact
unique := make(map[string]struct{})
@@ -471,7 +470,7 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool {
return false
}
func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) {
func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) {
// Pass nil properties so that variables in <parent> are not evaluated.
target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil)
// if version is property (e.g. ${revision}) - we still need to parse this pom
@@ -503,7 +502,7 @@ func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResu
return result, nil
}
func (p *parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) {
func (p *Parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) {
var errs error
// Try relativePath
@@ -536,7 +535,7 @@ func (p *parser) retrieveParent(currentPath, relativePath string, target artifac
return nil, errs
}
func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) {
func (p *Parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) {
pom, err := p.openRelativePom(currentPath, relativePath)
if err != nil {
return nil, err
@@ -563,7 +562,7 @@ func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativeP
return pom, nil
}
func (p *parser) openRelativePom(currentPath, relativePath string) (*pom, error) {
func (p *Parser) openRelativePom(currentPath, relativePath string) (*pom, error) {
// e.g. child/pom.xml => child/
dir := filepath.Dir(currentPath)
@@ -585,7 +584,7 @@ func (p *parser) openRelativePom(currentPath, relativePath string) (*pom, error)
return pom, nil
}
func (p *parser) openPom(filePath string) (*pom, error) {
func (p *Parser) openPom(filePath string) (*pom, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, xerrors.Errorf("file open error (%s): %w", filePath, err)
@@ -601,7 +600,7 @@ func (p *parser) openPom(filePath string) (*pom, error) {
content: content,
}, nil
}
func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error) {
func (p *Parser) tryRepository(groupID, artifactID, version string) (*pom, error) {
if version == "" {
return nil, xerrors.Errorf("Version missing for %s:%s", groupID, artifactID)
}
@@ -627,14 +626,14 @@ func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error
return nil, xerrors.Errorf("%s:%s:%s was not found in local/remote repositories", groupID, artifactID, version)
}
func (p *parser) loadPOMFromLocalRepository(paths []string) (*pom, error) {
func (p *Parser) loadPOMFromLocalRepository(paths []string) (*pom, error) {
paths = append([]string{p.localRepository}, paths...)
localPath := filepath.Join(paths...)
return p.openPom(localPath)
}
func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) {
func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) {
// Do not try fetching pom.xml from remote repositories in offline mode
if p.offline {
p.logger.Debug("Fetching the remote pom.xml is skipped")
@@ -660,7 +659,7 @@ func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (
return nil, xerrors.Errorf("the POM was not found in remote remoteRepositories")
}
func (p *parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) {
func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) {
repoURL, err := url.Parse(repo)
if err != nil {
p.logger.Error("URL parse error", log.String("repo", repo))

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,9 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/x/slices"
)
type pom struct {
@@ -110,9 +111,9 @@ func (p pom) artifact() artifact {
}
func (p pom) licenses() []string {
return lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) {
return slices.ZeroToNil(lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) {
return lic.Name, lic.Name != ""
})
}))
}
func (p pom) repositories(servers []Server) ([]string, []string) {
@@ -286,9 +287,9 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact {
exclusions[fmt.Sprintf("%s:%s", e.GroupID, e.ArtifactID)] = struct{}{}
}
var locations types.Locations
var locations ftypes.Locations
if opts.lineNumber {
locations = types.Locations{
locations = ftypes.Locations{
{
StartLine: d.StartLine,
EndLine: d.EndLine,
@@ -302,7 +303,7 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact {
Version: newVersion(d.Version),
Exclusions: exclusions,
Locations: locations,
Relationship: types.RelationshipIndirect, // default
Relationship: ftypes.RelationshipIndirect, // default
}
}

View File

@@ -8,7 +8,7 @@ import (
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -27,11 +27,11 @@ type primitiveDependency struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var oldDeps map[string][]primitiveDependency
var primMan primitiveManifest
var manMetadata toml.MetaData
@@ -68,19 +68,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
pkgParser := naivePkgParser{r: r}
lineNumIdx := pkgParser.parse()
var libs []types.Library
var deps []types.Dependency
var pkgs ftypes.Packages
var deps ftypes.Dependencies
for name, manifestDeps := range man.Dependencies {
for _, manifestDep := range manifestDeps {
version := depVersion(manifestDep.Version, man.JuliaVersion)
pkgID := manifestDep.UUID
lib := types.Library{
pkg := ftypes.Package{
ID: pkgID,
Name: name,
Version: version,
}
if pos, ok := lineNumIdx[manifestDep.UUID]; ok {
lib.Locations = []types.Location{
pkg.Locations = []ftypes.Location{
{
StartLine: pos.start,
EndLine: pos.end,
@@ -88,19 +88,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
if len(manifestDep.DependsOn) > 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: manifestDep.DependsOn,
})
}
}
}
sort.Sort(types.Libraries(libs))
sort.Sort(types.Dependencies(deps))
return libs, deps, nil
sort.Sort(pkgs)
sort.Sort(deps)
return pkgs, deps, nil
}
// Returns the effective version of the `dep`.

View File

@@ -8,26 +8,26 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
file string // Test input file
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "Manifest v1.6",
file: "testdata/primary/Manifest_v1.6.toml",
want: juliaV1_6Libs,
want: juliaV1_6Pkgs,
wantDeps: juliaV1_6Deps,
},
{
name: "Manifest v1.8",
file: "testdata/primary/Manifest_v1.8.toml",
want: juliaV1_8Libs,
want: juliaV1_8Pkgs,
wantDeps: juliaV1_8Deps,
},
{
@@ -45,13 +45,13 @@ func TestParse(t *testing.T) {
{
name: "dep extensions v1.9",
file: "testdata/dep_ext_v1.9/Manifest.toml",
want: juliaV1_9DepExtLibs,
want: juliaV1_9DepExtPkgs,
wantDeps: nil,
},
{
name: "shadowed dep v1.9",
file: "testdata/shadowed_dep_v1.9/Manifest.toml",
want: juliaV1_9ShadowedDepLibs,
want: juliaV1_9ShadowedDepPkgs,
wantDeps: juliaV1_9ShadowedDepDeps,
},
}
@@ -61,13 +61,13 @@ func TestParse(t *testing.T) {
f, err := os.Open(tt.file)
require.NoError(t, err)
gotLibs, gotDeps, err := NewParser().Parse(f)
gotPkgs, gotDeps, err := NewParser().Parse(f)
require.NoError(t, err)
sort.Sort(types.Libraries(tt.want))
assert.Equal(t, tt.want, gotLibs)
sort.Sort(ftypes.Packages(tt.want))
assert.Equal(t, tt.want, gotPkgs)
if tt.wantDeps != nil {
sort.Sort(types.Dependencies(tt.wantDeps))
sort.Sort(ftypes.Dependencies(tt.wantDeps))
assert.Equal(t, tt.wantDeps, gotDeps)
}
})

View File

@@ -1,18 +1,18 @@
package julia
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
juliaV1_6Libs = []types.Library{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []types.Location{{StartLine: 3, EndLine: 5}}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 7, EndLine: 11}}},
{ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []types.Location{{StartLine: 13, EndLine: 14}}},
{ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []types.Location{{StartLine: 16, EndLine: 20}}},
{ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []types.Location{{StartLine: 22, EndLine: 24}}},
{ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []types.Location{{StartLine: 26, EndLine: 27}}},
juliaV1_6Pkgs = []ftypes.Package{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []ftypes.Location{{StartLine: 3, EndLine: 5}}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []ftypes.Location{{StartLine: 7, EndLine: 11}}},
{ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []ftypes.Location{{StartLine: 13, EndLine: 14}}},
{ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []ftypes.Location{{StartLine: 16, EndLine: 20}}},
{ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []ftypes.Location{{StartLine: 22, EndLine: 24}}},
{ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []ftypes.Location{{StartLine: 26, EndLine: 27}}},
}
juliaV1_6Deps = []types.Dependency{
juliaV1_6Deps = []ftypes.Dependency{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{
"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5",
@@ -24,23 +24,23 @@ var (
{ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}},
}
juliaV1_8Libs = []types.Library{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []types.Location{{StartLine: 7, EndLine: 9}}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 11, EndLine: 15}}},
{ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []types.Location{{StartLine: 17, EndLine: 18}}},
{ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []types.Location{{StartLine: 20, EndLine: 24}}},
{ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []types.Location{{StartLine: 26, EndLine: 30}}},
{ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []types.Location{{StartLine: 32, EndLine: 36}}},
{ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []types.Location{{StartLine: 38, EndLine: 40}}},
{ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []types.Location{{StartLine: 42, EndLine: 44}}},
{ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []types.Location{{StartLine: 46, EndLine: 48}}},
{ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []types.Location{{StartLine: 50, EndLine: 51}}},
{ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []types.Location{{StartLine: 53, EndLine: 56}}},
{ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []types.Location{{StartLine: 58, EndLine: 60}}},
{ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []types.Location{{StartLine: 62, EndLine: 63}}},
juliaV1_8Pkgs = []ftypes.Package{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 7, EndLine: 9}}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []ftypes.Location{{StartLine: 11, EndLine: 15}}},
{ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 17, EndLine: 18}}},
{ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []ftypes.Location{{StartLine: 20, EndLine: 24}}},
{ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 26, EndLine: 30}}},
{ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 32, EndLine: 36}}},
{ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 38, EndLine: 40}}},
{ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 42, EndLine: 44}}},
{ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []ftypes.Location{{StartLine: 46, EndLine: 48}}},
{ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 50, EndLine: 51}}},
{ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []ftypes.Location{{StartLine: 53, EndLine: 56}}},
{ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 58, EndLine: 60}}},
{ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 62, EndLine: 63}}},
}
juliaV1_8Deps = []types.Dependency{
juliaV1_8Deps = []ftypes.Dependency{
{ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}},
{ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{
"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5",
@@ -61,17 +61,17 @@ var (
{ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}},
}
juliaV1_9DepExtLibs = []types.Library{
{ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []types.Location{{StartLine: 7, EndLine: 10}}},
juliaV1_9DepExtPkgs = []ftypes.Package{
{ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []ftypes.Location{{StartLine: 7, EndLine: 10}}},
}
juliaV1_9ShadowedDepLibs = []types.Library{
{ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []types.Location{{StartLine: 7, EndLine: 8}}},
{ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 13, EndLine: 14}}},
{ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 15, EndLine: 16}}},
juliaV1_9ShadowedDepPkgs = []ftypes.Package{
{ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 7, EndLine: 8}}},
{ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 13, EndLine: 14}}},
{ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 15, EndLine: 16}}},
}
juliaV1_9ShadowedDepDeps = []types.Dependency{
juliaV1_9ShadowedDepDeps = []ftypes.Dependency{
{ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}},
}
)

View File

@@ -15,7 +15,6 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -56,13 +55,13 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("npm"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile LockFile
input, err := io.ReadAll(r)
if err != nil {
@@ -72,20 +71,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("decode error: %w", err)
}
var libs []types.Library
var deps []types.Dependency
var pkgs []ftypes.Package
var deps []ftypes.Dependency
if lockFile.LockfileVersion == 1 {
libs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string))
pkgs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string))
} else {
libs, deps = p.parseV2(lockFile.Packages)
pkgs, deps = p.parseV2(lockFile.Packages)
}
return utils.UniqueLibraries(libs), uniqueDeps(deps), nil
return utils.UniquePackages(pkgs), uniqueDeps(deps), nil
}
func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.Dependency) {
libs := make(map[string]types.Library, len(packages)-1)
var deps []types.Dependency
func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftypes.Dependency) {
pkgs := make(map[string]ftypes.Package, len(packages)-1)
var deps []ftypes.Dependency
// Resolve links first
// https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages
@@ -116,51 +115,51 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.
}
pkgID := packageID(pkgName, pkg.Version)
location := types.Location{
location := ftypes.Location{
StartLine: pkg.StartLine,
EndLine: pkg.EndLine,
}
var ref types.ExternalRef
var ref ftypes.ExternalRef
if pkg.Resolved != "" {
ref = types.ExternalRef{
Type: types.RefOther,
ref = ftypes.ExternalRef{
Type: ftypes.RefOther,
URL: pkg.Resolved,
}
}
pkgIndirect := isIndirectLib(pkgPath, directDeps)
pkgIndirect := isIndirectPkg(pkgPath, directDeps)
// There are cases when similar libraries use same dependencies
// There are cases when similar packages use same dependencies
// we need to add location for each these dependencies
if savedLib, ok := libs[pkgID]; ok {
savedLib.Dev = savedLib.Dev && pkg.Dev
if savedLib.Relationship == types.RelationshipIndirect && !pkgIndirect {
savedLib.Relationship = types.RelationshipDirect
if savedPkg, ok := pkgs[pkgID]; ok {
savedPkg.Dev = savedPkg.Dev && pkg.Dev
if savedPkg.Relationship == ftypes.RelationshipIndirect && !pkgIndirect {
savedPkg.Relationship = ftypes.RelationshipDirect
}
if ref.URL != "" && !slices.Contains(savedLib.ExternalReferences, ref) {
savedLib.ExternalReferences = append(savedLib.ExternalReferences, ref)
sortExternalReferences(savedLib.ExternalReferences)
if ref.URL != "" && !slices.Contains(savedPkg.ExternalReferences, ref) {
savedPkg.ExternalReferences = append(savedPkg.ExternalReferences, ref)
sortExternalReferences(savedPkg.ExternalReferences)
}
savedLib.Locations = append(savedLib.Locations, location)
sort.Sort(savedLib.Locations)
savedPkg.Locations = append(savedPkg.Locations, location)
sort.Sort(savedPkg.Locations)
libs[pkgID] = savedLib
pkgs[pkgID] = savedPkg
continue
}
lib := types.Library{
newPkg := ftypes.Package{
ID: pkgID,
Name: pkgName,
Version: pkg.Version,
Relationship: lo.Ternary(pkgIndirect, types.RelationshipIndirect, types.RelationshipDirect),
Relationship: lo.Ternary(pkgIndirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect),
Dev: pkg.Dev,
ExternalReferences: lo.Ternary(ref.URL != "", []types.ExternalRef{ref}, nil),
Locations: []types.Location{location},
ExternalReferences: lo.Ternary(ref.URL != "", []ftypes.ExternalRef{ref}, nil),
Locations: []ftypes.Location{location},
}
libs[pkgID] = lib
pkgs[pkgID] = newPkg
// npm builds graph using optional deps. e.g.:
// └─┬ watchpack@1.7.5
@@ -179,15 +178,15 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.
}
if len(dependsOn) > 0 {
deps = append(deps, types.Dependency{
ID: lib.ID,
deps = append(deps, ftypes.Dependency{
ID: newPkg.ID,
DependsOn: dependsOn,
})
}
}
return maps.Values(libs), deps
return maps.Values(pkgs), deps
}
// for local package npm uses links. e.g.:
@@ -271,73 +270,73 @@ func findDependsOn(pkgPath, depName string, packages map[string]Package) (string
return "", xerrors.Errorf("can't find dependsOn for %s", depName)
}
func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]types.Library, []types.Dependency) {
func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]ftypes.Package, []ftypes.Dependency) {
// Update package name and version mapping.
for pkgName, dep := range dependencies {
// Overwrite the existing package version so that the nested version can take precedence.
versions[pkgName] = dep.Version
}
var libs []types.Library
var deps []types.Dependency
var pkgs []ftypes.Package
var deps []ftypes.Dependency
for pkgName, dep := range dependencies {
lib := types.Library{
pkg := ftypes.Package{
ID: packageID(pkgName, dep.Version),
Name: pkgName,
Version: dep.Version,
Dev: dep.Dev,
Relationship: types.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies
ExternalReferences: []types.ExternalRef{
Relationship: ftypes.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies
ExternalReferences: []ftypes.ExternalRef{
{
Type: types.RefOther,
Type: ftypes.RefOther,
URL: dep.Resolved,
},
},
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: dep.StartLine,
EndLine: dep.EndLine,
},
},
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
dependsOn := make([]string, 0, len(dep.Requires))
for libName, requiredVer := range dep.Requires {
for pName, requiredVer := range dep.Requires {
// Try to resolve the version with nested dependencies first
if resolvedDep, ok := dep.Dependencies[libName]; ok {
libID := packageID(libName, resolvedDep.Version)
dependsOn = append(dependsOn, libID)
if resolvedDep, ok := dep.Dependencies[pName]; ok {
pkgID := packageID(pName, resolvedDep.Version)
dependsOn = append(dependsOn, pkgID)
continue
}
// Try to resolve the version with the higher level dependencies
if ver, ok := versions[libName]; ok {
dependsOn = append(dependsOn, packageID(libName, ver))
if ver, ok := versions[pName]; ok {
dependsOn = append(dependsOn, packageID(pName, ver))
continue
}
// It should not reach here.
p.logger.Warn("Unable to resolve the version",
log.String("name", libName), log.String("version", requiredVer))
log.String("name", pName), log.String("version", requiredVer))
}
if len(dependsOn) > 0 {
deps = append(deps, types.Dependency{
ID: packageID(lib.Name, lib.Version),
deps = append(deps, ftypes.Dependency{
ID: packageID(pkg.Name, pkg.Version),
DependsOn: dependsOn,
})
}
if dep.Dependencies != nil {
// Recursion
childLibs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions))
libs = append(libs, childLibs...)
childpkgs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions))
pkgs = append(pkgs, childpkgs...)
deps = append(deps, childDeps...)
}
}
return libs, deps
return pkgs, deps
}
func (p *Parser) pkgNameFromPath(pkgPath string) string {
@@ -354,8 +353,8 @@ func (p *Parser) pkgNameFromPath(pkgPath string) string {
return pkgPath
}
func uniqueDeps(deps []types.Dependency) []types.Dependency {
var uniqDeps []types.Dependency
func uniqueDeps(deps []ftypes.Dependency) []ftypes.Dependency {
var uniqDeps ftypes.Dependencies
unique := make(map[string]struct{})
for _, dep := range deps {
@@ -367,14 +366,14 @@ func uniqueDeps(deps []types.Dependency) []types.Dependency {
}
}
sort.Sort(types.Dependencies(uniqDeps))
sort.Sort(uniqDeps)
return uniqDeps
}
func isIndirectLib(pkgPath string, directDeps map[string]struct{}) bool {
func isIndirectPkg(pkgPath string, directDeps map[string]struct{}) bool {
// A project can contain 2 different versions of the same dependency.
// e.g. `node_modules/string-width/node_modules/strip-ansi` and `node_modules/string-ansi`
// direct dependencies always have root path (`node_modules/<lib_name>`)
// direct dependencies always have root path (`node_modules/<pkg_name>`)
if _, ok := directDeps[pkgPath]; ok {
return false
}
@@ -411,7 +410,7 @@ func packageID(name, version string) string {
return dependency.ID(ftypes.Npm, name, version)
}
func sortExternalReferences(refs []types.ExternalRef) {
func sortExternalReferences(refs []ftypes.ExternalRef) {
sort.Slice(refs, func(i, j int) bool {
if refs[i].Type != refs[j].Type {
return refs[i].Type < refs[j].Type

View File

@@ -7,44 +7,44 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
file string // Test input file
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "lock version v1",
file: "testdata/package-lock_v1.json",
want: npmV1Libs,
want: npmV1Pkgs,
wantDeps: npmDeps,
},
{
name: "lock version v2",
file: "testdata/package-lock_v2.json",
want: npmV2Libs,
want: npmV2Pkgs,
wantDeps: npmDeps,
},
{
name: "lock version v3",
file: "testdata/package-lock_v3.json",
want: npmV2Libs,
want: npmV2Pkgs,
wantDeps: npmDeps,
},
{
name: "lock version v3 with workspace",
file: "testdata/package-lock_v3_with_workspace.json",
want: npmV3WithWorkspaceLibs,
want: npmV3WithWorkspacePkgs,
wantDeps: npmV3WithWorkspaceDeps,
},
{
name: "lock file v3 contains same dev and non-dev dependencies",
file: "testdata/package-lock_v3_with-same-dev-and-non-dev.json",
want: npmV3WithSameDevAndNonDevLibs,
want: npmV3WithSameDevAndNonDevPkgs,
wantDeps: npmV3WithSameDevAndNonDevDeps,
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
@@ -26,7 +25,7 @@ type packageJSON struct {
}
type Package struct {
types.Library
ftypes.Package
Dependencies map[string]string
OptionalDependencies map[string]string
DevDependencies map[string]string
@@ -57,11 +56,11 @@ func (p *Parser) Parse(r io.Reader) (Package, error) {
}
return Package{
Library: types.Library{
ID: id,
Name: pkgJSON.Name,
Version: pkgJSON.Version,
License: parseLicense(pkgJSON.License),
Package: ftypes.Package{
ID: id,
Name: pkgJSON.Name,
Version: pkgJSON.Version,
Licenses: parseLicense(pkgJSON.License),
},
Dependencies: pkgJSON.Dependencies,
OptionalDependencies: pkgJSON.OptionalDependencies,
@@ -70,17 +69,21 @@ func (p *Parser) Parse(r io.Reader) (Package, error) {
}, nil
}
func parseLicense(val interface{}) string {
func parseLicense(val interface{}) []string {
// the license isn't always a string, check for legacy struct if not string
switch v := val.(type) {
case string:
return v
if v != "" {
return []string{v}
}
case map[string]interface{}:
if license, ok := v["type"]; ok {
return license.(string)
if s, ok := license.(string); ok && s != "" {
return []string{s}
}
}
}
return ""
return nil
}
// parseWorkspaces returns slice of workspaces

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
@@ -27,11 +27,11 @@ func TestParse(t *testing.T) {
// npm install --save promise jquery
// npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}'
want: packagejson.Package{
Library: types.Library{
ID: "bootstrap@5.0.2",
Name: "bootstrap",
Version: "5.0.2",
License: "MIT",
Package: ftypes.Package{
ID: "bootstrap@5.0.2",
Name: "bootstrap",
Version: "5.0.2",
Licenses: []string{"MIT"},
},
Dependencies: map[string]string{
"js-tokens": "^4.0.0",
@@ -53,11 +53,11 @@ func TestParse(t *testing.T) {
name: "happy path - legacy license",
inputFile: "testdata/legacy_package.json",
want: packagejson.Package{
Library: types.Library{
ID: "angular@4.1.2",
Name: "angular",
Version: "4.1.2",
License: "ISC",
Package: ftypes.Package{
ID: "angular@4.1.2",
Name: "angular",
Version: "4.1.2",
Licenses: []string{"ISC"},
},
Dependencies: map[string]string{},
DevDependencies: map[string]string{
@@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
name: "happy path - version doesn't exist",
inputFile: "testdata/without_version_package.json",
want: packagejson.Package{
Library: types.Library{
Package: ftypes.Package{
ID: "",
Name: "angular",
},
@@ -80,7 +80,7 @@ func TestParse(t *testing.T) {
name: "happy path - workspace as struct",
inputFile: "testdata/workspace_as_map_package.json",
want: packagejson.Package{
Library: types.Library{
Package: ftypes.Package{
ID: "example@1.0.0",
Name: "example",
Version: "1.0.0",
@@ -109,8 +109,8 @@ func TestParse(t *testing.T) {
name: "without name and version",
inputFile: "testdata/without_name_and_version_package.json",
want: packagejson.Package{
Library: types.Library{
License: "MIT",
Package: ftypes.Package{
Licenses: []string{"MIT"},
},
},
},
@@ -162,7 +162,8 @@ func TestIsValidName(t *testing.T) {
{
name: "test@package",
want: false,
}, {
},
{
name: "test?package",
want: false,
},

View File

@@ -11,7 +11,6 @@ import (
"github.com/aquasecurity/go-version/pkg/semver"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -47,7 +46,7 @@ func NewParser() *Parser {
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile LockFile
if err := yaml.NewDecoder(r).Decode(&lockFile); err != nil {
return nil, nil, xerrors.Errorf("decode error: %w", err)
@@ -58,14 +57,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, nil
}
libs, deps := p.parse(lockVer, lockFile)
pkgs, deps := p.parse(lockVer, lockFile)
return libs, deps, nil
return pkgs, deps, nil
}
func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []types.Dependency) {
var libs []types.Library
var deps []types.Dependency
func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, []ftypes.Dependency) {
var pkgs []ftypes.Package
var deps []ftypes.Dependency
// Dependency path is a path to a dependency with a specific set of resolved subdependencies.
// cf. https://github.com/pnpm/spec/blob/ad27a225f81d9215becadfa540ef05fa4ad6dd60/dependency-path.md
@@ -90,22 +89,22 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []t
dependencies = append(dependencies, packageID(depName, depVer))
}
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: pkgID,
Name: name,
Version: version,
Relationship: lo.Ternary(isDirectLib(name, lockFile.Dependencies), types.RelationshipDirect, types.RelationshipIndirect),
Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect),
})
if len(dependencies) > 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: dependencies,
})
}
}
return libs, deps
return pkgs, deps
}
func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 {
@@ -179,7 +178,7 @@ func (p *Parser) parseDepPath(depPath, versionSep string) (string, string) {
return name, version
}
func isDirectLib(name string, directDeps map[string]interface{}) bool {
func isDirectPkg(name string, directDeps map[string]interface{}) bool {
_, ok := directDeps[name]
return ok
}

View File

@@ -3,21 +3,20 @@ package pnpm
import (
"os"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
file string // Test input file
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "normal",
@@ -65,39 +64,25 @@ func TestParse(t *testing.T) {
got, deps, err := NewParser().Parse(f)
require.NoError(t, err)
sortLibs(got)
sortLibs(tt.want)
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(tt.want))
assert.Equal(t, tt.want, got)
if tt.wantDeps != nil {
sortDeps(deps)
sortDeps(tt.wantDeps)
sort.Sort(ftypes.Dependencies(deps))
sort.Sort(ftypes.Dependencies(tt.wantDeps))
for _, dep := range deps {
sort.Strings(dep.DependsOn)
}
for _, dep := range tt.wantDeps {
sort.Strings(dep.DependsOn)
}
assert.Equal(t, tt.wantDeps, deps)
}
})
}
}
func sortDeps(deps []types.Dependency) {
sort.Slice(deps, func(i, j int) bool {
return strings.Compare(deps[i].ID, deps[j].ID) < 0
})
for i := range deps {
sort.Strings(deps[i].DependsOn)
}
}
func sortLibs(libs []types.Library) {
sort.Slice(libs, func(i, j int) bool {
ret := strings.Compare(libs[i].Name, libs[j].Name)
if ret == 0 {
return libs[i].Version < libs[j].Version
}
return ret < 0
})
}
func Test_parsePackage(t *testing.T) {
tests := []struct {
name string

View File

@@ -1,33 +1,33 @@
package pnpm
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// docker run --name node --rm -it node:16-alpine sh
// npm install -g pnpm
// pnpm add promise jquery
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u
pnpmNormal = []types.Library{
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u
pnpmNormal = []ftypes.Package{
{
ID: "asap@2.0.6",
Name: "asap",
Version: "2.0.6",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "jquery@3.6.0",
Name: "jquery",
Version: "3.6.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "promise@8.1.0",
Name: "promise",
Version: "8.1.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
}
pnpmNormalDeps = []types.Dependency{
pnpmNormalDeps = []ftypes.Dependency{
{
ID: "promise@8.1.0",
DependsOn: []string{"asap@2.0.6"},
@@ -38,46 +38,46 @@ var (
// npm install -g pnpm
// pnpm add react redux
// pnpm add -D mocha
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u
pnpmWithDev = []types.Library{
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u
pnpmWithDev = []ftypes.Package{
{
ID: "@babel/runtime@7.18.3",
Name: "@babel/runtime",
Version: "7.18.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "js-tokens@4.0.0",
Name: "js-tokens",
Version: "4.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "loose-envify@1.4.0",
Name: "loose-envify",
Version: "1.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "react@18.1.0",
Name: "react",
Version: "18.1.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "redux@4.2.0",
Name: "redux",
Version: "4.2.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "regenerator-runtime@0.13.9",
Name: "regenerator-runtime",
Version: "0.13.9",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
pnpmWithDevDeps = []types.Dependency{
pnpmWithDevDeps = []ftypes.Dependency{
{
ID: "@babel/runtime@7.18.3",
DependsOn: []string{"regenerator-runtime@0.13.9"},
@@ -100,346 +100,346 @@ var (
// npm install -g pnpm
// pnpm add react redux lodash request chalk commander
// pnpm add -D mocha
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u
pnpmMany = []types.Library{
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u
pnpmMany = []ftypes.Package{
{
ID: "@babel/runtime@7.18.3",
Name: "@babel/runtime",
Version: "7.18.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "ajv@6.12.6",
Name: "ajv",
Version: "6.12.6",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "asn1@0.2.6",
Name: "asn1",
Version: "0.2.6",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "assert-plus@1.0.0",
Name: "assert-plus",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "asynckit@0.4.0",
Name: "asynckit",
Version: "0.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "aws-sign2@0.7.0",
Name: "aws-sign2",
Version: "0.7.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "aws4@1.11.0",
Name: "aws4",
Version: "1.11.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "bcrypt-pbkdf@1.0.2",
Name: "bcrypt-pbkdf",
Version: "1.0.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "caseless@0.12.0",
Name: "caseless",
Version: "0.12.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "chalk@5.0.1",
Name: "chalk",
Version: "5.0.1",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "combined-stream@1.0.8",
Name: "combined-stream",
Version: "1.0.8",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "commander@9.3.0",
Name: "commander",
Version: "9.3.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "core-util-is@1.0.2",
Name: "core-util-is",
Version: "1.0.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "dashdash@1.14.1",
Name: "dashdash",
Version: "1.14.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "delayed-stream@1.0.0",
Name: "delayed-stream",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "ecc-jsbn@0.1.2",
Name: "ecc-jsbn",
Version: "0.1.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "extend@3.0.2",
Name: "extend",
Version: "3.0.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "extsprintf@1.3.0",
Name: "extsprintf",
Version: "1.3.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "fast-deep-equal@3.1.3",
Name: "fast-deep-equal",
Version: "3.1.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "fast-json-stable-stringify@2.1.0",
Name: "fast-json-stable-stringify",
Version: "2.1.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "forever-agent@0.6.1",
Name: "forever-agent",
Version: "0.6.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "form-data@2.3.3",
Name: "form-data",
Version: "2.3.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "getpass@0.1.7",
Name: "getpass",
Version: "0.1.7",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "har-schema@2.0.0",
Name: "har-schema",
Version: "2.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "har-validator@5.1.5",
Name: "har-validator",
Version: "5.1.5",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "http-signature@1.2.0",
Name: "http-signature",
Version: "1.2.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "is-typedarray@1.0.0",
Name: "is-typedarray",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "isstream@0.1.2",
Name: "isstream",
Version: "0.1.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "js-tokens@4.0.0",
Name: "js-tokens",
Version: "4.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "jsbn@0.1.1",
Name: "jsbn",
Version: "0.1.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "json-schema-traverse@0.4.1",
Name: "json-schema-traverse",
Version: "0.4.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "json-schema@0.4.0",
Name: "json-schema",
Version: "0.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "json-stringify-safe@5.0.1",
Name: "json-stringify-safe",
Version: "5.0.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "jsprim@1.4.2",
Name: "jsprim",
Version: "1.4.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "lodash@4.17.21",
Name: "lodash",
Version: "4.17.21",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "loose-envify@1.4.0",
Name: "loose-envify",
Version: "1.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "mime-db@1.52.0",
Name: "mime-db",
Version: "1.52.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "mime-types@2.1.35",
Name: "mime-types",
Version: "2.1.35",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "oauth-sign@0.9.0",
Name: "oauth-sign",
Version: "0.9.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "performance-now@2.1.0",
Name: "performance-now",
Version: "2.1.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "psl@1.8.0",
Name: "psl",
Version: "1.8.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "punycode@2.1.1",
Name: "punycode",
Version: "2.1.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "qs@6.5.3",
Name: "qs",
Version: "6.5.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "react@18.1.0",
Name: "react",
Version: "18.1.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "redux@4.2.0",
Name: "redux",
Version: "4.2.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "regenerator-runtime@0.13.9",
Name: "regenerator-runtime",
Version: "0.13.9",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "request@2.88.2",
Name: "request",
Version: "2.88.2",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "safe-buffer@5.2.1",
Name: "safe-buffer",
Version: "5.2.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "safer-buffer@2.1.2",
Name: "safer-buffer",
Version: "2.1.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "sshpk@1.17.0",
Name: "sshpk",
Version: "1.17.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "tough-cookie@2.5.0",
Name: "tough-cookie",
Version: "2.5.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "tunnel-agent@0.6.0",
Name: "tunnel-agent",
Version: "0.6.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "tweetnacl@0.14.5",
Name: "tweetnacl",
Version: "0.14.5",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "uri-js@4.4.1",
Name: "uri-js",
Version: "4.4.1",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "uuid@3.4.0",
Name: "uuid",
Version: "3.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "verror@1.10.0",
Name: "verror",
Version: "1.10.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
pnpmManyDeps = []types.Dependency{
pnpmManyDeps = []ftypes.Dependency{
{
ID: "@babel/runtime@7.18.3",
DependsOn: []string{"regenerator-runtime@0.13.9"},
@@ -610,48 +610,48 @@ var (
// pnpm update
// pnpm add https://github.com/debug-js/debug/tarball/4.3.4
// pnpm add https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipDirect},\n")}' | sort -u
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipDirect},\n")}' | sort -u
// manually update `Indirect` fields
pnpmArchives = []types.Library{
pnpmArchives = []ftypes.Package{
{
ID: "asynckit@0.4.0",
Name: "asynckit",
Version: "0.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "debug@4.3.4",
Name: "debug",
Version: "4.3.4",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "is-negative@2.0.1",
Name: "is-negative",
Version: "2.0.1",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "lodash@4.17.21",
Name: "lodash",
Version: "4.17.21",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "ms@2.1.2",
Name: "ms",
Version: "2.1.2",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "package1@1.0.0",
Name: "package1",
Version: "1.0.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
}
pnpmArchivesDeps = []types.Dependency{
pnpmArchivesDeps = []ftypes.Dependency{
{
ID: "debug@4.3.4",
DependsOn: []string{"ms@2.1.2"},
@@ -665,7 +665,7 @@ var (
// docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh
// npm install -g pnpm@8.5.1
// pnpm add promise@8.1.0 jquery@3.6.0
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u
pnpmV6 = pnpmNormal
pnpmV6Deps = pnpmNormalDeps
@@ -673,46 +673,46 @@ var (
// npm install -g pnpm@8.5.1
// pnpm add react@18.1.0 redux@4.2.0
// pnpm add -D mocha@10.0.0
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u
pnpmV6WithDev = []types.Library{
// pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u
pnpmV6WithDev = []ftypes.Package{
{
ID: "@babel/runtime@7.22.3",
Name: "@babel/runtime",
Version: "7.22.3",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "js-tokens@4.0.0",
Name: "js-tokens",
Version: "4.0.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "loose-envify@1.4.0",
Name: "loose-envify",
Version: "1.4.0",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
{
ID: "react@18.1.0",
Name: "react",
Version: "18.1.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "redux@4.2.0",
Name: "redux",
Version: "4.2.0",
Relationship: types.RelationshipDirect,
Relationship: ftypes.RelationshipDirect,
},
{
ID: "regenerator-runtime@0.13.11",
Name: "regenerator-runtime",
Version: "0.13.11",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
},
}
pnpmV6WithDevDeps = []types.Dependency{
pnpmV6WithDevDeps = []ftypes.Dependency{
{
ID: "@babel/runtime@7.22.3",
DependsOn: []string{"regenerator-runtime@0.13.11"},

View File

@@ -11,7 +11,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -31,7 +30,7 @@ type Library struct {
Patterns []string
Name string
Version string
Location types.Location
Location ftypes.Location
}
type Dependency struct {
Pattern string
@@ -128,14 +127,14 @@ func ignoreProtocol(protocol string) bool {
return false
}
func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []types.Dependency) {
func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []ftypes.Dependency) {
// find dependencies by patterns
for libID, depPatterns := range dependsOn {
for pkgID, depPatterns := range dependsOn {
depIDs := lo.Map(depPatterns, func(pattern string, index int) string {
return patternIDs[pattern]
})
deps = append(deps, types.Dependency{
ID: libID,
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: depIDs,
})
}
@@ -146,7 +145,7 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("yarn"),
}
@@ -236,7 +235,7 @@ func (p *Parser) parseBlock(block []byte, lineNum int) (lib Library, deps []stri
return Library{}, nil, scanner.LineNum(lineNum), nil
}
lib.Location = types.Location{
lib.Location = ftypes.Location{
StartLine: lineNum + emptyLines,
EndLine: scanner.LineNum(lineNum),
}
@@ -270,9 +269,9 @@ func parseDependency(line string) (string, error) {
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
lineNumber := 1
var libs []types.Library
var pkgs []ftypes.Package
// patternIDs holds mapping between patterns and library IDs
// e.g. ajv@^6.5.5 => ajv@6.10.0
@@ -291,21 +290,21 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
continue
}
libID := packageID(lib.Name, lib.Version)
libs = append(libs, types.Library{
ID: libID,
pkgID := packageID(lib.Name, lib.Version)
pkgs = append(pkgs, ftypes.Package{
ID: pkgID,
Name: lib.Name,
Version: lib.Version,
Locations: []types.Location{lib.Location},
Locations: []ftypes.Location{lib.Location},
})
for _, pattern := range lib.Patterns {
// e.g.
// combined-stream@^1.0.6 => combined-stream@1.0.8
// combined-stream@~1.0.6 => combined-stream@1.0.8
patternIDs[pattern] = libID
patternIDs[pattern] = pkgID
if len(deps) > 0 {
dependsOn[libID] = deps
dependsOn[pkgID] = deps
}
}
}
@@ -317,7 +316,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// Replace dependency patterns with library IDs
// e.g. ajv@^6.5.5 => ajv@6.10.0
deps := parseResults(patternIDs, dependsOn)
return libs, deps, nil
return pkgs, deps, nil
}
func packageID(name, version string) string {

View File

@@ -3,13 +3,12 @@ package yarn
import (
"os"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParsePattern(t *testing.T) {
@@ -255,8 +254,8 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
file string // Test input file
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "happy",
@@ -305,10 +304,10 @@ func TestParse(t *testing.T) {
got, deps, err := NewParser().Parse(f)
require.NoError(t, err)
sortLibs(got)
sortLibs(tt.want)
sortPkgs(got)
sortPkgs(tt.want)
assert.Equal(t, tt.want, got)
if tt.wantDeps != nil {
sortDeps(deps)
sortDeps(tt.wantDeps)
@@ -318,31 +317,16 @@ func TestParse(t *testing.T) {
}
}
func sortDeps(deps []types.Dependency) {
sort.Slice(deps, func(i, j int) bool {
return strings.Compare(deps[i].ID, deps[j].ID) < 0
})
for i := range deps {
sort.Strings(deps[i].DependsOn)
func sortPkgs(pkgs ftypes.Packages) {
sort.Sort(pkgs)
for _, pkg := range pkgs {
sort.Sort(pkg.Locations)
}
}
func sortLibs(libs []types.Library) {
sort.Slice(libs, func(i, j int) bool {
ret := strings.Compare(libs[i].Name, libs[j].Name)
if ret == 0 {
return libs[i].Version < libs[j].Version
}
return ret < 0
})
for _, lib := range libs {
sortLocations(lib.Locations)
func sortDeps(deps ftypes.Dependencies) {
sort.Sort(deps)
for _, dep := range deps {
sort.Strings(dep.DependsOn)
}
}
func sortLocations(locs []types.Location) {
sort.Slice(locs, func(i, j int) bool {
return locs[i].StartLine < locs[j].StartLine
})
}

View File

@@ -1,30 +1,30 @@
package yarn
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
yarnHappy = []types.Library{
{ID: "@babel/helper-regex@7.4.4", Name: "@babel/helper-regex", Version: "7.4.4", Locations: []types.Location{{StartLine: 4, EndLine: 9}}},
{ID: "ansi-regex@2.1.1", Name: "ansi-regex", Version: "2.1.1", Locations: []types.Location{{StartLine: 11, EndLine: 14}}},
{ID: "ansi-regex@3.0.0", Name: "ansi-regex", Version: "3.0.0", Locations: []types.Location{{StartLine: 16, EndLine: 19}}},
{ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 21, EndLine: 24}}},
{ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Locations: []types.Location{{StartLine: 26, EndLine: 29}}},
{ID: "is-fullwidth-code-point@2.0.0", Name: "is-fullwidth-code-point", Version: "2.0.0", Locations: []types.Location{{StartLine: 31, EndLine: 34}}},
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 41, EndLine: 44}}},
{ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 36, EndLine: 39}}},
{ID: "lodash@4.17.11", Name: "lodash", Version: "4.17.11", Locations: []types.Location{{StartLine: 46, EndLine: 49}}},
{ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 51, EndLine: 56}}},
{ID: "safe-buffer@5.1.2", Name: "safe-buffer", Version: "5.1.2", Locations: []types.Location{{StartLine: 58, EndLine: 61}}},
{ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Locations: []types.Location{{StartLine: 63, EndLine: 66}}},
{ID: "statuses@1.5.0", Name: "statuses", Version: "1.5.0", Locations: []types.Location{{StartLine: 68, EndLine: 71}}},
{ID: "string-width@2.1.1", Name: "string-width", Version: "2.1.1", Locations: []types.Location{{StartLine: 73, EndLine: 79}}},
{ID: "strip-ansi@3.0.1", Name: "strip-ansi", Version: "3.0.1", Locations: []types.Location{{StartLine: 82, EndLine: 87}}},
{ID: "strip-ansi@4.0.0", Name: "strip-ansi", Version: "4.0.0", Locations: []types.Location{{StartLine: 89, EndLine: 94}}},
{ID: "whatwg-fetch@3.0.0", Name: "whatwg-fetch", Version: "3.0.0", Locations: []types.Location{{StartLine: 96, EndLine: 99}}},
{ID: "wide-align@1.1.3", Name: "wide-align", Version: "1.1.3", Locations: []types.Location{{StartLine: 101, EndLine: 106}}},
yarnHappy = []ftypes.Package{
{ID: "@babel/helper-regex@7.4.4", Name: "@babel/helper-regex", Version: "7.4.4", Locations: []ftypes.Location{{StartLine: 4, EndLine: 9}}},
{ID: "ansi-regex@2.1.1", Name: "ansi-regex", Version: "2.1.1", Locations: []ftypes.Location{{StartLine: 11, EndLine: 14}}},
{ID: "ansi-regex@3.0.0", Name: "ansi-regex", Version: "3.0.0", Locations: []ftypes.Location{{StartLine: 16, EndLine: 19}}},
{ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []ftypes.Location{{StartLine: 21, EndLine: 24}}},
{ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Locations: []ftypes.Location{{StartLine: 26, EndLine: 29}}},
{ID: "is-fullwidth-code-point@2.0.0", Name: "is-fullwidth-code-point", Version: "2.0.0", Locations: []ftypes.Location{{StartLine: 31, EndLine: 34}}},
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 41, EndLine: 44}}},
{ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 36, EndLine: 39}}},
{ID: "lodash@4.17.11", Name: "lodash", Version: "4.17.11", Locations: []ftypes.Location{{StartLine: 46, EndLine: 49}}},
{ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []ftypes.Location{{StartLine: 51, EndLine: 56}}},
{ID: "safe-buffer@5.1.2", Name: "safe-buffer", Version: "5.1.2", Locations: []ftypes.Location{{StartLine: 58, EndLine: 61}}},
{ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Locations: []ftypes.Location{{StartLine: 63, EndLine: 66}}},
{ID: "statuses@1.5.0", Name: "statuses", Version: "1.5.0", Locations: []ftypes.Location{{StartLine: 68, EndLine: 71}}},
{ID: "string-width@2.1.1", Name: "string-width", Version: "2.1.1", Locations: []ftypes.Location{{StartLine: 73, EndLine: 79}}},
{ID: "strip-ansi@3.0.1", Name: "strip-ansi", Version: "3.0.1", Locations: []ftypes.Location{{StartLine: 82, EndLine: 87}}},
{ID: "strip-ansi@4.0.0", Name: "strip-ansi", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 89, EndLine: 94}}},
{ID: "whatwg-fetch@3.0.0", Name: "whatwg-fetch", Version: "3.0.0", Locations: []ftypes.Location{{StartLine: 96, EndLine: 99}}},
{ID: "wide-align@1.1.3", Name: "wide-align", Version: "1.1.3", Locations: []ftypes.Location{{StartLine: 101, EndLine: 106}}},
}
yarnHappyDeps = []types.Dependency{
yarnHappyDeps = []ftypes.Dependency{
{
ID: "@babel/helper-regex@7.4.4",
DependsOn: []string{
@@ -64,26 +64,26 @@ var (
},
}
yarnV2Happy = []types.Library{
{ID: "@types/color-name@1.1.1", Name: "@types/color-name", Version: "1.1.1", Locations: []types.Location{{StartLine: 8, EndLine: 13}}},
{ID: "abbrev@1.1.1", Name: "abbrev", Version: "1.1.1", Locations: []types.Location{{StartLine: 15, EndLine: 20}}},
{ID: "ansi-styles@3.2.1", Name: "ansi-styles", Version: "3.2.1", Locations: []types.Location{{StartLine: 22, EndLine: 29}}},
{ID: "ansi-styles@4.2.1", Name: "ansi-styles", Version: "4.2.1", Locations: []types.Location{{StartLine: 31, EndLine: 39}}},
{ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Locations: []types.Location{{StartLine: 41, EndLine: 46}}},
{ID: "async@3.2.0", Name: "async", Version: "3.2.0", Locations: []types.Location{{StartLine: 48, EndLine: 53}}},
{ID: "color-convert@1.9.3", Name: "color-convert", Version: "1.9.3", Locations: []types.Location{{StartLine: 63, EndLine: 70}}},
{ID: "color-convert@2.0.1", Name: "color-convert", Version: "2.0.1", Locations: []types.Location{{StartLine: 72, EndLine: 79}}},
{ID: "color-name@1.1.3", Name: "color-name", Version: "1.1.3", Locations: []types.Location{{StartLine: 81, EndLine: 86}}},
{ID: "color-name@1.1.4", Name: "color-name", Version: "1.1.4", Locations: []types.Location{{StartLine: 88, EndLine: 93}}},
{ID: "ipaddr.js@1.9.1", Name: "ipaddr.js", Version: "1.9.1", Locations: []types.Location{{StartLine: 104, EndLine: 109}}},
{ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 111, EndLine: 116}}},
{ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Locations: []types.Location{{StartLine: 118, EndLine: 127}}},
{ID: "node-gyp@7.1.0", Name: "node-gyp", Version: "7.1.0", Locations: []types.Location{{StartLine: 129, EndLine: 136}}},
{ID: "once@1.4.0", Name: "once", Version: "1.4.0", Locations: []types.Location{{StartLine: 138, EndLine: 145}}},
{ID: "wrappy@1.0.2", Name: "wrappy", Version: "1.0.2", Locations: []types.Location{{StartLine: 147, EndLine: 152}}},
yarnV2Happy = []ftypes.Package{
{ID: "@types/color-name@1.1.1", Name: "@types/color-name", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 8, EndLine: 13}}},
{ID: "abbrev@1.1.1", Name: "abbrev", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 15, EndLine: 20}}},
{ID: "ansi-styles@3.2.1", Name: "ansi-styles", Version: "3.2.1", Locations: []ftypes.Location{{StartLine: 22, EndLine: 29}}},
{ID: "ansi-styles@4.2.1", Name: "ansi-styles", Version: "4.2.1", Locations: []ftypes.Location{{StartLine: 31, EndLine: 39}}},
{ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Locations: []ftypes.Location{{StartLine: 41, EndLine: 46}}},
{ID: "async@3.2.0", Name: "async", Version: "3.2.0", Locations: []ftypes.Location{{StartLine: 48, EndLine: 53}}},
{ID: "color-convert@1.9.3", Name: "color-convert", Version: "1.9.3", Locations: []ftypes.Location{{StartLine: 63, EndLine: 70}}},
{ID: "color-convert@2.0.1", Name: "color-convert", Version: "2.0.1", Locations: []ftypes.Location{{StartLine: 72, EndLine: 79}}},
{ID: "color-name@1.1.3", Name: "color-name", Version: "1.1.3", Locations: []ftypes.Location{{StartLine: 81, EndLine: 86}}},
{ID: "color-name@1.1.4", Name: "color-name", Version: "1.1.4", Locations: []ftypes.Location{{StartLine: 88, EndLine: 93}}},
{ID: "ipaddr.js@1.9.1", Name: "ipaddr.js", Version: "1.9.1", Locations: []ftypes.Location{{StartLine: 104, EndLine: 109}}},
{ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 111, EndLine: 116}}},
{ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 118, EndLine: 127}}},
{ID: "node-gyp@7.1.0", Name: "node-gyp", Version: "7.1.0", Locations: []ftypes.Location{{StartLine: 129, EndLine: 136}}},
{ID: "once@1.4.0", Name: "once", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 138, EndLine: 145}}},
{ID: "wrappy@1.0.2", Name: "wrappy", Version: "1.0.2", Locations: []ftypes.Location{{StartLine: 147, EndLine: 152}}},
}
yarnV2HappyDeps = []types.Dependency{
yarnV2HappyDeps = []ftypes.Dependency{
{
ID: "ansi-styles@3.2.1",
DependsOn: []string{
@@ -123,13 +123,13 @@ var (
},
}
yarnWithLocal = []types.Library{
{ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 5, EndLine: 8}}},
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 10, EndLine: 13}}},
{ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 15, EndLine: 20}}},
yarnWithLocal = []ftypes.Package{
{ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []ftypes.Location{{StartLine: 5, EndLine: 8}}},
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 10, EndLine: 13}}},
{ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []ftypes.Location{{StartLine: 15, EndLine: 20}}},
}
yarnWithLocalDeps = []types.Dependency{
yarnWithLocalDeps = []ftypes.Dependency{
{
ID: "promise@8.0.3",
DependsOn: []string{
@@ -138,20 +138,20 @@ var (
},
}
yarnWithNpm = []types.Library{
{ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Locations: []types.Location{{StartLine: 1, EndLine: 4}}},
yarnWithNpm = []ftypes.Package{
{ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Locations: []ftypes.Location{{StartLine: 1, EndLine: 4}}},
}
yarnBadProtocol = []types.Library{
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 4, EndLine: 7}}},
yarnBadProtocol = []ftypes.Package{
{ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 4, EndLine: 7}}},
}
yarnV2DepsWithProtocol = []types.Library{
{ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Locations: []types.Location{{StartLine: 16, EndLine: 26}}},
{ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Locations: []types.Location{{StartLine: 28, EndLine: 33}}},
yarnV2DepsWithProtocol = []ftypes.Package{
{ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Locations: []ftypes.Location{{StartLine: 16, EndLine: 26}}},
{ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Locations: []ftypes.Location{{StartLine: 28, EndLine: 33}}},
}
yarnV2DepsWithProtocolDeps = []types.Dependency{
yarnV2DepsWithProtocolDeps = []ftypes.Dependency{
{
ID: "debug@4.3.4",
DependsOn: []string{"ms@2.1.2"},

View File

@@ -6,7 +6,7 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -25,29 +25,27 @@ type config struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var cfgData config
if err := xml.NewDecoder(r).Decode(&cfgData); err != nil {
return nil, nil, xerrors.Errorf("failed to decode .config file: %w", err)
}
var libs []types.Library
var pkgs []ftypes.Package
for _, pkg := range cfgData.Packages {
if pkg.ID == "" || pkg.DevDependency {
continue
}
lib := types.Library{
pkgs = append(pkgs, ftypes.Package{
Name: pkg.ID,
Version: pkg.Version,
}
libs = append(libs, lib)
})
}
return utils.UniqueLibraries(libs), nil, nil
return utils.UniquePackages(pkgs), nil, nil
}

View File

@@ -8,20 +8,20 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string // Test input file
inputFile string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "Config",
inputFile: "testdata/packages.config",
want: []types.Library{
want: []ftypes.Package{
{Name: "Newtonsoft.Json", Version: "6.0.4"},
{Name: "Microsoft.AspNet.WebApi", Version: "5.2.2"},
},
@@ -29,7 +29,7 @@ func TestParse(t *testing.T) {
{
name: "with development dependency",
inputFile: "testdata/dev_dependency.config",
want: []types.Library{
want: []ftypes.Package{
{Name: "Newtonsoft.Json", Version: "8.0.3"},
},
},

View File

@@ -9,7 +9,6 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -31,11 +30,11 @@ type Dependency struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile LockFile
input, err := io.ReadAll(r)
if err != nil {
@@ -45,7 +44,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("failed to decode packages.lock.json: %w", err)
}
var libs []types.Library
var pkgs []ftypes.Package
depsMap := make(map[string][]string)
for _, targetContent := range lockFile.Targets {
for packageName, packageContent := range targetContent {
@@ -56,19 +55,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
depId := packageID(packageName, packageContent.Resolved)
lib := types.Library{
pkg := ftypes.Package{
ID: depId,
Name: packageName,
Version: packageContent.Resolved,
Relationship: lo.Ternary(packageContent.Type == "Direct", types.RelationshipDirect, types.RelationshipIndirect),
Locations: []types.Location{
Relationship: lo.Ternary(packageContent.Type == "Direct", ftypes.RelationshipDirect, ftypes.RelationshipIndirect),
Locations: []ftypes.Location{
{
StartLine: packageContent.StartLine,
EndLine: packageContent.EndLine,
},
},
}
libs = append(libs, lib)
pkgs = append(pkgs, pkg)
var dependsOn []string
@@ -86,16 +85,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
}
var deps []types.Dependency
var deps []ftypes.Dependency
for depId, dependsOn := range depsMap {
dep := types.Dependency{
dep := ftypes.Dependency{
ID: depId,
DependsOn: dependsOn,
}
deps = append(deps, dep)
}
return utils.UniqueLibraries(libs), deps, nil
return utils.UniquePackages(pkgs), deps, nil
}
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps

View File

@@ -10,14 +10,14 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
vectors := []struct {
file string // Test input file
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
file: "testdata/packages_lock_simple.json",
@@ -49,21 +49,8 @@ func TestParse(t *testing.T) {
got, deps, err := NewParser().Parse(f)
require.NoError(t, err)
sort.Slice(got, func(i, j int) bool {
ret := strings.Compare(got[i].Name, got[j].Name)
if ret == 0 {
return got[i].Version < got[j].Version
}
return ret < 0
})
sort.Slice(v.want, func(i, j int) bool {
ret := strings.Compare(v.want[i].Name, v.want[j].Name)
if ret == 0 {
return v.want[i].Version < v.want[j].Version
}
return ret < 0
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(v.want))
assert.Equal(t, v.want, got)
@@ -76,7 +63,7 @@ func TestParse(t *testing.T) {
}
}
func sortDeps(deps []types.Dependency) {
func sortDeps(deps []ftypes.Dependency) {
sort.Slice(deps, func(i, j int) bool {
return strings.Compare(deps[i].ID, deps[j].ID) < 0
})

View File

@@ -1,6 +1,6 @@
package lock
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest
@@ -11,13 +11,13 @@ var (
// dotnet add package NuGet.Frameworks
// dotnet restore --use-lock-file
// cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"'
nuGetSimple = []types.Library{
nuGetSimple = []ftypes.Package{
{
ID: "Newtonsoft.Json@12.0.3",
Name: "Newtonsoft.Json",
Version: "12.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 10,
@@ -28,8 +28,8 @@ var (
ID: "NuGet.Frameworks@5.7.0",
Name: "NuGet.Frameworks",
Version: "5.7.0",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 16,
@@ -37,7 +37,7 @@ var (
},
},
}
nuGetSimpleDeps []types.Dependency
nuGetSimpleDeps []ftypes.Dependency
// docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest
// apt -y update && apt -y install jq
@@ -47,13 +47,13 @@ var (
// dotnet add package NuGet.Frameworks
// dotnet restore --use-lock-file
// cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"'
nuGetSubDependencies = []types.Library{
nuGetSubDependencies = []ftypes.Package{
{
ID: "Microsoft.Extensions.ApiDescription.Server@3.0.0",
Name: "Microsoft.Extensions.ApiDescription.Server",
Version: "3.0.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 29,
EndLine: 33,
@@ -64,8 +64,8 @@ var (
ID: "Microsoft.OpenApi@1.1.4",
Name: "Microsoft.OpenApi",
Version: "1.1.4",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 34,
EndLine: 38,
@@ -76,8 +76,8 @@ var (
ID: "Newtonsoft.Json@12.0.3",
Name: "Newtonsoft.Json",
Version: "12.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 10,
@@ -88,8 +88,8 @@ var (
ID: "NuGet.Frameworks@5.7.0",
Name: "NuGet.Frameworks",
Version: "5.7.0",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 16,
@@ -100,8 +100,8 @@ var (
ID: "Swashbuckle.AspNetCore@5.5.1",
Name: "Swashbuckle.AspNetCore",
Version: "5.5.1",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 17,
EndLine: 28,
@@ -112,8 +112,8 @@ var (
ID: "Swashbuckle.AspNetCore.Swagger@5.5.1",
Name: "Swashbuckle.AspNetCore.Swagger",
Version: "5.5.1",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 39,
EndLine: 46,
@@ -124,8 +124,8 @@ var (
ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1",
Name: "Swashbuckle.AspNetCore.SwaggerGen",
Version: "5.5.1",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 47,
EndLine: 54,
@@ -136,8 +136,8 @@ var (
ID: "Swashbuckle.AspNetCore.SwaggerUI@5.5.1",
Name: "Swashbuckle.AspNetCore.SwaggerUI",
Version: "5.5.1",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 55,
EndLine: 59,
@@ -145,7 +145,7 @@ var (
},
},
}
nuGetSubDependenciesDeps = []types.Dependency{
nuGetSubDependenciesDeps = []ftypes.Dependency{
{
ID: "Swashbuckle.AspNetCore.Swagger@5.5.1",
DependsOn: []string{"Microsoft.OpenApi@1.1.4"},
@@ -173,13 +173,13 @@ var (
// dotnet add package AWSSDK.Core
// dotnet restore --use-lock-file
// cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"'
nuGetLegacy = []types.Library{
nuGetLegacy = []ftypes.Package{
{
ID: "AWSSDK.Core@3.5.1.30",
Name: "AWSSDK.Core",
Version: "3.5.1.30",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 10,
@@ -190,8 +190,8 @@ var (
ID: "Newtonsoft.Json@12.0.3",
Name: "Newtonsoft.Json",
Version: "12.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 16,
@@ -199,7 +199,7 @@ var (
},
},
}
nuGetLegacyDeps []types.Dependency
nuGetLegacyDeps []ftypes.Dependency
// docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest
// apt -y update && apt -y install jq
@@ -210,13 +210,13 @@ var (
// dotnet restore --use-lock-file
// dotnet add package AWSSDK.Core
// cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' | sort -u
nuGetMultiTarget = []types.Library{
nuGetMultiTarget = []ftypes.Package{
{
ID: "AWSSDK.Core@3.5.1.30",
Name: "AWSSDK.Core",
Version: "3.5.1.30",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 10,
@@ -243,8 +243,8 @@ var (
ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0",
Name: "Microsoft.Bcl.AsyncInterfaces",
Version: "1.1.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 460,
EndLine: 467,
@@ -255,8 +255,8 @@ var (
ID: "Microsoft.CSharp@4.3.0",
Name: "Microsoft.CSharp",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 138,
EndLine: 147,
@@ -267,8 +267,8 @@ var (
ID: "Microsoft.NETCore.Platforms@1.1.0",
Name: "Microsoft.NETCore.Platforms",
Version: "1.1.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 148,
EndLine: 152,
@@ -283,8 +283,8 @@ var (
ID: "Microsoft.NETCore.Targets@1.1.0",
Name: "Microsoft.NETCore.Targets",
Version: "1.1.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 153,
EndLine: 157,
@@ -295,8 +295,8 @@ var (
ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0",
Name: "Microsoft.NETFramework.ReferenceAssemblies",
Version: "1.0.0",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 19,
@@ -315,8 +315,8 @@ var (
ID: "Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0",
Name: "Microsoft.NETFramework.ReferenceAssemblies.net20",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 26,
EndLine: 30,
@@ -331,8 +331,8 @@ var (
ID: "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0",
Name: "Microsoft.NETFramework.ReferenceAssemblies.net40",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 82,
EndLine: 86,
@@ -343,8 +343,8 @@ var (
ID: "NETStandard.Library@1.6.1",
Name: "NETStandard.Library",
Version: "1.6.1",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 95,
EndLine: 125,
@@ -355,8 +355,8 @@ var (
ID: "NETStandard.Library@2.0.3",
Name: "NETStandard.Library",
Version: "2.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 445,
EndLine: 453,
@@ -367,8 +367,8 @@ var (
ID: "Newtonsoft.Json@12.0.3",
Name: "Newtonsoft.Json",
Version: "12.0.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 20,
EndLine: 25,
@@ -395,8 +395,8 @@ var (
ID: "System.Collections@4.3.0",
Name: "System.Collections",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 158,
EndLine: 167,
@@ -407,8 +407,8 @@ var (
ID: "System.ComponentModel@4.3.0",
Name: "System.ComponentModel",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 168,
EndLine: 175,
@@ -418,9 +418,9 @@ var (
{
ID: "System.ComponentModel.Primitives@4.3.0",
Name: "System.ComponentModel.Primitives",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
Version: "4.3.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 176,
EndLine: 185,
@@ -430,9 +430,9 @@ var (
{
ID: "System.ComponentModel.TypeConverter@4.3.0",
Name: "System.ComponentModel.TypeConverter",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
Version: "4.3.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 186,
EndLine: 203,
@@ -443,8 +443,8 @@ var (
ID: "System.Diagnostics.Debug@4.3.0",
Name: "System.Diagnostics.Debug",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 204,
EndLine: 213,
@@ -455,8 +455,8 @@ var (
ID: "System.Diagnostics.Tools@4.3.0",
Name: "System.Diagnostics.Tools",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 214,
EndLine: 223,
@@ -467,8 +467,8 @@ var (
ID: "System.Dynamic.Runtime@4.3.0",
Name: "System.Dynamic.Runtime",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 224,
EndLine: 234,
@@ -479,8 +479,8 @@ var (
ID: "System.Globalization@4.3.0",
Name: "System.Globalization",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 235,
EndLine: 244,
@@ -491,8 +491,8 @@ var (
ID: "System.IO@4.3.0",
Name: "System.IO",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 245,
EndLine: 256,
@@ -503,8 +503,8 @@ var (
ID: "System.Linq@4.3.0",
Name: "System.Linq",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 257,
EndLine: 265,
@@ -515,8 +515,8 @@ var (
ID: "System.Linq.Expressions@4.3.0",
Name: "System.Linq.Expressions",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 266,
EndLine: 274,
@@ -527,8 +527,8 @@ var (
ID: "System.Net.Primitives@4.3.0",
Name: "System.Net.Primitives",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 275,
EndLine: 284,
@@ -539,8 +539,8 @@ var (
ID: "System.ObjectModel@4.3.0",
Name: "System.ObjectModel",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 285,
EndLine: 292,
@@ -551,8 +551,8 @@ var (
ID: "System.Reflection@4.3.0",
Name: "System.Reflection",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 293,
EndLine: 304,
@@ -562,9 +562,9 @@ var (
{
ID: "System.Reflection.Extensions@4.3.0",
Name: "System.Reflection.Extensions",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
Version: "4.3.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 305,
EndLine: 315,
@@ -574,9 +574,9 @@ var (
{
ID: "System.Reflection.Primitives@4.3.0",
Name: "System.Reflection.Primitives",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
Version: "4.3.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 316,
EndLine: 325,
@@ -586,9 +586,9 @@ var (
{
ID: "System.Resources.ResourceManager@4.3.0",
Name: "System.Resources.ResourceManager",
Relationship: types.RelationshipIndirect,
Relationship: ftypes.RelationshipIndirect,
Version: "4.3.0",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 326,
EndLine: 337,
@@ -599,8 +599,8 @@ var (
ID: "System.Runtime@4.3.0",
Name: "System.Runtime",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 338,
EndLine: 346,
@@ -611,8 +611,8 @@ var (
ID: "System.Runtime.CompilerServices.Unsafe@4.5.2",
Name: "System.Runtime.CompilerServices.Unsafe",
Version: "4.5.2",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 473,
EndLine: 477,
@@ -623,8 +623,8 @@ var (
ID: "System.Runtime.Extensions@4.3.0",
Name: "System.Runtime.Extensions",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 347,
EndLine: 356,
@@ -635,8 +635,8 @@ var (
ID: "System.Runtime.Serialization.Primitives@4.3.0",
Name: "System.Runtime.Serialization.Primitives",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 357,
EndLine: 364,
@@ -647,8 +647,8 @@ var (
ID: "System.Text.Encoding@4.3.0",
Name: "System.Text.Encoding",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 365,
EndLine: 374,
@@ -659,8 +659,8 @@ var (
ID: "System.Text.Encoding.Extensions@4.3.0",
Name: "System.Text.Encoding.Extensions",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 375,
EndLine: 385,
@@ -671,8 +671,8 @@ var (
ID: "System.Text.RegularExpressions@4.3.0",
Name: "System.Text.RegularExpressions",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 386,
EndLine: 393,
@@ -683,8 +683,8 @@ var (
ID: "System.Threading@4.3.0",
Name: "System.Threading",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 394,
EndLine: 402,
@@ -695,8 +695,8 @@ var (
ID: "System.Threading.Tasks@4.3.0",
Name: "System.Threading.Tasks",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 403,
EndLine: 412,
@@ -707,8 +707,8 @@ var (
ID: "System.Threading.Tasks.Extensions@4.5.2",
Name: "System.Threading.Tasks.Extensions",
Version: "4.5.2",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 478,
EndLine: 485,
@@ -719,8 +719,8 @@ var (
ID: "System.Xml.ReaderWriter@4.3.0",
Name: "System.Xml.ReaderWriter",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 413,
EndLine: 423,
@@ -731,8 +731,8 @@ var (
ID: "System.Xml.XDocument@4.3.0",
Name: "System.Xml.XDocument",
Version: "4.3.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 424,
EndLine: 433,
@@ -740,7 +740,7 @@ var (
},
},
}
nuGetMultiTargetDeps = []types.Dependency{
nuGetMultiTargetDeps = []ftypes.Dependency{
{
ID: "AWSSDK.Core@3.5.1.30",
DependsOn: []string{"Microsoft.Bcl.AsyncInterfaces@1.1.0"},

View File

@@ -8,12 +8,11 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type pkg struct {
type Pkg struct {
Version string `xml:"Version,attr"`
UpdatePackageName string `xml:"Update,attr"`
IncludePackageName string `xml:"Include,attr"`
@@ -21,8 +20,8 @@ type pkg struct {
// https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L150
type itemGroup struct {
PackageReferenceEntry []pkg `xml:"PackageReference"`
PackageVersionEntry []pkg `xml:"PackageVersion"`
PackageReferenceEntry []Pkg `xml:"PackageReference"`
PackageVersionEntry []Pkg `xml:"PackageVersion"`
}
type project struct {
@@ -32,11 +31,11 @@ type project struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p pkg) library() types.Library {
func (p Pkg) Package() ftypes.Package {
// Update attribute is considered legacy, so preferring Include
name := p.UpdatePackageName
if p.IncludePackageName != "" {
@@ -45,20 +44,20 @@ func (p pkg) library() types.Library {
name = strings.TrimSpace(name)
version := strings.TrimSpace(p.Version)
return types.Library{
return ftypes.Package{
ID: dependency.ID(ftypes.NuGet, name, version),
Name: name,
Version: version,
}
}
func shouldSkipLib(lib types.Library) bool {
if lib.Name == "" || lib.Version == "" {
func shouldSkipPkg(pkg ftypes.Package) bool {
if pkg.Name == "" || pkg.Version == "" {
return true
}
// *packages.props files don't contain variable resolution information.
// So we need to skip them.
if isVariable(lib.Name) || isVariable(lib.Version) {
if isVariable(pkg.Name) || isVariable(pkg.Version) {
return true
}
return false
@@ -68,20 +67,20 @@ func isVariable(s string) bool {
return strings.HasPrefix(s, "$(") && strings.HasSuffix(s, ")")
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var configData project
if err := xml.NewDecoder(r).Decode(&configData); err != nil {
return nil, nil, xerrors.Errorf("failed to decode '*.packages.props' file: %w", err)
}
var libs []types.Library
var pkgs []ftypes.Package
for _, item := range configData.ItemGroups {
for _, pkg := range append(item.PackageReferenceEntry, item.PackageVersionEntry...) {
lib := pkg.library()
if !shouldSkipLib(lib) {
libs = append(libs, lib)
pkg := pkg.Package()
if !shouldSkipPkg(pkg) {
pkgs = append(pkgs, pkg)
}
}
}
return utils.UniqueLibraries(libs), nil, nil
return utils.UniquePackages(pkgs), nil, nil
}

View File

@@ -8,20 +8,20 @@ import (
"github.com/stretchr/testify/require"
config "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/packagesprops"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string // Test input file
inputFile string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "PackagesProps",
inputFile: "testdata/packages.props",
want: []types.Library{
want: []ftypes.Package{
{Name: "Microsoft.Extensions.Configuration", Version: "2.1.1", ID: "Microsoft.Extensions.Configuration@2.1.1"},
{Name: "Microsoft.Extensions.DependencyInjection.Abstractions", Version: "2.2.1", ID: "Microsoft.Extensions.DependencyInjection.Abstractions@2.2.1"},
{Name: "Microsoft.Extensions.Http", Version: "3.2.1", ID: "Microsoft.Extensions.Http@3.2.1"},
@@ -30,7 +30,7 @@ func TestParse(t *testing.T) {
{
name: "DirectoryPackagesProps",
inputFile: "testdata/Directory.Packages.props",
want: []types.Library{
want: []ftypes.Package{
{Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"},
{Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"},
{Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"},
@@ -39,7 +39,7 @@ func TestParse(t *testing.T) {
{
name: "SeveralItemGroupElements",
inputFile: "testdata/several_item_groups",
want: []types.Library{
want: []ftypes.Package{
{Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"},
{Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"},
{Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"},
@@ -48,14 +48,14 @@ func TestParse(t *testing.T) {
{
name: "VariablesAsNamesOrVersion",
inputFile: "testdata/variables_and_empty",
want: []types.Library{
want: []ftypes.Package{
{Name: "PackageFour", Version: "2.4.1", ID: "PackageFour@2.4.1"},
},
},
{
name: "NoItemGroupInXMLStructure",
inputFile: "testdata/no_item_group.props",
want: []types.Library(nil),
want: []ftypes.Package(nil),
},
{
name: "NoProject",

View File

@@ -10,13 +10,12 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type lockFile struct {
type LockFile struct {
Packages []packageInfo `json:"packages"`
}
type packageInfo struct {
@@ -32,14 +31,14 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("composer"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var lockFile lockFile
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile LockFile
input, err := io.ReadAll(r)
if err != nil {
return nil, nil, xerrors.Errorf("read error: %w", err)
@@ -48,61 +47,61 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("decode error: %w", err)
}
libs := make(map[string]types.Library)
pkgs := make(map[string]ftypes.Package)
foundDeps := make(map[string][]string)
for _, pkg := range lockFile.Packages {
lib := types.Library{
ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version),
Name: pkg.Name,
Version: pkg.Version,
Relationship: types.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies
License: strings.Join(pkg.License, ", "),
Locations: []types.Location{
for _, lpkg := range lockFile.Packages {
pkg := ftypes.Package{
ID: dependency.ID(ftypes.Composer, lpkg.Name, lpkg.Version),
Name: lpkg.Name,
Version: lpkg.Version,
Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies
Licenses: lpkg.License,
Locations: []ftypes.Location{
{
StartLine: pkg.StartLine,
EndLine: pkg.EndLine,
StartLine: lpkg.StartLine,
EndLine: lpkg.EndLine,
},
},
}
libs[lib.Name] = lib
pkgs[pkg.Name] = pkg
var dependsOn []string
for depName := range pkg.Require {
for depName := range lpkg.Require {
// Require field includes required php version, skip this
// Also skip PHP extensions
if depName == "php" || strings.HasPrefix(depName, "ext") {
continue
}
dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the libraries
dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the packages
}
if len(dependsOn) > 0 {
foundDeps[lib.ID] = dependsOn
foundDeps[pkg.ID] = dependsOn
}
}
// fill deps versions
var deps []types.Dependency
for libID, depsOn := range foundDeps {
var deps ftypes.Dependencies
for pkgID, depsOn := range foundDeps {
var dependsOn []string
for _, depName := range depsOn {
if lib, ok := libs[depName]; ok {
dependsOn = append(dependsOn, lib.ID)
if pkg, ok := pkgs[depName]; ok {
dependsOn = append(dependsOn, pkg.ID)
continue
}
p.logger.Debug("Unable to find version", log.String("name", depName))
}
sort.Strings(dependsOn)
deps = append(deps, types.Dependency{
ID: libID,
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: dependsOn,
})
}
libSlice := maps.Values(libs)
sort.Sort(types.Libraries(libSlice))
sort.Sort(types.Dependencies(deps))
pkgSlice := maps.Values(pkgs)
sort.Sort(ftypes.Packages(pkgSlice))
sort.Sort(deps)
return libSlice, deps, nil
return pkgSlice, deps, nil
}
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps

View File

@@ -1,7 +1,7 @@
package composer
import (
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
@@ -13,15 +13,15 @@ var (
// apk add jq
// composer require guzzlehttp/guzzle:6.5.8
// composer require pear/log:1.13.3 --dev
// composer show -i --no-dev -f json | jq --sort-keys -rc '.installed[] | "{ID: \"\(.name)@\(.version)\", Name: \"\(.name)\", Version: \"\(.version)\", License: \"MIT\", Locations: []types.Location{{StartLine: , EndLine: }}},"'
// composer show -i --no-dev -f json | jq --sort-keys -rc '.installed[] | "{ID: \"\(.name)@\(.version)\", Name: \"\(.name)\", Version: \"\(.version)\", License: \"MIT\", Locations: []ftypes.Location{{StartLine: , EndLine: }}},"'
// locations are filled manually
composerLibs = []types.Library{
composerPkgs = []ftypes.Package{
{
ID: "guzzlehttp/guzzle@6.5.8",
Name: "guzzlehttp/guzzle",
Version: "6.5.8",
License: "MIT",
Locations: []types.Location{
ID: "guzzlehttp/guzzle@6.5.8",
Name: "guzzlehttp/guzzle",
Version: "6.5.8",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 9,
EndLine: 123,
@@ -29,11 +29,11 @@ var (
},
},
{
ID: "guzzlehttp/promises@1.5.2",
Name: "guzzlehttp/promises",
Version: "1.5.2",
License: "MIT",
Locations: []types.Location{
ID: "guzzlehttp/promises@1.5.2",
Name: "guzzlehttp/promises",
Version: "1.5.2",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 124,
EndLine: 207,
@@ -41,11 +41,11 @@ var (
},
},
{
ID: "guzzlehttp/psr7@1.9.0",
Name: "guzzlehttp/psr7",
Version: "1.9.0",
License: "MIT",
Locations: []types.Location{
ID: "guzzlehttp/psr7@1.9.0",
Name: "guzzlehttp/psr7",
Version: "1.9.0",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 208,
EndLine: 317,
@@ -53,11 +53,11 @@ var (
},
},
{
ID: "psr/http-message@1.0.1",
Name: "psr/http-message",
Version: "1.0.1",
License: "MIT",
Locations: []types.Location{
ID: "psr/http-message@1.0.1",
Name: "psr/http-message",
Version: "1.0.1",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 318,
EndLine: 370,
@@ -65,11 +65,11 @@ var (
},
},
{
ID: "ralouphie/getallheaders@3.0.3",
Name: "ralouphie/getallheaders",
Version: "3.0.3",
License: "MIT",
Locations: []types.Location{
ID: "ralouphie/getallheaders@3.0.3",
Name: "ralouphie/getallheaders",
Version: "3.0.3",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 371,
EndLine: 414,
@@ -77,11 +77,11 @@ var (
},
},
{
ID: "symfony/polyfill-intl-idn@v1.27.0",
Name: "symfony/polyfill-intl-idn",
Version: "v1.27.0",
License: "MIT",
Locations: []types.Location{
ID: "symfony/polyfill-intl-idn@v1.27.0",
Name: "symfony/polyfill-intl-idn",
Version: "v1.27.0",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 415,
EndLine: 501,
@@ -89,11 +89,11 @@ var (
},
},
{
ID: "symfony/polyfill-intl-normalizer@v1.27.0",
Name: "symfony/polyfill-intl-normalizer",
Version: "v1.27.0",
License: "MIT",
Locations: []types.Location{
ID: "symfony/polyfill-intl-normalizer@v1.27.0",
Name: "symfony/polyfill-intl-normalizer",
Version: "v1.27.0",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 502,
EndLine: 585,
@@ -101,11 +101,11 @@ var (
},
},
{
ID: "symfony/polyfill-php72@v1.27.0",
Name: "symfony/polyfill-php72",
Version: "v1.27.0",
License: "MIT",
Locations: []types.Location{
ID: "symfony/polyfill-php72@v1.27.0",
Name: "symfony/polyfill-php72",
Version: "v1.27.0",
Licenses: []string{"MIT"},
Locations: []ftypes.Location{
{
StartLine: 586,
EndLine: 661,
@@ -114,7 +114,7 @@ var (
},
}
// dependencies are filled manually
composerDeps = []types.Dependency{
composerDeps = []ftypes.Dependency{
{
ID: "guzzlehttp/guzzle@6.5.8",
DependsOn: []string{
@@ -144,13 +144,13 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
file string
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "happy path",
file: "testdata/composer_happy.lock",
wantLibs: composerLibs,
wantPkgs: composerPkgs,
wantDeps: composerDeps,
},
}
@@ -161,10 +161,10 @@ func TestParse(t *testing.T) {
require.NoError(t, err)
defer f.Close()
gotLibs, gotDeps, err := NewParser().Parse(f)
gotPkgs, gotDeps, err := NewParser().Parse(f)
require.NoError(t, err)
assert.Equal(t, tt.wantLibs, gotLibs)
assert.Equal(t, tt.wantPkgs, gotPkgs)
assert.Equal(t, tt.wantDeps, gotDeps)
})
}

View File

@@ -7,9 +7,10 @@ import (
"net/textproto"
"strings"
"github.com/samber/lo"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -18,7 +19,7 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("python"),
}
@@ -26,7 +27,7 @@ func NewParser() types.Parser {
// Parse parses egg and wheel metadata.
// e.g. .egg-info/PKG-INFO and dist-info/METADATA
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
rd := textproto.NewReader(bufio.NewReader(r))
h, err := rd.ReadMIMEHeader()
if e := textproto.ProtocolError(""); errors.As(err, &e) {
@@ -82,11 +83,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
license = "file://" + h.Get("License-File")
}
return []types.Library{
return []ftypes.Package{
{
Name: name,
Version: version,
License: license,
Name: name,
Version: version,
Licenses: lo.Ternary(license != "", []string{license}, nil),
},
}, nil, nil
}

View File

@@ -8,14 +8,14 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want []types.Library
want []ftypes.Package
wantErr bool
}{
// listing dependencies based on METADATA/PKG-INFO files
@@ -33,16 +33,22 @@ func TestParse(t *testing.T) {
// cd /usr/lib/python3.9/site-packages/setuptools-52.0.0-py3.9.egg-info/
// cat PKG-INFO | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \
// tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
want: []types.Library{{Name: "setuptools", Version: "51.3.3", License: "UNKNOWN"}},
want: []ftypes.Package{
{
Name: "setuptools",
Version: "51.3.3",
Licenses: []string{"UNKNOWN"},
},
},
},
{
name: "egg PKG-INFO with description containing non-RFC 7230 bytes",
input: "testdata/unidecode-egg-info.PKG-INFO",
want: []types.Library{
want: []ftypes.Package{
{
Name: "Unidecode",
Version: "0.4.1",
License: "UNKNOWN",
Name: "Unidecode",
Version: "0.4.1",
Licenses: []string{"UNKNOWN"},
},
},
},
@@ -55,7 +61,13 @@ func TestParse(t *testing.T) {
// cd /usr/lib/python3.9/site-packages/
// cat distlib-0.3.1-py3.9.egg-info | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \
// tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python license"}},
want: []ftypes.Package{
{
Name: "distlib",
Version: "0.3.1",
Licenses: []string{"Python license"},
},
},
},
{
name: "wheel METADATA",
@@ -67,31 +79,53 @@ func TestParse(t *testing.T) {
// find dist-infos/ | grep -v METADATA | xargs rm -R
// for single METADATA file with known name
// cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
want: []types.Library{{Name: "simple", Version: "0.1.0", License: ""}},
// cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^Licenses: []string{" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\"}, \""$2"\", \""$3"\"\}\n")}'
want: []ftypes.Package{
{
Name: "simple",
Version: "0.1.0",
Licenses: nil,
},
},
},
{
name: "wheel METADATA",
// for single METADATA file with known name
// cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
// cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^Licenses: []string{" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\"}, \""$2"\", \""$3"\"\}\n")}'
input: "testdata/distlib-0.3.1.METADATA",
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python Software Foundation License"}},
want: []ftypes.Package{
{
Name: "distlib",
Version: "0.3.1",
Licenses: []string{"Python Software Foundation License"},
},
},
},
{
name: "wheel METADATA",
// Input defines "Classifier: License" but it ends at "OSI Approved" which doesn't define any specific license, thus "License" field is added to results
input: "testdata/asyncssh-2.14.2.METADATA",
want: []types.Library{{Name: "asyncssh", Version: "2.14.2", License: "Eclipse Public License v2.0"}},
want: []ftypes.Package{
{
Name: "asyncssh",
Version: "2.14.2",
Licenses: []string{"Eclipse Public License v2.0"},
},
},
},
{
name: "wheel METADATA",
// Input defines multiple "Classifier: License"
input: "testdata/pyphen-0.14.0.METADATA",
want: []types.Library{
{Name: "pyphen", Version: "0.14.0", License: "GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"},
want: []ftypes.Package{
{
Name: "pyphen",
Version: "0.14.0",
Licenses: []string{"GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"},
},
},
},
{
@@ -102,33 +136,33 @@ func TestParse(t *testing.T) {
{
name: "with License-Expression field",
input: "testdata/iniconfig-2.0.0.METADATA",
want: []types.Library{
want: []ftypes.Package{
{
Name: "iniconfig",
Version: "2.0.0",
License: "MIT",
Name: "iniconfig",
Version: "2.0.0",
Licenses: []string{"MIT"},
},
},
},
{
name: "with an empty license field but with license in Classifier",
input: "testdata/zipp-3.12.1.METADATA",
want: []types.Library{
want: []ftypes.Package{
{
Name: "zipp",
Version: "3.12.1",
License: "MIT License",
Name: "zipp",
Version: "3.12.1",
Licenses: []string{"MIT License"},
},
},
},
{
name: "without licenses, but with a license file (a license in Classifier was removed)",
input: "testdata/networkx-3.0.METADATA",
want: []types.Library{
want: []ftypes.Package{
{
Name: "networkx",
Version: "3.0",
License: "file://LICENSE.txt",
Name: "networkx",
Version: "3.0",
Licenses: []string{"file://LICENSE.txt"},
},
},
},

View File

@@ -10,7 +10,7 @@ import (
"golang.org/x/text/transform"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -24,11 +24,11 @@ const (
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
// `requirements.txt` can use byte order marks (BOM)
// e.g. on Windows `requirements.txt` can use UTF-16LE with BOM
// We need to override them to avoid the file being read incorrectly
@@ -36,7 +36,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
decodedReader := transform.NewReader(r, transformer)
scanner := bufio.NewScanner(decodedReader)
var libs []types.Library
var pkgs []ftypes.Package
for scanner.Scan() {
line := scanner.Text()
line = strings.ReplaceAll(line, " ", "")
@@ -49,7 +49,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
if len(s) != 2 {
continue
}
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
Name: s[0],
Version: s[1],
})
@@ -57,7 +57,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
if err := scanner.Err(); err != nil {
return nil, nil, xerrors.Errorf("scan error: %w", err)
}
return libs, nil, nil
return pkgs, nil, nil
}
func rStripByKey(line, key string) string {

View File

@@ -8,13 +8,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
vectors := []struct {
file string
want []types.Library
want []ftypes.Package
}{
{
file: "testdata/requirements_flask.txt",

View File

@@ -1,9 +1,9 @@
package pip
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
requirementsFlask = []types.Library{
requirementsFlask = []ftypes.Package{
{Name: "click", Version: "8.0.0"},
{Name: "Flask", Version: "2.0.0"},
{Name: "itsdangerous", Version: "2.0.0"},
@@ -12,45 +12,45 @@ var (
{Name: "Werkzeug", Version: "2.0.0"},
}
requirementsComments = []types.Library{
requirementsComments = []ftypes.Package{
{Name: "click", Version: "8.0.0"},
{Name: "Flask", Version: "2.0.0"},
{Name: "Jinja2", Version: "3.0.0"},
{Name: "MarkupSafe", Version: "2.0.0"},
}
requirementsSpaces = []types.Library{
requirementsSpaces = []ftypes.Package{
{Name: "click", Version: "8.0.0"},
{Name: "Flask", Version: "2.0.0"},
{Name: "itsdangerous", Version: "2.0.0"},
{Name: "Jinja2", Version: "3.0.0"},
}
requirementsNoVersion = []types.Library{
requirementsNoVersion = []ftypes.Package{
{Name: "Flask", Version: "2.0.0"},
}
requirementsOperator = []types.Library{
requirementsOperator = []ftypes.Package{
{Name: "Django", Version: "2.3.4"},
{Name: "SomeProject", Version: "5.4"},
}
requirementsHash = []types.Library{
requirementsHash = []ftypes.Package{
{Name: "FooProject", Version: "1.2"},
{Name: "Jinja2", Version: "3.0.0"},
}
requirementsHyphens = []types.Library{
requirementsHyphens = []ftypes.Package{
{Name: "oauth2-client", Version: "4.0.0"},
{Name: "python-gitlab", Version: "2.0.0"},
}
requirementsExtras = []types.Library{
requirementsExtras = []ftypes.Package{
{Name: "pyjwt", Version: "2.1.0"},
{Name: "celery", Version: "4.4.7"},
}
requirementsUtf16le = []types.Library{
requirementsUtf16le = []ftypes.Package{
{Name: "attrs", Version: "20.3.0"},
}
)

View File

@@ -7,7 +7,7 @@ import (
"github.com/liamg/jfather"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -22,11 +22,11 @@ type dependency struct {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile lockFile
input, err := io.ReadAll(r)
if err != nil {
@@ -36,20 +36,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("failed to decode Pipenv.lock: %w", err)
}
var libs []types.Library
for pkgName, dependency := range lockFile.Default {
libs = append(libs, types.Library{
var pkgs []ftypes.Package
for pkgName, dep := range lockFile.Default {
pkgs = append(pkgs, ftypes.Package{
Name: pkgName,
Version: strings.TrimLeft(dependency.Version, "="),
Locations: []types.Location{
Version: strings.TrimLeft(dep.Version, "="),
Locations: []ftypes.Location{
{
StartLine: dependency.StartLine,
EndLine: dependency.EndLine,
StartLine: dep.StartLine,
EndLine: dep.EndLine,
},
},
})
}
return libs, nil, nil
return pkgs, nil, nil
}
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps

View File

@@ -4,19 +4,18 @@ import (
"os"
"path"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
vectors := []struct {
file string // Test input file
want []types.Library
want []ftypes.Package
}{
{
file: "testdata/Pipfile_normal.lock",
@@ -40,21 +39,8 @@ func TestParse(t *testing.T) {
got, _, err := NewParser().Parse(f)
require.NoError(t, err)
sort.Slice(got, func(i, j int) bool {
ret := strings.Compare(got[i].Name, got[j].Name)
if ret == 0 {
return got[i].Version < got[j].Version
}
return ret < 0
})
sort.Slice(v.want, func(i, j int) bool {
ret := strings.Compare(v.want[i].Name, v.want[j].Name)
if ret == 0 {
return v.want[i].Version < v.want[j].Version
}
return ret < 0
})
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(v.want))
assert.Equal(t, v.want, got)
})

View File

@@ -1,6 +1,6 @@
package pipenv
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// docker run --name pipenv --rm -it python:3.9-alpine sh
@@ -11,13 +11,13 @@ var (
// pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"'
// graph doesn't contain information about location of dependency in lock file.
// add locations manually
pipenvNormal = []types.Library{
{Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 65, EndLine: 71}}},
{Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 57, EndLine: 64}}},
{Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 40, EndLine: 56}}},
{Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 33, EndLine: 39}}},
{Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}},
{Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}},
pipenvNormal = []ftypes.Package{
{Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 65, EndLine: 71}}},
{Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 57, EndLine: 64}}},
{Name: "pyyaml", Version: "5.1", Locations: []ftypes.Location{{StartLine: 40, EndLine: 56}}},
{Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}},
{Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 26, EndLine: 32}}},
{Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 19, EndLine: 25}}},
}
// docker run --name pipenv --rm -it python:3.9-alpine bash
@@ -28,17 +28,17 @@ var (
// pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"'
// graph doesn't contain information about location of dependency in lock file.
// add locations manually
pipenvDjango = []types.Library{
{Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 95, EndLine: 101}}},
{Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 88, EndLine: 94}}},
{Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 80, EndLine: 87}}},
{Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 63, EndLine: 79}}},
{Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 56, EndLine: 62}}},
{Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 49, EndLine: 55}}},
{Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 41, EndLine: 48}}},
{Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 33, EndLine: 40}}},
{Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}},
{Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}},
pipenvDjango = []ftypes.Package{
{Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 95, EndLine: 101}}},
{Name: "sqlparse", Version: "0.3.0", Locations: []ftypes.Location{{StartLine: 88, EndLine: 94}}},
{Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 80, EndLine: 87}}},
{Name: "pyyaml", Version: "5.1", Locations: []ftypes.Location{{StartLine: 63, EndLine: 79}}},
{Name: "pytz", Version: "2019.1", Locations: []ftypes.Location{{StartLine: 56, EndLine: 62}}},
{Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 49, EndLine: 55}}},
{Name: "djangorestframework", Version: "3.9.3", Locations: []ftypes.Location{{StartLine: 41, EndLine: 48}}},
{Name: "django", Version: "2.2", Locations: []ftypes.Location{{StartLine: 33, EndLine: 40}}},
{Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 26, EndLine: 32}}},
{Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 19, EndLine: 25}}},
}
// docker run --name pipenv --rm -it python:3.9-alpine bash
@@ -49,30 +49,30 @@ var (
// pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"'
// graph doesn't contain information about location of dependency in lock file.
// add locations manually
pipenvMany = []types.Library{
{Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 237, EndLine: 244}}},
{Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 230, EndLine: 236}}},
{Name: "six", Version: "1.12.0", Locations: []types.Location{{StartLine: 222, EndLine: 229}}},
{Name: "simplejson", Version: "3.16.0", Locations: []types.Location{{StartLine: 204, EndLine: 221}}},
{Name: "s3transfer", Version: "0.2.0", Locations: []types.Location{{StartLine: 197, EndLine: 203}}},
{Name: "rsa", Version: "3.4.2", Locations: []types.Location{{StartLine: 190, EndLine: 196}}},
{Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 182, EndLine: 189}}},
{Name: "pyyaml", Version: "3.13", Locations: []types.Location{{StartLine: 165, EndLine: 181}}},
{Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 158, EndLine: 164}}},
{Name: "python-dateutil", Version: "2.8.0", Locations: []types.Location{{StartLine: 150, EndLine: 157}}},
{Name: "pyasn1", Version: "0.4.5", Locations: []types.Location{{StartLine: 142, EndLine: 149}}},
{Name: "markupsafe", Version: "1.1.1", Locations: []types.Location{{StartLine: 109, EndLine: 141}}},
{Name: "jmespath", Version: "0.9.4", Locations: []types.Location{{StartLine: 102, EndLine: 108}}},
{Name: "jinja2", Version: "2.10.1", Locations: []types.Location{{StartLine: 94, EndLine: 101}}},
{Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 87, EndLine: 93}}},
{Name: "framework", Version: "0.1.0", Locations: []types.Location{{StartLine: 80, EndLine: 86}}},
{Name: "docutils", Version: "0.14", Locations: []types.Location{{StartLine: 72, EndLine: 79}}},
{Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 64, EndLine: 71}}},
{Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 56, EndLine: 63}}},
{Name: "colorama", Version: "0.3.9", Locations: []types.Location{{StartLine: 49, EndLine: 55}}},
{Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 42, EndLine: 48}}},
{Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 35, EndLine: 41}}},
{Name: "botocore", Version: "1.12.137", Locations: []types.Location{{StartLine: 27, EndLine: 34}}},
{Name: "awscli", Version: "1.16.147", Locations: []types.Location{{StartLine: 19, EndLine: 26}}},
pipenvMany = []ftypes.Package{
{Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 237, EndLine: 244}}},
{Name: "sqlparse", Version: "0.3.0", Locations: []ftypes.Location{{StartLine: 230, EndLine: 236}}},
{Name: "six", Version: "1.12.0", Locations: []ftypes.Location{{StartLine: 222, EndLine: 229}}},
{Name: "simplejson", Version: "3.16.0", Locations: []ftypes.Location{{StartLine: 204, EndLine: 221}}},
{Name: "s3transfer", Version: "0.2.0", Locations: []ftypes.Location{{StartLine: 197, EndLine: 203}}},
{Name: "rsa", Version: "3.4.2", Locations: []ftypes.Location{{StartLine: 190, EndLine: 196}}},
{Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 182, EndLine: 189}}},
{Name: "pyyaml", Version: "3.13", Locations: []ftypes.Location{{StartLine: 165, EndLine: 181}}},
{Name: "pytz", Version: "2019.1", Locations: []ftypes.Location{{StartLine: 158, EndLine: 164}}},
{Name: "python-dateutil", Version: "2.8.0", Locations: []ftypes.Location{{StartLine: 150, EndLine: 157}}},
{Name: "pyasn1", Version: "0.4.5", Locations: []ftypes.Location{{StartLine: 142, EndLine: 149}}},
{Name: "markupsafe", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 109, EndLine: 141}}},
{Name: "jmespath", Version: "0.9.4", Locations: []ftypes.Location{{StartLine: 102, EndLine: 108}}},
{Name: "jinja2", Version: "2.10.1", Locations: []ftypes.Location{{StartLine: 94, EndLine: 101}}},
{Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 87, EndLine: 93}}},
{Name: "framework", Version: "0.1.0", Locations: []ftypes.Location{{StartLine: 80, EndLine: 86}}},
{Name: "docutils", Version: "0.14", Locations: []ftypes.Location{{StartLine: 72, EndLine: 79}}},
{Name: "djangorestframework", Version: "3.9.3", Locations: []ftypes.Location{{StartLine: 64, EndLine: 71}}},
{Name: "django", Version: "2.2", Locations: []ftypes.Location{{StartLine: 56, EndLine: 63}}},
{Name: "colorama", Version: "0.3.9", Locations: []ftypes.Location{{StartLine: 49, EndLine: 55}}},
{Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 42, EndLine: 48}}},
{Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 35, EndLine: 41}}},
{Name: "botocore", Version: "1.12.137", Locations: []ftypes.Location{{StartLine: 27, EndLine: 34}}},
{Name: "awscli", Version: "1.16.147", Locations: []ftypes.Location{{StartLine: 19, EndLine: 26}}},
}
)

View File

@@ -9,7 +9,6 @@ import (
version "github.com/aquasecurity/go-pep440-version"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -39,61 +38,61 @@ func NewParser() *Parser {
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockfile Lockfile
if _, err := toml.NewDecoder(r).Decode(&lockfile); err != nil {
return nil, nil, xerrors.Errorf("failed to decode poetry.lock: %w", err)
}
// Keep all installed versions
libVersions := p.parseVersions(lockfile)
pkgVersions := p.parseVersions(lockfile)
var libs []types.Library
var deps []types.Dependency
var pkgs []ftypes.Package
var deps []ftypes.Dependency
for _, pkg := range lockfile.Packages {
if pkg.Category == "dev" {
continue
}
pkgID := packageID(pkg.Name, pkg.Version)
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: pkgID,
Name: pkg.Name,
Version: pkg.Version,
})
dependsOn := p.parseDependencies(pkg.Dependencies, libVersions)
dependsOn := p.parseDependencies(pkg.Dependencies, pkgVersions)
if len(dependsOn) != 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: dependsOn,
})
}
}
return libs, deps, nil
return pkgs, deps, nil
}
// parseVersions stores all installed versions of libraries for use in dependsOn
// as the dependencies of libraries use version range.
// parseVersions stores all installed versions of packages for use in dependsOn
// as the dependencies of packages use version range.
func (p *Parser) parseVersions(lockfile Lockfile) map[string][]string {
libVersions := make(map[string][]string)
pkgVersions := make(map[string][]string)
for _, pkg := range lockfile.Packages {
if pkg.Category == "dev" {
continue
}
if vers, ok := libVersions[pkg.Name]; ok {
libVersions[pkg.Name] = append(vers, pkg.Version)
if vers, ok := pkgVersions[pkg.Name]; ok {
pkgVersions[pkg.Name] = append(vers, pkg.Version)
} else {
libVersions[pkg.Name] = []string{pkg.Version}
pkgVersions[pkg.Name] = []string{pkg.Version}
}
}
return libVersions
return pkgVersions
}
func (p *Parser) parseDependencies(deps map[string]any, libVersions map[string][]string) []string {
func (p *Parser) parseDependencies(deps map[string]any, pkgVersions map[string][]string) []string {
var dependsOn []string
for name, versRange := range deps {
if dep, err := p.parseDependency(name, versRange, libVersions); err != nil {
if dep, err := p.parseDependency(name, versRange, pkgVersions); err != nil {
p.logger.Debug("Failed to parse poetry dependency", log.Err(err))
} else if dep != "" {
dependsOn = append(dependsOn, dep)
@@ -105,9 +104,9 @@ func (p *Parser) parseDependencies(deps map[string]any, libVersions map[string][
return dependsOn
}
func (p *Parser) parseDependency(name string, versRange any, libVersions map[string][]string) (string, error) {
func (p *Parser) parseDependency(name string, versRange any, pkgVersions map[string][]string) (string, error) {
name = normalizePkgName(name)
vers, ok := libVersions[name]
vers, ok := pkgVersions[name]
if !ok {
return "", xerrors.Errorf("no version found for %q", name)
}

View File

@@ -8,34 +8,34 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
file string
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
wantErr assert.ErrorAssertionFunc
}{
{
name: "normal",
file: "testdata/poetry_normal.lock",
wantLibs: poetryNormal,
wantPkgs: poetryNormal,
wantErr: assert.NoError,
},
{
name: "many",
file: "testdata/poetry_many.lock",
wantLibs: poetryMany,
wantPkgs: poetryMany,
wantDeps: poetryManyDeps,
wantErr: assert.NoError,
},
{
name: "flask",
file: "testdata/poetry_flask.lock",
wantLibs: poetryFlask,
wantPkgs: poetryFlask,
wantDeps: poetryFlaskDeps,
wantErr: assert.NoError,
},
@@ -47,11 +47,11 @@ func TestParser_Parse(t *testing.T) {
defer f.Close()
p := NewParser()
gotLibs, gotDeps, err := p.Parse(f)
gotPkgs, gotDeps, err := p.Parse(f)
if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) {
return
}
assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file)
assert.Equalf(t, tt.wantPkgs, gotPkgs, "Parse(%v)", tt.file)
assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file)
})
}
@@ -62,7 +62,7 @@ func TestParseDependency(t *testing.T) {
name string
packageName string
versionRange interface{}
libsVersions map[string][]string
pkgsVersions map[string][]string
want string
wantErr string
}{
@@ -70,7 +70,7 @@ func TestParseDependency(t *testing.T) {
name: "handle package name",
packageName: "Test_project.Name",
versionRange: "*",
libsVersions: map[string][]string{
pkgsVersions: map[string][]string{
"test-project-name": {"1.0.0"},
},
want: "test-project-name@1.0.0",
@@ -79,7 +79,7 @@ func TestParseDependency(t *testing.T) {
name: "version range as string",
packageName: "test",
versionRange: ">=1.0.0",
libsVersions: map[string][]string{
pkgsVersions: map[string][]string{
"test": {"2.0.0"},
},
want: "test@2.0.0",
@@ -88,7 +88,7 @@ func TestParseDependency(t *testing.T) {
name: "version range == *",
packageName: "test",
versionRange: "*",
libsVersions: map[string][]string{
pkgsVersions: map[string][]string{
"test": {"3.0.0"},
},
want: "test@3.0.0",
@@ -100,23 +100,23 @@ func TestParseDependency(t *testing.T) {
"version": ">=4.8.3",
"markers": "python_version < \"3.8\"",
},
libsVersions: map[string][]string{
pkgsVersions: map[string][]string{
"test": {"5.0.0"},
},
want: "test@5.0.0",
},
{
name: "libsVersions doesn't contain required version",
name: "pkgsVersions doesn't contain required version",
packageName: "test",
versionRange: ">=1.0.0",
libsVersions: map[string][]string{},
pkgsVersions: map[string][]string{},
wantErr: "no version found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewParser().parseDependency(tt.packageName, tt.versionRange, tt.libsVersions)
got, err := NewParser().parseDependency(tt.packageName, tt.versionRange, tt.pkgsVersions)
if tt.wantErr != "" {
assert.ErrorContains(t, err, tt.wantErr)
return

View File

@@ -1,6 +1,6 @@
package poetry
import "github.com/aquasecurity/trivy/pkg/dependency/types"
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
var (
// docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh
@@ -10,7 +10,7 @@ var (
// poetry new normal && cd normal
// poetry add pypi@2.1
// poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }'
poetryNormal = []types.Library{
poetryNormal = []ftypes.Package{
{ID: "pypi@2.1", Name: "pypi", Version: "2.1"},
}
@@ -24,7 +24,7 @@ var (
// poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }'
// `--no-dev` flag uncorrected returns deps. Then need to remove `dev` deps manually
// list of dev deps - cat poetry.lock | grep 'category = "dev"' -B 3
poetryMany = []types.Library{
poetryMany = []ftypes.Package{
{ID: "attrs@22.2.0", Name: "attrs", Version: "22.2.0"},
{ID: "backports-cached-property@1.0.2", Name: "backports-cached-property", Version: "1.0.2"},
{ID: "build@0.10.0", Name: "build", Version: "0.10.0"},
@@ -82,7 +82,7 @@ var (
}
// cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice
poetryManyDeps = []types.Dependency{
poetryManyDeps = []ftypes.Dependency{
{ID: "build@0.10.0", DependsOn: []string{"colorama@0.4.6", "importlib-metadata@6.0.0", "packaging@23.0", "pyproject-hooks@1.0.0", "tomli@2.0.1"}},
{ID: "cachecontrol@0.12.11", DependsOn: []string{"lockfile@0.12.2", "msgpack@1.0.4", "requests@2.28.2"}},
{ID: "cffi@1.15.1", DependsOn: []string{"pycparser@2.21"}},
@@ -115,7 +115,7 @@ var (
// poetry new web && cd web
// poetry add flask@1.0.3
// poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }'
poetryFlask = []types.Library{
poetryFlask = []ftypes.Package{
{ID: "click@8.1.3", Name: "click", Version: "8.1.3"},
{ID: "colorama@0.4.6", Name: "colorama", Version: "0.4.6"},
{ID: "flask@1.0.3", Name: "flask", Version: "1.0.3"},
@@ -126,7 +126,7 @@ var (
}
// cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice
poetryFlaskDeps = []types.Dependency{
poetryFlaskDeps = []ftypes.Dependency{
{ID: "click@8.1.3", DependsOn: []string{"colorama@0.4.6"}},
{ID: "flask@1.0.3", DependsOn: []string{"click@8.1.3", "itsdangerous@2.1.2", "jinja2@3.1.2", "werkzeug@2.2.3"}},
{ID: "jinja2@3.1.2", DependsOn: []string{"markupsafe@2.1.2"}},

View File

@@ -9,21 +9,20 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
libs := make(map[string]types.Library)
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
pkgs := make(map[string]ftypes.Package)
var dependsOn, directDeps []string
var deps []types.Dependency
var deps []ftypes.Dependency
var pkgID string
lineNum := 1
@@ -34,7 +33,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// Parse dependencies
if countLeadingSpace(line) == 4 {
if len(dependsOn) > 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: dependsOn,
})
@@ -49,12 +48,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
version = strings.SplitN(version, "-", 2)[0] // drop platform (e.g. 1.13.6-x86_64-linux => 1.13.6)
name := s[0]
pkgID = packageID(name, version)
libs[name] = types.Library{
pkgs[name] = ftypes.Package{
ID: pkgID,
Name: name,
Version: version,
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: lineNum,
EndLine: lineNum,
@@ -77,7 +76,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
// append last dependency (if any)
if len(dependsOn) > 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: dependsOn,
})
@@ -85,17 +84,17 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// Identify which are direct dependencies
for _, d := range directDeps {
if l, ok := libs[d]; ok {
l.Relationship = types.RelationshipDirect
libs[d] = l
if l, ok := pkgs[d]; ok {
l.Relationship = ftypes.RelationshipDirect
pkgs[d] = l
}
}
for i, dep := range deps {
dependsOn = make([]string, 0)
for _, pkgName := range dep.DependsOn {
if lib, ok := libs[pkgName]; ok {
dependsOn = append(dependsOn, packageID(pkgName, lib.Version))
if pkg, ok := pkgs[pkgName]; ok {
dependsOn = append(dependsOn, packageID(pkgName, pkg.Version))
}
}
deps[i].DependsOn = dependsOn
@@ -104,11 +103,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("scan error: %w", err)
}
libSlice := maps.Values(libs)
sort.Slice(libSlice, func(i, j int) bool {
return libSlice[i].Name < libSlice[j].Name
})
return libSlice, deps, nil
pkgSlice := maps.Values(pkgs)
sort.Sort(ftypes.Packages(pkgSlice))
return pkgSlice, deps, nil
}
func countLeadingSpace(line string) int {

View File

@@ -9,41 +9,17 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/bundler"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var (
NormalLibs = []types.Library{
{
ID: "coderay@1.1.2",
Name: "coderay",
Version: "1.1.2",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
{
ID: "concurrent-ruby@1.1.5",
Name: "concurrent-ruby",
Version: "1.1.5",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
NormalPkgs = []ftypes.Package{
{
ID: "dotenv@2.7.2",
Name: "dotenv",
Version: "2.7.2",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 6,
EndLine: 6,
@@ -54,20 +30,56 @@ var (
ID: "faker@1.9.3",
Name: "faker",
Version: "1.9.3",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 7,
EndLine: 7,
},
},
},
{
ID: "pry@0.12.2",
Name: "pry",
Version: "0.12.2",
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 12,
EndLine: 12,
},
},
},
{
ID: "coderay@1.1.2",
Name: "coderay",
Version: "1.1.2",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
{
ID: "concurrent-ruby@1.1.5",
Name: "concurrent-ruby",
Version: "1.1.5",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
{
ID: "i18n@1.6.0",
Name: "i18n",
Version: "1.6.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 9,
EndLine: 9,
@@ -78,28 +90,16 @@ var (
ID: "method_source@0.9.2",
Name: "method_source",
Version: "0.9.2",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 11,
},
},
},
{
ID: "pry@0.12.2",
Name: "pry",
Version: "0.12.2",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
{
StartLine: 12,
EndLine: 12,
},
},
},
}
NormalDeps = []types.Dependency{
NormalDeps = []ftypes.Dependency{
{
ID: "faker@1.9.3",
DependsOn: []string{"i18n@1.6.0"},
@@ -116,37 +116,13 @@ var (
},
},
}
Bundler2Libs = []types.Library{
{
ID: "coderay@1.1.3",
Name: "coderay",
Version: "1.1.3",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
{
ID: "concurrent-ruby@1.1.10",
Name: "concurrent-ruby",
Version: "1.1.10",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
Bundler2Pkgs = []ftypes.Package{
{
ID: "dotenv@2.7.6",
Name: "dotenv",
Version: "2.7.6",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 6,
EndLine: 6,
@@ -157,64 +133,88 @@ var (
ID: "faker@2.21.0",
Name: "faker",
Version: "2.21.0",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 7,
EndLine: 7,
},
},
},
{
ID: "i18n@1.10.0",
Name: "i18n",
Version: "1.10.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 9,
EndLine: 9,
},
},
},
{
ID: "json@2.6.2",
Name: "json",
Version: "2.6.2",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 11,
EndLine: 11,
},
},
},
{
ID: "method_source@1.0.0",
Name: "method_source",
Version: "1.0.0",
Relationship: types.RelationshipIndirect,
Locations: []types.Location{
{
StartLine: 12,
EndLine: 12,
},
},
},
{
ID: "pry@0.14.1",
Name: "pry",
Version: "0.14.1",
Relationship: types.RelationshipDirect,
Locations: []types.Location{
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{
StartLine: 13,
EndLine: 13,
},
},
},
{
ID: "coderay@1.1.3",
Name: "coderay",
Version: "1.1.3",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 4,
EndLine: 4,
},
},
},
{
ID: "concurrent-ruby@1.1.10",
Name: "concurrent-ruby",
Version: "1.1.10",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 5,
},
},
},
{
ID: "i18n@1.10.0",
Name: "i18n",
Version: "1.10.0",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 9,
EndLine: 9,
},
},
},
{
ID: "method_source@1.0.0",
Name: "method_source",
Version: "1.0.0",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{
StartLine: 12,
EndLine: 12,
},
},
},
}
Bundler2Deps = []types.Dependency{
Bundler2Deps = []ftypes.Dependency{
{
ID: "faker@2.21.0",
DependsOn: []string{"i18n@1.10.0"},
@@ -237,28 +237,28 @@ func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
file string
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
wantErr assert.ErrorAssertionFunc
}{
{
name: "normal",
file: "testdata/Gemfile_normal.lock",
wantLibs: NormalLibs,
wantPkgs: NormalPkgs,
wantDeps: NormalDeps,
wantErr: assert.NoError,
},
{
name: "bundler2",
file: "testdata/Gemfile_bundler2.lock",
wantLibs: Bundler2Libs,
wantPkgs: Bundler2Pkgs,
wantDeps: Bundler2Deps,
wantErr: assert.NoError,
},
{
name: "malformed",
file: "testdata/Gemfile_malformed.lock",
wantLibs: []types.Library{},
wantPkgs: []ftypes.Package{},
wantErr: assert.NoError,
},
}
@@ -269,11 +269,11 @@ func TestParser_Parse(t *testing.T) {
defer f.Close()
p := &bundler.Parser{}
gotLibs, gotDeps, err := p.Parse(f)
gotPkgs, gotDeps, err := p.Parse(f)
if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) {
return
}
assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file)
assert.Equalf(t, tt.wantPkgs, gotPkgs, "Parse(%v)", tt.file)
assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file)
})
}

View File

@@ -8,7 +8,8 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/licensing"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -43,11 +44,11 @@ var (
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.Dependency, err error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) (pkgs []ftypes.Package, deps []ftypes.Dependency, err error) {
var newVar, name, version, license string
scanner := bufio.NewScanner(r)
@@ -94,11 +95,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.D
return nil, nil, xerrors.New("failed to parse gemspec")
}
return []types.Library{
return []ftypes.Package{
{
Name: name,
Version: version,
License: license,
Name: name,
Version: version,
Licenses: licensing.SplitLicenses(license),
},
}, nil, nil
}

View File

@@ -8,50 +8,62 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/gemspec"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
wantErr string
}{
{
name: "happy",
inputFile: "testdata/normal00.gemspec",
want: []types.Library{{
Name: "rake",
Version: "13.0.3",
License: "MIT",
}},
want: []ftypes.Package{
{
Name: "rake",
Version: "13.0.3",
Licenses: []string{"MIT"},
},
},
},
{
name: "another variable name",
inputFile: "testdata/normal01.gemspec",
want: []types.Library{{
Name: "async",
Version: "1.25.0",
}},
want: []ftypes.Package{
{
Name: "async",
Version: "1.25.0",
},
},
},
{
name: "license",
inputFile: "testdata/license.gemspec",
want: []types.Library{{
Name: "async",
Version: "1.25.0",
License: "MIT",
}},
want: []ftypes.Package{
{
Name: "async",
Version: "1.25.0",
Licenses: []string{"MIT"},
},
},
},
{
name: "multiple licenses",
inputFile: "testdata/multiple_licenses.gemspec",
want: []types.Library{{
Name: "test-unit",
Version: "3.3.7",
License: "Ruby, BSDL, PSFL",
}},
want: []ftypes.Package{
{
Name: "test-unit",
Version: "3.3.7",
Licenses: []string{
"Ruby",
"BSDL",
"PSFL",
},
},
},
},
{
name: "malformed variable name",

View File

@@ -7,7 +7,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -32,30 +31,30 @@ func convertError(err error) error {
type Parser struct{}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{}
}
// Parse scans files to try to report Rust crates and version injected into Rust binaries
// via https://github.com/rust-secure-code/cargo-auditable
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
info, err := rustaudit.GetDependencyInfo(r)
if err != nil {
return nil, nil, convertError(err)
}
var libs []types.Library
var deps []types.Dependency
var pkgs []ftypes.Package
var deps []ftypes.Dependency
for _, pkg := range info.Packages {
if pkg.Kind != rustaudit.Runtime {
continue
}
pkgID := packageID(pkg.Name, pkg.Version)
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: pkgID,
Name: pkg.Name,
Version: pkg.Version,
Relationship: lo.Ternary(pkg.Root, types.RelationshipRoot, types.RelationshipUnknown), // TODO: Determine the direct dependencies by checking the dependencies of the root crate
Relationship: lo.Ternary(pkg.Root, ftypes.RelationshipRoot, ftypes.RelationshipUnknown), // TODO: Determine the direct dependencies by checking the dependencies of the root crate
})
var childDeps []string
@@ -66,14 +65,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
}
if len(childDeps) > 0 {
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: pkgID,
DependsOn: childDeps,
})
}
}
return libs, deps, nil
return pkgs, deps, nil
}
func packageID(name, version string) string {

View File

@@ -8,28 +8,28 @@ import (
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/rust/binary"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
// Test binaries generated from cargo-auditable test fixture
// https://github.com/rust-secure-code/cargo-auditable/tree/6b77151/cargo-auditable/tests/fixtures/workspace
var (
libs = []types.Library{
pkgs = []ftypes.Package{
{
ID: "crate_with_features@0.1.0",
Name: "crate_with_features",
Version: "0.1.0",
Relationship: types.RelationshipRoot,
Relationship: ftypes.RelationshipRoot,
},
{
ID: "library_crate@0.1.0",
Name: "library_crate",
Version: "0.1.0",
Relationship: types.RelationshipUnknown,
Relationship: ftypes.RelationshipUnknown,
},
}
deps = []types.Dependency{
deps = []ftypes.Dependency{
{
ID: "crate_with_features@0.1.0",
DependsOn: []string{"library_crate@0.1.0"},
@@ -41,26 +41,26 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
wantDeps []types.Dependency
want []ftypes.Package
wantDeps []ftypes.Dependency
wantErr string
}{
{
name: "ELF",
inputFile: "testdata/test.elf",
want: libs,
want: pkgs,
wantDeps: deps,
},
{
name: "PE",
inputFile: "testdata/test.exe",
want: libs,
want: pkgs,
wantDeps: deps,
},
{
name: "Mach-O",
inputFile: "testdata/test.macho",
want: libs,
want: pkgs,
wantDeps: deps,
},
{

View File

@@ -10,7 +10,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -30,13 +29,13 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("cargo"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockfile Lockfile
decoder := toml.NewDecoder(r)
if _, err := decoder.Decode(&lockfile); err != nil {
@@ -52,21 +51,21 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
lineNumIdx := pkgParser.parse()
// We need to get version for unique dependencies for lockfile v3 from lockfile.Packages
pkgs := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) {
pkgMap := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) {
return pkg.Name, pkg
})
var libs []types.Library
var deps []types.Dependency
for _, pkg := range lockfile.Packages {
pkgID := packageID(pkg.Name, pkg.Version)
lib := types.Library{
var pkgs ftypes.Packages
var deps ftypes.Dependencies
for _, lpkg := range lockfile.Packages {
pkgID := packageID(lpkg.Name, lpkg.Version)
pkg := ftypes.Package{
ID: pkgID,
Name: pkg.Name,
Version: pkg.Version,
Name: lpkg.Name,
Version: lpkg.Version,
}
if pos, ok := lineNumIdx[pkgID]; ok {
lib.Locations = []types.Location{
pkg.Locations = []ftypes.Location{
{
StartLine: pos.start,
EndLine: pos.end,
@@ -74,17 +73,17 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}
}
libs = append(libs, lib)
dep := p.parseDependencies(pkgID, pkg, pkgs)
pkgs = append(pkgs, pkg)
dep := p.parseDependencies(pkgID, lpkg, pkgMap)
if dep != nil {
deps = append(deps, *dep)
}
}
sort.Sort(types.Libraries(libs))
sort.Sort(types.Dependencies(deps))
return libs, deps, nil
sort.Sort(pkgs)
sort.Sort(deps)
return pkgs, deps, nil
}
func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *types.Dependency {
func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *ftypes.Dependency {
var dependOn []string
for _, pkgDep := range pkg.Dependencies {
@@ -118,7 +117,7 @@ func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]c
}
if len(dependOn) > 0 {
sort.Strings(dependOn)
return &types.Dependency{
return &ftypes.Dependency{
ID: pkgId,
DependsOn: dependOn,
}

View File

@@ -5,77 +5,339 @@ import (
"os"
"path"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var (
cargoNormalLibs = []types.Library{
{ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 8, EndLine: 13}}},
{ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}},
{ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 20, EndLine: 26}}},
{ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 43, EndLine: 51}}},
{ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 15, EndLine: 18}}},
{ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 33, EndLine: 36}}},
{ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 28, EndLine: 31}}},
{ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 38, EndLine: 41}}},
cargoNormalPkgs = []ftypes.Package{
{
ID: "normal@0.1.0",
Name: "normal",
Version: "0.1.0",
Locations: []ftypes.Location{
{
StartLine: 8,
EndLine: 13,
},
},
},
{
ID: "libc@0.2.54",
Name: "libc",
Version: "0.2.54",
Locations: []ftypes.Location{
{
StartLine: 3,
EndLine: 6,
},
},
},
{
ID: "typemap@0.3.3",
Name: "typemap",
Version: "0.3.3",
Locations: []ftypes.Location{
{
StartLine: 20,
EndLine: 26,
},
},
},
{
ID: "url@1.7.2",
Name: "url",
Version: "1.7.2",
Locations: []ftypes.Location{
{
StartLine: 43,
EndLine: 51,
},
},
},
{
ID: "unsafe-any@0.4.2",
Name: "unsafe-any",
Version: "0.4.2",
Locations: []ftypes.Location{
{
StartLine: 15,
EndLine: 18,
},
},
},
{
ID: "matches@0.1.8",
Name: "matches",
Version: "0.1.8",
Locations: []ftypes.Location{
{
StartLine: 33,
EndLine: 36,
},
},
},
{
ID: "idna@0.1.5",
Name: "idna",
Version: "0.1.5",
Locations: []ftypes.Location{
{
StartLine: 28,
EndLine: 31,
},
},
},
{
ID: "percent-encoding@1.0.1",
Name: "percent-encoding",
Version: "1.0.1",
Locations: []ftypes.Location{
{
StartLine: 38,
EndLine: 41,
},
},
},
}
cargoNormalDeps = []types.Dependency{
cargoNormalDeps = []ftypes.Dependency{
{
ID: "normal@0.1.0",
DependsOn: []string{"libc@0.2.54"}},
DependsOn: []string{"libc@0.2.54"},
},
{
ID: "typemap@0.3.3",
DependsOn: []string{"unsafe-any@0.4.2"},
},
{
ID: "url@1.7.2",
DependsOn: []string{"idna@0.1.5", "matches@0.1.8", "percent-encoding@1.0.1"},
ID: "url@1.7.2",
DependsOn: []string{
"idna@0.1.5",
"matches@0.1.8",
"percent-encoding@1.0.1",
},
},
}
cargoMixedLibs = []types.Library{
{ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 17, EndLine: 22}}},
{ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}},
{ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 55, EndLine: 61}}},
{ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 26, EndLine: 34}}},
{ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 9, EndLine: 12}}},
{ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 41, EndLine: 44}}},
{ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 36, EndLine: 39}}},
{ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 46, EndLine: 49}}},
cargoMixedPkgs = []ftypes.Package{
{
ID: "normal@0.1.0",
Name: "normal",
Version: "0.1.0",
Locations: []ftypes.Location{
{
StartLine: 17,
EndLine: 22,
},
},
},
{
ID: "libc@0.2.54",
Name: "libc",
Version: "0.2.54",
Locations: []ftypes.Location{
{
StartLine: 3,
EndLine: 6,
},
},
},
{
ID: "typemap@0.3.3",
Name: "typemap",
Version: "0.3.3",
Locations: []ftypes.Location{
{
StartLine: 55,
EndLine: 61,
},
},
},
{
ID: "url@1.7.2",
Name: "url",
Version: "1.7.2",
Locations: []ftypes.Location{
{
StartLine: 26,
EndLine: 34,
},
},
},
{
ID: "unsafe-any@0.4.2",
Name: "unsafe-any",
Version: "0.4.2",
Locations: []ftypes.Location{
{
StartLine: 9,
EndLine: 12,
},
},
},
{
ID: "matches@0.1.8",
Name: "matches",
Version: "0.1.8",
Locations: []ftypes.Location{
{
StartLine: 41,
EndLine: 44,
},
},
},
{
ID: "idna@0.1.5",
Name: "idna",
Version: "0.1.5",
Locations: []ftypes.Location{
{
StartLine: 36,
EndLine: 39,
},
},
},
{
ID: "percent-encoding@1.0.1",
Name: "percent-encoding",
Version: "1.0.1",
Locations: []ftypes.Location{
{
StartLine: 46,
EndLine: 49,
},
},
},
}
cargoV3Pkgs = []ftypes.Package{
{
ID: "aho-corasick@0.7.20",
Name: "aho-corasick",
Version: "0.7.20",
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 12,
},
},
},
{
ID: "app@0.1.0",
Name: "app",
Version: "0.1.0",
Locations: []ftypes.Location{
{
StartLine: 14,
EndLine: 21,
},
},
},
{
ID: "libc@0.2.140",
Name: "libc",
Version: "0.2.140",
Locations: []ftypes.Location{
{
StartLine: 23,
EndLine: 27,
},
},
},
{
ID: "memchr@1.0.2",
Name: "memchr",
Version: "1.0.2",
Locations: []ftypes.Location{
{
StartLine: 29,
EndLine: 36,
},
},
},
{
ID: "memchr@2.5.0",
Name: "memchr",
Version: "2.5.0",
Locations: []ftypes.Location{
{
StartLine: 38,
EndLine: 42,
},
},
},
{
ID: "regex@1.7.3",
Name: "regex",
Version: "1.7.3",
Locations: []ftypes.Location{
{
StartLine: 44,
EndLine: 53,
},
},
},
{
ID: "regex-syntax@0.5.6",
Name: "regex-syntax",
Version: "0.5.6",
Locations: []ftypes.Location{
{
StartLine: 55,
EndLine: 62,
},
},
},
{
ID: "regex-syntax@0.6.29",
Name: "regex-syntax",
Version: "0.6.29",
Locations: []ftypes.Location{
{
StartLine: 64,
EndLine: 68,
},
},
},
{
ID: "ucd-util@0.1.10",
Name: "ucd-util",
Version: "0.1.10",
Locations: []ftypes.Location{
{
StartLine: 70,
EndLine: 74,
},
},
},
}
cargoV3Libs = []types.Library{
{ID: "aho-corasick@0.7.20", Name: "aho-corasick", Version: "0.7.20", Locations: []types.Location{{StartLine: 5, EndLine: 12}}},
{ID: "app@0.1.0", Name: "app", Version: "0.1.0", Locations: []types.Location{{StartLine: 14, EndLine: 21}}},
{ID: "libc@0.2.140", Name: "libc", Version: "0.2.140", Locations: []types.Location{{StartLine: 23, EndLine: 27}}},
{ID: "memchr@1.0.2", Name: "memchr", Version: "1.0.2", Locations: []types.Location{{StartLine: 29, EndLine: 36}}},
{ID: "memchr@2.5.0", Name: "memchr", Version: "2.5.0", Locations: []types.Location{{StartLine: 38, EndLine: 42}}},
{ID: "regex@1.7.3", Name: "regex", Version: "1.7.3", Locations: []types.Location{{StartLine: 44, EndLine: 53}}},
{ID: "regex-syntax@0.5.6", Name: "regex-syntax", Version: "0.5.6", Locations: []types.Location{{StartLine: 55, EndLine: 62}}},
{ID: "regex-syntax@0.6.29", Name: "regex-syntax", Version: "0.6.29", Locations: []types.Location{{StartLine: 64, EndLine: 68}}},
{ID: "ucd-util@0.1.10", Name: "ucd-util", Version: "0.1.10", Locations: []types.Location{{StartLine: 70, EndLine: 74}}},
}
cargoV3Deps = []types.Dependency{
cargoV3Deps = []ftypes.Dependency{
{
ID: "aho-corasick@0.7.20",
DependsOn: []string{"memchr@2.5.0"}},
DependsOn: []string{"memchr@2.5.0"},
},
{
ID: "app@0.1.0",
DependsOn: []string{"memchr@1.0.2", "regex-syntax@0.5.6", "regex@1.7.3"},
ID: "app@0.1.0",
DependsOn: []string{
"memchr@1.0.2",
"regex-syntax@0.5.6",
"regex@1.7.3",
},
},
{
ID: "memchr@1.0.2",
DependsOn: []string{"libc@0.2.140"},
},
{
ID: "regex@1.7.3",
DependsOn: []string{"aho-corasick@0.7.20", "memchr@2.5.0", "regex-syntax@0.6.29"},
ID: "regex@1.7.3",
DependsOn: []string{
"aho-corasick@0.7.20",
"memchr@2.5.0",
"regex-syntax@0.6.29",
},
},
{
ID: "regex-syntax@0.5.6",
@@ -87,25 +349,25 @@ var (
func TestParse(t *testing.T) {
vectors := []struct {
file string // Test input file
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
wantErr assert.ErrorAssertionFunc
}{
{
file: "testdata/cargo_normal.lock",
wantLibs: cargoNormalLibs,
wantPkgs: cargoNormalPkgs,
wantDeps: cargoNormalDeps,
wantErr: assert.NoError,
},
{
file: "testdata/cargo_mixed.lock",
wantLibs: cargoMixedLibs,
wantPkgs: cargoMixedPkgs,
wantDeps: cargoNormalDeps,
wantErr: assert.NoError,
},
{
file: "testdata/cargo_v3.lock",
wantLibs: cargoV3Libs,
wantPkgs: cargoV3Pkgs,
wantDeps: cargoV3Deps,
wantErr: assert.NoError,
},
@@ -120,7 +382,7 @@ func TestParse(t *testing.T) {
f, err := os.Open(v.file)
require.NoError(t, err)
gotLibs, gotDeps, err := NewParser().Parse(f)
gotPkgs, gotDeps, err := NewParser().Parse(f)
if !v.wantErr(t, err, fmt.Sprintf("Parse(%v)", v.file)) {
return
@@ -130,23 +392,10 @@ func TestParse(t *testing.T) {
return
}
sortLibs(v.wantLibs)
sortDeps(v.wantDeps)
assert.Equalf(t, v.wantLibs, gotLibs, "Parse libraries(%v)", v.file)
sort.Sort(ftypes.Packages(v.wantPkgs))
sort.Sort(ftypes.Dependencies(v.wantDeps))
assert.Equalf(t, v.wantPkgs, gotPkgs, "Parse libraries(%v)", v.file)
assert.Equalf(t, v.wantDeps, gotDeps, "Parse dependencies(%v)", v.file)
})
}
}
func sortLibs(libs []types.Library) {
sort.Slice(libs, func(i, j int) bool {
return strings.Compare(libs[i].ID, libs[j].ID) < 0
})
}
func sortDeps(deps []types.Dependency) {
sort.Slice(deps, func(i, j int) bool {
return strings.Compare(deps[i].ID, deps[j].ID) < 0
})
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -20,7 +19,7 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("cocoapods"),
}
@@ -30,32 +29,32 @@ type lockFile struct {
Pods []any `yaml:"PODS"` // pod can be string or map[string]interface{}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
lock := &lockFile{}
decoder := yaml.NewDecoder(r)
if err := decoder.Decode(&lock); err != nil {
return nil, nil, xerrors.Errorf("failed to decode cocoapods lock file: %s", err.Error())
}
parsedDeps := make(map[string]types.Library) // dependency name => Library
directDeps := make(map[string][]string) // dependency name => slice of child dependency names
parsedDeps := make(map[string]ftypes.Package) // dependency name => Package
directDeps := make(map[string][]string) // dependency name => slice of child dependency names
for _, pod := range lock.Pods {
switch dep := pod.(type) {
case string: // dependency with version number
lib, err := parseDep(dep)
pkg, err := parseDep(dep)
if err != nil {
p.logger.Debug("Dependency parse error", log.Err(err))
continue
}
parsedDeps[lib.Name] = lib
parsedDeps[pkg.Name] = pkg
case map[string]interface{}: // dependency with its child dependencies
for dep, childDeps := range dep {
lib, err := parseDep(dep)
pkg, err := parseDep(dep)
if err != nil {
p.logger.Debug("Dependency parse error", log.Err(err))
continue
}
parsedDeps[lib.Name] = lib
parsedDeps[pkg.Name] = pkg
children, ok := childDeps.([]interface{})
if !ok {
@@ -67,30 +66,30 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
if !ok {
return nil, nil, xerrors.Errorf("must be string: %q", childDep)
}
directDeps[lib.Name] = append(directDeps[lib.Name], strings.Fields(s)[0])
directDeps[pkg.Name] = append(directDeps[pkg.Name], strings.Fields(s)[0])
}
}
}
}
var deps []types.Dependency
var deps ftypes.Dependencies
for dep, childDeps := range directDeps {
var dependsOn []string
// find versions for child dependencies
for _, childDep := range childDeps {
dependsOn = append(dependsOn, packageID(childDep, parsedDeps[childDep].Version))
}
deps = append(deps, types.Dependency{
deps = append(deps, ftypes.Dependency{
ID: parsedDeps[dep].ID,
DependsOn: dependsOn,
})
}
sort.Sort(types.Dependencies(deps))
return utils.UniqueLibraries(maps.Values(parsedDeps)), deps, nil
sort.Sort(deps)
return utils.UniquePackages(maps.Values(parsedDeps)), deps, nil
}
func parseDep(dep string) (types.Library, error) {
func parseDep(dep string) (ftypes.Package, error) {
// dep example:
// 'AppCenter (4.2.0)'
// direct dep examples:
@@ -99,18 +98,18 @@ func parseDep(dep string) (types.Library, error) {
// 'AppCenter/Analytics (-> 4.2.0)'
ss := strings.Split(dep, " (")
if len(ss) != 2 {
return types.Library{}, xerrors.Errorf("Unable to determine cocoapods dependency: %q", dep)
return ftypes.Package{}, xerrors.Errorf("Unable to determine cocoapods dependency: %q", dep)
}
name := ss[0]
version := strings.Trim(strings.TrimSpace(ss[1]), "()")
lib := types.Library{
pkg := ftypes.Package{
ID: packageID(name, version),
Name: name,
Version: version,
}
return lib, nil
return pkg, nil
}
func packageID(name, version string) string {

View File

@@ -5,7 +5,7 @@ import (
"testing"
"github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -14,13 +14,18 @@ func TestParse(t *testing.T) {
tests := []struct {
name string
inputFile string // Test input file
wantLibs []types.Library
wantDeps []types.Dependency
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
}{
{
name: "happy path",
inputFile: "testdata/happy.lock",
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "AppCenter@4.2.0",
Name: "AppCenter",
Version: "4.2.0",
},
{
ID: "AppCenter/Analytics@4.2.0",
Name: "AppCenter/Analytics",
@@ -36,18 +41,13 @@ func TestParse(t *testing.T) {
Name: "AppCenter/Crashes",
Version: "4.2.0",
},
{
ID: "AppCenter@4.2.0",
Name: "AppCenter",
Version: "4.2.0",
},
{
ID: "KeychainAccess@4.2.1",
Name: "KeychainAccess",
Version: "4.2.1",
},
},
wantDeps: []types.Dependency{
wantDeps: []ftypes.Dependency{
{
ID: "AppCenter/Analytics@4.2.0",
DependsOn: []string{
@@ -85,10 +85,10 @@ func TestParse(t *testing.T) {
require.NoError(t, err)
defer f.Close()
gotLibs, gotDeps, err := cocoapods.NewParser().Parse(f)
gotPkgs, gotDeps, err := cocoapods.NewParser().Parse(f)
require.NoError(t, err)
assert.Equal(t, tt.wantLibs, gotLibs)
assert.Equal(t, tt.wantPkgs, gotPkgs)
assert.Equal(t, tt.wantDeps, gotDeps)
})
}

View File

@@ -10,7 +10,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -21,13 +20,13 @@ type Parser struct {
logger *log.Logger
}
func NewParser() types.Parser {
func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("swift"),
}
}
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
var lockFile LockFile
input, err := io.ReadAll(r)
if err != nil {
@@ -37,13 +36,13 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return nil, nil, xerrors.Errorf("decode error: %w", err)
}
var libs types.Libraries
var pkgs ftypes.Packages
pins := lockFile.Object.Pins
if lockFile.Version > 1 {
pins = lockFile.Pins
}
for _, pin := range pins {
name := libraryName(pin, lockFile.Version)
name := pkgName(pin, lockFile.Version)
// Skip packages for which we cannot resolve the version
if pin.State.Version == "" && pin.State.Branch == "" {
@@ -55,11 +54,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
// e.g. https://github.com/element-hq/element-ios/blob/6a9bcc88ea37147efba8f0a7bcf3ec187f4a4011/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved#L84-L92
version := lo.Ternary(pin.State.Version != "", pin.State.Version, pin.State.Branch)
libs = append(libs, types.Library{
pkgs = append(pkgs, ftypes.Package{
ID: dependency.ID(ftypes.Swift, name, version),
Name: name,
Version: version,
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: pin.StartLine,
EndLine: pin.EndLine,
@@ -67,11 +66,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
},
})
}
sort.Sort(libs)
return libs, nil, nil
sort.Sort(pkgs)
return pkgs, nil, nil
}
func libraryName(pin Pin, lockVersion int) string {
func pkgName(pin Pin, lockVersion int) string {
// Package.resolved v1 uses `RepositoryURL`
// v2 uses `Location`
name := pin.RepositoryURL

View File

@@ -1,7 +1,7 @@
package swift
import (
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"os"
"testing"
@@ -11,7 +11,7 @@ func TestParser_Parse(t *testing.T) {
tests := []struct {
name string
inputFile string
want []types.Library
want []ftypes.Package
}{
// docker run -it --rm swift@sha256:3c62ac97506ecf19ca15e4db57d7930e6a71559b23b19aa57e13d380133a54db
// mkdir app && cd app
@@ -22,60 +22,60 @@ func TestParser_Parse(t *testing.T) {
{
name: "happy path v1",
inputFile: "testdata/happy-v1-Package.resolved",
want: []types.Library{
want: []ftypes.Package{
{
ID: "github.com/Quick/Nimble@9.2.1",
Name: "github.com/Quick/Nimble",
Version: "9.2.1",
Locations: []types.Location{{StartLine: 4, EndLine: 12}},
Locations: []ftypes.Location{{StartLine: 4, EndLine: 12}},
},
{
ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1",
Name: "github.com/ReactiveCocoa/ReactiveSwift",
Version: "7.1.1",
Locations: []types.Location{{StartLine: 13, EndLine: 21}},
Locations: []ftypes.Location{{StartLine: 13, EndLine: 21}},
},
},
},
{
name: "happy path v2",
inputFile: "testdata/happy-v2-Package.resolved",
want: []types.Library{
want: []ftypes.Package{
{
ID: "github.com/Quick/Nimble@9.2.1",
Name: "github.com/Quick/Nimble",
Version: "9.2.1",
Locations: []types.Location{{StartLine: 21, EndLine: 29}},
Locations: []ftypes.Location{{StartLine: 21, EndLine: 29}},
},
{
ID: "github.com/Quick/Quick@7.2.0",
Name: "github.com/Quick/Quick",
Version: "7.2.0",
Locations: []types.Location{{StartLine: 30, EndLine: 38}},
Locations: []ftypes.Location{{StartLine: 30, EndLine: 38}},
},
{
ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1",
Name: "github.com/ReactiveCocoa/ReactiveSwift",
Version: "7.1.1",
Locations: []types.Location{{StartLine: 39, EndLine: 47}},
Locations: []ftypes.Location{{StartLine: 39, EndLine: 47}},
},
{
ID: "github.com/element-hq/swift-ogg@0.0.1",
Name: "github.com/element-hq/swift-ogg",
Version: "0.0.1",
Locations: []types.Location{{StartLine: 48, EndLine: 56}},
Locations: []ftypes.Location{{StartLine: 48, EndLine: 56}},
},
{
ID: "github.com/mattgallagher/CwlCatchException@2.1.2",
Name: "github.com/mattgallagher/CwlCatchException",
Version: "2.1.2",
Locations: []types.Location{{StartLine: 3, EndLine: 11}},
Locations: []ftypes.Location{{StartLine: 3, EndLine: 11}},
},
{
ID: "github.com/mattgallagher/CwlPreconditionTesting@2.1.2",
Name: "github.com/mattgallagher/CwlPreconditionTesting",
Version: "2.1.2",
Locations: []types.Location{{StartLine: 12, EndLine: 20}},
Locations: []ftypes.Location{{StartLine: 12, EndLine: 20}},
},
},
},
@@ -92,9 +92,9 @@ func TestParser_Parse(t *testing.T) {
f, err := os.Open(tt.inputFile)
assert.NoError(t, err)
libs, _, err := parser.Parse(f)
gotPkgs, _, err := parser.Parse(f)
assert.NoError(t, err)
assert.Equal(t, tt.want, libs)
assert.Equal(t, tt.want, gotPkgs)
})
}
}

View File

@@ -6,7 +6,7 @@ import (
"golang.org/x/exp/maps"
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
func UniqueStrings(ss []string) []string {
@@ -22,36 +22,36 @@ func UniqueStrings(ss []string) []string {
return results
}
func UniqueLibraries(libs []types.Library) []types.Library {
if len(libs) == 0 {
func UniquePackages(pkgs []ftypes.Package) []ftypes.Package {
if len(pkgs) == 0 {
return nil
}
unique := make(map[string]types.Library)
for _, lib := range libs {
identifier := fmt.Sprintf("%s@%s", lib.Name, lib.Version)
unique := make(map[string]ftypes.Package)
for _, pkg := range pkgs {
identifier := fmt.Sprintf("%s@%s", pkg.Name, pkg.Version)
if l, ok := unique[identifier]; !ok {
unique[identifier] = lib
unique[identifier] = pkg
} else {
// There are times when we get 2 same libraries as root and dev dependencies.
// There are times when we get 2 same packages as root and dev dependencies.
// https://github.com/aquasecurity/trivy/issues/5532
// In these cases, we need to mark the dependency as a root dependency.
if !lib.Dev {
l.Dev = lib.Dev
if !pkg.Dev {
l.Dev = pkg.Dev
unique[identifier] = l
}
if len(lib.Locations) > 0 {
if len(pkg.Locations) > 0 {
// merge locations
l.Locations = append(l.Locations, lib.Locations...)
l.Locations = append(l.Locations, pkg.Locations...)
sort.Sort(l.Locations)
unique[identifier] = l
}
}
}
libSlice := maps.Values(unique)
sort.Sort(types.Libraries(libSlice))
pkgSlice := maps.Values(unique)
sort.Sort(ftypes.Packages(pkgSlice))
return libSlice
return pkgSlice
}
func MergeMaps(parent, child map[string]string) map[string]string {

View File

@@ -1,7 +1,7 @@
package utils
import (
"github.com/aquasecurity/trivy/pkg/dependency/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/require"
"testing"
)
@@ -9,17 +9,17 @@ import (
func TestUniqueLibraries(t *testing.T) {
tests := []struct {
name string
libs []types.Library
wantLibs []types.Library
pkgs []ftypes.Package
wantPkgs []ftypes.Package
}{
{
name: "happy path merge locations",
libs: []types.Library{
pkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
Version: "0.2.6",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 10,
EndLine: 14,
@@ -30,7 +30,7 @@ func TestUniqueLibraries(t *testing.T) {
ID: "asn1@0.2.6",
Name: "asn1",
Version: "0.2.6",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 24,
EndLine: 30,
@@ -38,12 +38,12 @@ func TestUniqueLibraries(t *testing.T) {
},
},
},
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
Version: "0.2.6",
Locations: []types.Location{
Locations: []ftypes.Location{
{
StartLine: 10,
EndLine: 14,
@@ -58,7 +58,7 @@ func TestUniqueLibraries(t *testing.T) {
},
{
name: "happy path Dev and Root deps",
libs: []types.Library{
pkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
@@ -72,7 +72,7 @@ func TestUniqueLibraries(t *testing.T) {
Dev: false,
},
},
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
@@ -83,7 +83,7 @@ func TestUniqueLibraries(t *testing.T) {
},
{
name: "happy path Root and Dev deps",
libs: []types.Library{
pkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
@@ -97,7 +97,7 @@ func TestUniqueLibraries(t *testing.T) {
Dev: true,
},
},
wantLibs: []types.Library{
wantPkgs: []ftypes.Package{
{
ID: "asn1@0.2.6",
Name: "asn1",
@@ -110,8 +110,8 @@ func TestUniqueLibraries(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotLibs := UniqueLibraries(tt.libs)
require.Equal(t, tt.wantLibs, gotLibs)
gotPkgs := UniquePackages(tt.pkgs)
require.Equal(t, tt.wantPkgs, gotPkgs)
})
}
}

View File

@@ -1,127 +0,0 @@
package types
import (
"encoding/json"
"golang.org/x/xerrors"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type Library struct {
ID string `json:",omitempty"`
Name string
Version string
Dev bool `json:",omitempty"`
Relationship Relationship `json:",omitempty"`
License string `json:",omitempty"`
ExternalReferences []ExternalRef `json:",omitempty"`
Locations Locations `json:",omitempty"`
FilePath string `json:",omitempty"` // Required to show nested jars
}
type Libraries []Library
func (libs Libraries) Len() int { return len(libs) }
func (libs Libraries) Less(i, j int) bool {
switch {
case libs[i].Relationship != libs[j].Relationship:
if libs[i].Relationship == RelationshipUnknown {
return false
} else if libs[j].Relationship == RelationshipUnknown {
return true
}
return libs[i].Relationship < libs[j].Relationship
case libs[i].ID != libs[j].ID: // ID could be empty
return libs[i].ID < libs[j].ID
case libs[i].Name != libs[j].Name: // Name could be the same
return libs[i].Name < libs[j].Name
}
return libs[i].Version < libs[j].Version
}
func (libs Libraries) Swap(i, j int) { libs[i], libs[j] = libs[j], libs[i] }
// Location in lock file
type Location struct {
StartLine int `json:",omitempty"`
EndLine int `json:",omitempty"`
}
type Locations []Location
func (locs Locations) Len() int { return len(locs) }
func (locs Locations) Less(i, j int) bool {
return locs[i].StartLine < locs[j].StartLine
}
func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] }
type ExternalRef struct {
Type RefType
URL string
}
type Dependency struct {
ID string
DependsOn []string
}
type Dependencies []Dependency
func (deps Dependencies) Len() int { return len(deps) }
func (deps Dependencies) Less(i, j int) bool {
return deps[i].ID < deps[j].ID
}
func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] }
type Parser interface {
// Parse parses the dependency file
Parse(r xio.ReadSeekerAt) ([]Library, []Dependency, error)
}
type RefType string
const (
RefVCS RefType = "vcs"
RefOther RefType = "other"
)
type Relationship int
const (
RelationshipUnknown Relationship = iota
RelationshipRoot
RelationshipDirect
RelationshipIndirect
)
var relationshipNames = [...]string{
"unknown",
"root",
"direct",
"indirect",
}
func (r Relationship) String() string {
if r <= RelationshipUnknown || int(r) >= len(relationshipNames) {
return "unknown"
}
return relationshipNames[r]
}
func (r Relationship) MarshalJSON() ([]byte, error) {
return json.Marshal(r.String())
}
func (r *Relationship) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
for i, name := range relationshipNames {
if s == name {
*r = Relationship(i)
return nil
}
}
return xerrors.Errorf("invalid relationship (%s)", s)
}

View File

@@ -202,7 +202,7 @@ func (r *AnalysisResult) Sort() {
})
for _, app := range r.Applications {
sort.Sort(app.Libraries)
sort.Sort(app.Packages)
}
// Custom resources
@@ -475,12 +475,12 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF
skippedFiles := result.SystemInstalledFiles
for _, app := range result.Applications {
skippedFiles = append(skippedFiles, app.FilePath)
for _, lib := range app.Libraries {
for _, pkg := range app.Packages {
// The analysis result could contain packages listed in SBOM.
// The files of those packages don't have to be analyzed.
// This is especially helpful for expensive post-analyzers such as the JAR analyzer.
if lib.FilePath != "" {
skippedFiles = append(skippedFiles, lib.FilePath)
if pkg.FilePath != "" {
skippedFiles = append(skippedFiles, pkg.FilePath)
}
}
}

View File

@@ -69,7 +69,7 @@ func TestAnalysisResult_Merge(t *testing.T) {
{
Type: "bundler",
FilePath: "app/Gemfile.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "rails",
Version: "5.0.0",
@@ -95,7 +95,7 @@ func TestAnalysisResult_Merge(t *testing.T) {
{
Type: "bundler",
FilePath: "app2/Gemfile.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "nokogiri",
Version: "1.0.0",
@@ -134,7 +134,7 @@ func TestAnalysisResult_Merge(t *testing.T) {
{
Type: "bundler",
FilePath: "app/Gemfile.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "rails",
Version: "5.0.0",
@@ -144,7 +144,7 @@ func TestAnalysisResult_Merge(t *testing.T) {
{
Type: "bundler",
FilePath: "app2/Gemfile.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "nokogiri",
Version: "1.0.0",
@@ -378,7 +378,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) {
{
Type: "bundler",
FilePath: "/app/Gemfile.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
ID: "actioncable@5.2.3",
Name: "actioncable",
@@ -441,7 +441,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) {
{
Type: "bundler",
FilePath: "/app/Gemfile-dev.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
ID: "actioncable@5.2.3",
Name: "actioncable",
@@ -576,7 +576,7 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) {
{
Type: types.Jar,
FilePath: "testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "com.fasterxml.jackson.core:jackson-annotations",
Version: "2.15.0-rc2",
@@ -596,7 +596,7 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) {
{
Type: types.Poetry,
FilePath: "testdata/post-apps/poetry/happy/poetry.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
ID: "certifi@2022.12.7",
Name: "certifi",

View File

@@ -2,11 +2,9 @@ package language
import (
"io"
"strings"
"golang.org/x/xerrors"
godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types"
"github.com/aquasecurity/trivy/pkg/digest"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -15,8 +13,13 @@ import (
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
type Parser interface {
// Parse parses the dependency file
Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error)
}
// Analyze returns an analysis result of the lock file
func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser) (*analyzer.AnalysisResult, error) {
func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser) (*analyzer.AnalysisResult, error) {
app, err := Parse(fileType, filePath, r, parser)
if err != nil {
return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
@@ -30,7 +33,7 @@ func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parse
}
// AnalyzePackage returns an analysis result of the package file other than lock files
func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*analyzer.AnalysisResult, error) {
func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser, checksum bool) (*analyzer.AnalysisResult, error) {
app, err := ParsePackage(fileType, filePath, r, parser, checksum)
if err != nil {
return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
@@ -44,24 +47,24 @@ func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt
}
// Parse returns a parsed result of the lock file
func Parse(fileType types.LangType, filePath string, r io.Reader, parser godeptypes.Parser) (*types.Application, error) {
func Parse(fileType types.LangType, filePath string, r io.Reader, parser Parser) (*types.Application, error) {
rr, err := xio.NewReadSeekerAt(r)
if err != nil {
return nil, xerrors.Errorf("reader error: %w", err)
}
parsedLibs, parsedDependencies, err := parser.Parse(rr)
parsedPkgs, parsedDependencies, err := parser.Parse(rr)
if err != nil {
return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
}
// The file path of each library should be empty in case of dependency list such as lock file
// since they all will be the same path.
return toApplication(fileType, filePath, "", nil, parsedLibs, parsedDependencies), nil
return toApplication(fileType, filePath, "", nil, parsedPkgs, parsedDependencies), nil
}
// ParsePackage returns a parsed result of the package file
func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*types.Application, error) {
parsedLibs, parsedDependencies, err := parser.Parse(r)
func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser, checksum bool) (*types.Application, error) {
parsedPkgs, parsedDependencies, err := parser.Parse(r)
if err != nil {
return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err)
}
@@ -73,11 +76,11 @@ func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt,
// The file path of each library should be empty in case of dependency list such as lock file
// since they all will be the same path.
return toApplication(fileType, filePath, filePath, r, parsedLibs, parsedDependencies), nil
return toApplication(fileType, filePath, filePath, r, parsedPkgs, parsedDependencies), nil
}
func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.ReadSeekerAt, libs []godeptypes.Library, depGraph []godeptypes.Dependency) *types.Application {
if len(libs) == 0 {
func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.ReadSeekerAt, pkgs []types.Package, depGraph []types.Dependency) *types.Application {
if len(pkgs) == 0 {
return nil
}
@@ -92,50 +95,24 @@ func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.
deps[dep.ID] = dep.DependsOn
}
var pkgs []types.Package
for _, lib := range libs {
var licenses []string
if lib.License != "" {
licenses = licensing.SplitLicenses(lib.License)
for i, license := range licenses {
licenses[i] = licensing.Normalize(strings.TrimSpace(license))
}
}
var locs []types.Location
for _, loc := range lib.Locations {
l := types.Location{
StartLine: loc.StartLine,
EndLine: loc.EndLine,
}
locs = append(locs, l)
}
for i, pkg := range pkgs {
// This file path is populated for virtual file paths within archives, such as nested JAR files.
libPath := libFilePath
if lib.FilePath != "" {
libPath = lib.FilePath
if pkg.FilePath == "" {
pkgs[i].FilePath = libFilePath
}
pkgs[i].DependsOn = deps[pkg.ID]
pkgs[i].Digest = d
pkgs[i].Indirect = isIndirect(pkg.Relationship) // For backward compatibility
newPkg := types.Package{
ID: lib.ID,
Name: lib.Name,
Version: lib.Version,
Dev: lib.Dev,
FilePath: libPath,
Indirect: isIndirect(lib.Relationship), // For backward compatibility
Relationship: lib.Relationship,
Licenses: licenses,
DependsOn: deps[lib.ID],
Locations: locs,
Digest: d,
for j, license := range pkg.Licenses {
pkgs[i].Licenses[j] = licensing.Normalize(license)
}
pkgs = append(pkgs, newPkg)
}
return &types.Application{
Type: fileType,
FilePath: filePath,
Libraries: pkgs,
Type: fileType,
FilePath: filePath,
Packages: pkgs,
}
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
"github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -20,13 +19,13 @@ type mockParser struct {
t *testing.T
}
func (p *mockParser) Parse(r xio.ReadSeekerAt) ([]godeptypes.Library, []godeptypes.Dependency, error) {
func (p *mockParser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) {
b, err := io.ReadAll(r)
require.NoError(p.t, err)
switch string(b) {
case "happy":
return []godeptypes.Library{
return []types.Package{
{
Name: "test",
Version: "1.2.3",
@@ -63,7 +62,7 @@ func TestAnalyze(t *testing.T) {
{
Type: types.GoBinary,
FilePath: "app/myweb",
Libraries: types.Packages{
Packages: types.Packages{
{
Name: "test",
Version: "1.2.3",

View File

@@ -14,7 +14,6 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan"
godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
"github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -33,7 +32,7 @@ const (
// conanLockAnalyzer analyzes conan.lock
type conanLockAnalyzer struct {
logger *log.Logger
parser godeptypes.Parser
parser language.Parser
}
func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) {
@@ -65,15 +64,15 @@ func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna
}
// Fill licenses
for i, lib := range app.Libraries {
for i, lib := range app.Packages {
if license, ok := licenses[lib.Name]; ok {
app.Libraries[i].Licenses = []string{
app.Packages[i].Licenses = []string{
license,
}
}
}
sort.Sort(app.Libraries)
sort.Sort(app.Packages)
apps = append(apps, *app)
return nil
}); err != nil {

View File

@@ -27,7 +27,7 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
{
Type: types.Conan,
FilePath: "conan.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
ID: "openssl/3.0.5",
Name: "openssl",
@@ -70,7 +70,7 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) {
{
Type: types.Conan,
FilePath: "conan.lock",
Libraries: types.Packages{
Packages: types.Packages{
{
ID: "openssl/3.0.5",
Name: "openssl",

Some files were not shown because too many files have changed in this diff Show More