mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 06:43:05 -08:00
feat: add support for WASM modules (#2195)
This commit is contained in:
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: v1.4.1
|
||||
args: release --rm-dist
|
||||
args: release --rm-dist --timeout 60m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }}
|
||||
- name: Checkout trivy-repo
|
||||
|
||||
31
.github/workflows/test.yaml
vendored
31
.github/workflows/test.yaml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
pull_request:
|
||||
env:
|
||||
GO_VERSION: "1.18"
|
||||
TINYGO_VERSION: "0.23.0"
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
@@ -35,6 +36,12 @@ jobs:
|
||||
with:
|
||||
version: v1.45
|
||||
args: --deadline=30m
|
||||
skip-cache: true # https://github.com/golangci/golangci-lint-action/issues/244#issuecomment-1052197778
|
||||
|
||||
- name: Install TinyGo
|
||||
run: |
|
||||
wget https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VERSION}/tinygo_${TINYGO_VERSION}_amd64.deb
|
||||
sudo dpkg -i tinygo_${TINYGO_VERSION}_amd64.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: make test
|
||||
@@ -55,6 +62,28 @@ jobs:
|
||||
- name: Run integration tests
|
||||
run: make test-integration
|
||||
|
||||
module-test:
|
||||
name: Module Integration Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
- name: Install TinyGo
|
||||
run: |
|
||||
wget https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VERSION}/tinygo_${TINYGO_VERSION}_amd64.deb
|
||||
sudo dpkg -i tinygo_${TINYGO_VERSION}_amd64.deb
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run module integration tests
|
||||
run: |
|
||||
make test-module-integration
|
||||
|
||||
build-test:
|
||||
name: Build Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -83,7 +112,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: v1.4.1
|
||||
args: release --snapshot --rm-dist --skip-publish
|
||||
args: release --snapshot --rm-dist --skip-publish --timeout 60m
|
||||
|
||||
build-documents:
|
||||
name: Documentation Test
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,3 +30,6 @@ integration/testdata/fixtures/images
|
||||
|
||||
# goreleaser output
|
||||
dist
|
||||
|
||||
# WebAssembly
|
||||
*.wasm
|
||||
|
||||
@@ -49,6 +49,7 @@ run:
|
||||
- ".*._mock.go$"
|
||||
- ".*._test.go$"
|
||||
- "integration/*"
|
||||
- "examples/*"
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
|
||||
49
Makefile
49
Makefile
@@ -1,15 +1,24 @@
|
||||
VERSION := $(shell git describe --tags)
|
||||
LDFLAGS=-ldflags "-s -w -X=main.version=$(VERSION)"
|
||||
LDFLAGS := -ldflags "-s -w -X=main.version=$(VERSION)"
|
||||
|
||||
GOPATH=$(shell go env GOPATH)
|
||||
GOBIN=$(GOPATH)/bin
|
||||
GOSRC=$(GOPATH)/src
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
GOBIN := $(GOPATH)/bin
|
||||
GOSRC := $(GOPATH)/src
|
||||
|
||||
TEST_MODULE_DIR := pkg/module/testdata
|
||||
TEST_MODULE_SRCS := $(wildcard $(TEST_MODULE_DIR)/*/*.go)
|
||||
TEST_MODULES := $(patsubst %.go,%.wasm,$(TEST_MODULE_SRCS))
|
||||
|
||||
EXAMPLE_MODULE_DIR := examples/module
|
||||
EXAMPLE_MODULE_SRCS := $(wildcard $(EXAMPLE_MODULE_DIR)/*/*.go)
|
||||
EXAMPLE_MODULES := $(patsubst %.go,%.wasm,$(EXAMPLE_MODULE_SRCS))
|
||||
|
||||
MKDOCS_IMAGE := aquasec/mkdocs-material:dev
|
||||
MKDOCS_PORT := 8000
|
||||
|
||||
u := $(if $(update),-u)
|
||||
|
||||
# Tools
|
||||
$(GOBIN)/wire:
|
||||
go install github.com/google/wire/cmd/wire@v0.5.0
|
||||
|
||||
@@ -22,9 +31,12 @@ $(GOBIN)/golangci-lint:
|
||||
$(GOBIN)/labeler:
|
||||
go install github.com/knqyf263/labeler@latest
|
||||
|
||||
$(GOBIN)/easyjson:
|
||||
go install github.com/mailru/easyjson/...@v0.7.7
|
||||
|
||||
.PHONY: wire
|
||||
wire: $(GOBIN)/wire
|
||||
wire gen ./pkg/...
|
||||
wire gen ./pkg/commands/... ./pkg/rpc/...
|
||||
|
||||
.PHONY: mock
|
||||
mock: $(GOBIN)/mockery
|
||||
@@ -35,19 +47,36 @@ deps:
|
||||
go get ${u} -d
|
||||
go mod tidy
|
||||
|
||||
.PHONY: generate-test-modules
|
||||
generate-test-modules: $(TEST_MODULES)
|
||||
|
||||
# Compile WASM modules for unit and integration tests
|
||||
%.wasm:%.go
|
||||
@if !(type "tinygo" > /dev/null 2>&1); then \
|
||||
echo "Need to install TinyGo. Follow https://tinygo.org/getting-started/install/"; \
|
||||
exit 1; \
|
||||
fi
|
||||
go generate $<
|
||||
|
||||
# Run unit tests
|
||||
.PHONY: test
|
||||
test:
|
||||
test: $(TEST_MODULES)
|
||||
go test -v -short -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
integration/testdata/fixtures/images/*.tar.gz: $(GOBIN)/crane
|
||||
mkdir -p integration/testdata/fixtures/images/
|
||||
integration/scripts/download-images.sh
|
||||
|
||||
# Run integration tests
|
||||
.PHONY: test-integration
|
||||
test-integration: integration/testdata/fixtures/images/*.tar.gz
|
||||
go test -v -tags=integration ./integration/...
|
||||
|
||||
# Run WASM integration tests
|
||||
.PHONY: test-module-integration
|
||||
test-module-integration: integration/testdata/fixtures/images/*.tar.gz $(EXAMPLE_MODULES)
|
||||
go test -v -tags=module_integration ./integration/...
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOBIN)/golangci-lint
|
||||
$(GOBIN)/golangci-lint run --timeout 5m
|
||||
@@ -78,12 +107,18 @@ install:
|
||||
clean:
|
||||
rm -rf integration/testdata/fixtures/images
|
||||
|
||||
# Create labels on GitHub
|
||||
.PHONY: label
|
||||
label: $(GOBIN)/labeler
|
||||
labeler apply misc/triage/labels.yaml -r aquasecurity/trivy -l 5
|
||||
|
||||
# Run MkDocs development server to preview the documentation page
|
||||
.PHONY: mkdocs-serve
|
||||
## Runs MkDocs development server to preview the documentation page
|
||||
mkdocs-serve:
|
||||
docker build -t $(MKDOCS_IMAGE) -f docs/build/Dockerfile docs/build
|
||||
docker run --name mkdocs-serve --rm -v $(PWD):/docs -p $(MKDOCS_PORT):8000 $(MKDOCS_IMAGE)
|
||||
|
||||
# Generate JSON marshaler/unmarshaler for TinyGo/WebAssembly as TinyGo doesn't support encoding/json.
|
||||
.PHONY: easyjson
|
||||
easyjson: $(GOBIN)/easyjson
|
||||
easyjson pkg/module/serialize/types.go
|
||||
|
||||
351
docs/docs/advanced/modules.md
Normal file
351
docs/docs/advanced/modules.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# Modules
|
||||
|
||||
!!! warning "EXPERIMENTAL"
|
||||
This feature might change without preserving backwards compatibility.
|
||||
|
||||
Trivy provides a module feature to allow others to extend the Trivy CLI without the need to change the Trivy code base.
|
||||
It changes the behavior during scanning by WebAssembly.
|
||||
|
||||
## Overview
|
||||
Trivy modules are add-on tools that integrate seamlessly with Trivy.
|
||||
They provide a way to extend the core feature set of Trivy, but without updating the Trivy binary.
|
||||
|
||||
- They can be added and removed from a Trivy installation without impacting the core Trivy tool.
|
||||
- They can be written in any programming language supporting WebAssembly.
|
||||
- It supports only [TinyGo][tinygo] at the moment.
|
||||
|
||||
You can write your own detection logic.
|
||||
|
||||
- Evaluate complex vulnerability conditions like [Spring4Shell][spring4shell]
|
||||
- Detect a shell script communicating with malicious domains
|
||||
- Detect malicious python install script (setup.py)
|
||||
- Even detect misconfigurations in WordPress setting
|
||||
- etc.
|
||||
|
||||
Then, you can update the scan result however you want.
|
||||
|
||||
- Change a severity
|
||||
- Remove a vulnerability
|
||||
- Add a new vulnerability
|
||||
- etc.
|
||||
|
||||
Modules should be distributed in OCI registries like GitHub Container Registry.
|
||||
|
||||
!!! warning
|
||||
WebAssembly doesn't allow file access and network access by default.
|
||||
Modules can read required files only, but cannot overwrite them.
|
||||
WebAssembly is sandboxed and secure by design, but Trivy modules available in public are not audited for security.
|
||||
You should install and run third-party modules at your own risk even though
|
||||
|
||||
Under the hood Trivy leverages [wazero][wazero] to run WebAssembly modules without CGO.
|
||||
|
||||
## Installing a Module
|
||||
A module can be installed using the `trivy module install` command.
|
||||
This command takes an url. It will download the module and install it in the module cache.
|
||||
|
||||
Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set.
|
||||
Trivy will now search XDG_DATA_HOME for the location of the Trivy modules cache.
|
||||
The preference order is as follows:
|
||||
|
||||
- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir
|
||||
- $HOME/.trivy/plugins
|
||||
|
||||
For example, to download the WebAssembly module, you can execute the following command:
|
||||
|
||||
```bash
|
||||
$ trivy module install ghcr.io/aquasecurity/trivy-module-spring4shell
|
||||
```
|
||||
|
||||
## Using Modules
|
||||
Once the module is installed, Trivy will load all available modules in the cache on the start of the next Trivy execution.
|
||||
The modules may inject custom logic into scanning and change the result.
|
||||
You can run Trivy as usual and modules are loaded automatically.
|
||||
|
||||
You will see the log messages about WASM modules.
|
||||
|
||||
```shell
|
||||
$ trivy image ghcr.io/aquasecurity/trivy-test-images:spring4shell-jre8
|
||||
2022-06-12T12:57:13.210+0300 INFO Loading ghcr.io/aquasecurity/trivy-module-spring4shell/spring4shell.wasm...
|
||||
2022-06-12T12:57:13.596+0300 INFO Registering WASM module: spring4shell@v1
|
||||
...
|
||||
2022-06-12T12:57:14.865+0300 INFO Module spring4shell: Java Version: 8, Tomcat Version: 8.5.77
|
||||
2022-06-12T12:57:14.865+0300 INFO Module spring4shell: change CVE-2022-22965 severity from CRITICAL to LOW
|
||||
|
||||
Java (jar)
|
||||
|
||||
Total: 9 (UNKNOWN: 1, LOW: 3, MEDIUM: 2, HIGH: 3, CRITICAL: 0)
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┬─────────────────────┬──────────┬───────────────────┬────────────────────────┬────────────────────────────────────────────────────────────┐
|
||||
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
|
||||
├──────────────────────────────────────────────────────────────┼─────────────────────┼──────────┼───────────────────┼────────────────────────┼────────────────────────────────────────────────────────────┤
|
||||
│ org.springframework.boot:spring-boot (helloworld.war) │ CVE-2022-22965 │ LOW │ 2.6.3 │ 2.5.12, 2.6.6 │ spring-framework: RCE via Data Binding on JDK 9+ │
|
||||
│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-22965 │
|
||||
├──────────────────────────────────────────────────────────────┼─────────────────────┼──────────┼───────────────────┼────────────────────────┼────────────────────────────────────────────────────────────┤
|
||||
...(snip)...
|
||||
```
|
||||
|
||||
In the above example, the Spring4Shell module changed the severity from CRITICAL to LOW because the application doesn't satisfy one of conditions.
|
||||
|
||||
## Uninstalling Modules
|
||||
Specify a module repository with `trivy module uninstall` command.
|
||||
|
||||
```bash
|
||||
$ trivy module uninstall ghcr.io/aquasecurity/trivy-module-spring4shell
|
||||
```
|
||||
|
||||
## Building Modules
|
||||
It supports TinyGo only at the moment.
|
||||
|
||||
### TinyGo
|
||||
Trivy provides Go SDK including three interfaces.
|
||||
Your own module needs to implement either or both `Analyzer` and `PostScanner` in addition to `Module`.
|
||||
|
||||
```go
|
||||
type Module interface {
|
||||
Version() int
|
||||
Name() string
|
||||
}
|
||||
|
||||
type Analyzer interface {
|
||||
RequiredFiles() []string
|
||||
Analyze(filePath string) (*serialize.AnalysisResult, error)
|
||||
}
|
||||
|
||||
type PostScanner interface {
|
||||
PostScanSpec() serialize.PostScanSpec
|
||||
PostScan(serialize.Results) (serialize.Results, error)
|
||||
}
|
||||
```
|
||||
|
||||
In the following tutorial, it creates a WordPress module that detects a WordPress version and a critical vulnerability accordingly.
|
||||
|
||||
!!! tips
|
||||
You can use logging functions such as `Debug` and `Info` for debugging.
|
||||
See [examples](#examples) for the detail.
|
||||
|
||||
#### Module interface
|
||||
`Version()` returns your module version and should be incremented after updates.
|
||||
`Name()` returns your module name.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
const (
|
||||
version = 1
|
||||
name = "wordpress-module"
|
||||
)
|
||||
|
||||
type WordpressModule struct{
|
||||
// Cannot define fields as modules can't keep state.
|
||||
}
|
||||
|
||||
func (WordpressModule) Version() int {
|
||||
return version
|
||||
}
|
||||
|
||||
func (WordpressModule) Name() string {
|
||||
return name
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
A struct cannot have any fields. Each method invocation is performed in different states.
|
||||
|
||||
#### Analyzer interface
|
||||
If you implement the `Analyzer` interface, `Analyze` method is called when the file path is matched to file patterns returned by `RequiredFiles()`.
|
||||
A file pattern must be a regular expression. The syntax detail is [here][regexp].
|
||||
|
||||
`Analyze` takes the matched file path, then the file can be opened by `os.Open()`.
|
||||
|
||||
```go
|
||||
const typeWPVersion = "wordpress-version"
|
||||
|
||||
func (WordpressModule) RequiredFiles() []string {
|
||||
return []string{
|
||||
`wp-includes\/version.php`,
|
||||
}
|
||||
}
|
||||
|
||||
func (WordpressModule) Analyze(filePath string) (*serialize.AnalysisResult, error) {
|
||||
f, err := os.Open(filePath) // e.g. filePath: /usr/src/wordpress/wp-includes/version.php
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var wpVersion string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "$wp_version=") {
|
||||
continue
|
||||
}
|
||||
|
||||
ss := strings.Split(line, "=")
|
||||
if len(ss) != 2 {
|
||||
return nil, fmt.Errorf("invalid wordpress version: %s", line)
|
||||
}
|
||||
|
||||
// NOTE: it is an example; you actually need to handle comments, etc
|
||||
ss[1] = strings.TrimSpace(ss[1])
|
||||
wpVersion = strings.Trim(ss[1], `";`)
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serialize.AnalysisResult{
|
||||
CustomResources: []serialize.CustomResource{
|
||||
{
|
||||
Type: typeWPVersion,
|
||||
FilePath: filePath,
|
||||
Data: wpVersion,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
!!! tips
|
||||
Trivy caches analysis results according to the module version.
|
||||
We'd recommend cleaning the cache or changing the module version every time you update `Analyzer`.
|
||||
|
||||
|
||||
#### PostScanner interface
|
||||
`PostScan` is called after scanning and takes the scan result as an argument from Trivy.
|
||||
In post scanning, your module can perform one of three actions:
|
||||
|
||||
- Insert
|
||||
- Add a new security finding
|
||||
- e.g. Add a new vulnerability and misconfiguration
|
||||
- Update
|
||||
- Update the detected vulnerability and misconfiguration
|
||||
- e.g. Change a severity
|
||||
- Delete
|
||||
- Delete the detected vulnerability and misconfiguration
|
||||
- e.g. Remove Spring4Shell because it is not actually affected.
|
||||
|
||||
`PostScanSpec()` returns which action the module does.
|
||||
If it is `Update` or `Delete`, it also needs to return IDs such as CVE-ID and misconfiguration ID, which your module wants to update or delete.
|
||||
|
||||
`serialize.Results` contains the filtered results matching IDs you specified.
|
||||
Also, it includes `CustomResources` with the values your `Analyze` returns, so you can modify the scan result according to the custom resources.
|
||||
|
||||
```go
|
||||
func (WordpressModule) PostScanSpec() serialize.PostScanSpec {
|
||||
return serialize.PostScanSpec{
|
||||
Action: api.ActionInsert, // Add new vulnerabilities
|
||||
}
|
||||
}
|
||||
|
||||
func (WordpressModule) PostScan(results serialize.Results) (serialize.Results, error) {
|
||||
// e.g. results
|
||||
// [
|
||||
// {
|
||||
// "Target": "",
|
||||
// "Class": "custom",
|
||||
// "CustomResources": [
|
||||
// {
|
||||
// "Type": "wordpress-version",
|
||||
// "FilePath": "/usr/src/wordpress/wp-includes/version.php",
|
||||
// "Layer": {
|
||||
// "DiffID": "sha256:057649e61046e02c975b84557c03c6cca095b8c9accd3bd20eb4e432f7aec887"
|
||||
// },
|
||||
// "Data": "5.7.1"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
var wpVersion int
|
||||
for _, result := range results {
|
||||
if result.Class != types.ClassCustom {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range result.CustomResources {
|
||||
if c.Type != typeWPVersion {
|
||||
continue
|
||||
}
|
||||
wpVersion = c.Data.(string)
|
||||
wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion))
|
||||
|
||||
...snip...
|
||||
|
||||
if affectedVersion.Check(ver) {
|
||||
vulnerable = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if vulnerable {
|
||||
// Add CVE-2020-36326
|
||||
results = append(results, serialize.Result{
|
||||
Target: wpPath,
|
||||
Class: types.ClassLangPkg,
|
||||
Type: "wordpress",
|
||||
Vulnerabilities: []types.DetectedVulnerability {
|
||||
{
|
||||
VulnerabilityID: "CVE-2020-36326",
|
||||
PkgName: "wordpress",
|
||||
InstalledVersion: wpVersion,
|
||||
FixedVersion: "5.7.2",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "PHPMailer 6.1.8 through 6.4.0 allows object injection through Phar Deserialization via addAttachment with a UNC pathname.",
|
||||
Severity: "CRITICAL",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
```
|
||||
|
||||
The new vulnerability will be added to the scan results.
|
||||
This example shows how the module inserts a new finding.
|
||||
If you are interested in `Update`, you can see an example of [Spring4Shell][trivy-module-spring4shell].
|
||||
|
||||
In the `Delete` action, `PostScan` needs to return results you want to delete.
|
||||
If `PostScan` returns an empty, Trivy will not delete anything.
|
||||
|
||||
#### Build
|
||||
Follow [the install guide][tinygo-installation] and install TinyGo.
|
||||
|
||||
```bash
|
||||
$ tinygo build -o wordpress.wasm -scheduler=none -target=wasi --no-debug wordpress.go
|
||||
```
|
||||
|
||||
Put the built binary to the module directory that is under the home directory by default.
|
||||
|
||||
```bash
|
||||
$ mkdir -p ~/.trivy/modules
|
||||
$ cp spring4shell.wasm ~/.trivy/modules
|
||||
```
|
||||
|
||||
## Distribute Your Module
|
||||
You can distribute your own module in OCI registries. Please follow [the oras installation instruction][oras].
|
||||
|
||||
```bash
|
||||
oras push ghcr.io/aquasecurity/trivy-module-wordpress:latest wordpress.wasm:application/vnd.module.wasm.content.layer.v1+wasm
|
||||
Uploading 3daa3dac086b wordpress.wasm
|
||||
Pushed ghcr.io/aquasecurity/trivy-module-wordpress:latest
|
||||
Digest: sha256:6416d0199d66ce52ced19f01d75454b22692ff3aa7737e45f7a189880840424f
|
||||
```
|
||||
|
||||
## Examples
|
||||
- [Spring4Shell][trivy-module-spring4shell]
|
||||
- [WordPress][trivy-module-wordpress]
|
||||
|
||||
[regexp]: https://github.com/google/re2/wiki/Syntax
|
||||
|
||||
[tinygo]: https://tinygo.org/
|
||||
[spring4shell]: https://blog.aquasec.com/zero-day-rce-vulnerability-spring4shell
|
||||
[wazero]: https://github.com/tetratelabs/wazero
|
||||
|
||||
[trivy-module-spring4shell]: https://github.com/aquasecurity/trivy/tree/main/examples/module/spring4shell
|
||||
[trivy-module-wordpress]: https://github.com/aquasecurity/trivy/tree/main/examples/module/wordpress
|
||||
|
||||
[tinygo-installation]: https://tinygo.org/getting-started/install/
|
||||
[oras]: https://oras.land/cli/
|
||||
@@ -1,8 +1,7 @@
|
||||
# Kubernetes
|
||||
|
||||
!!! warning "EXPERIMENTAL"
|
||||
|
||||
This feature might change without preserving backwards compatibility.
|
||||
This feature might change without preserving backwards compatibility.
|
||||
|
||||
The Trivy K8s CLI allows you to scan your Kubernetes cluster for Vulnerabilities, Secrets and Misconfigurations. You can either run the CLI locally or integrate it into your CI/CD pipeline. The difference to the Trivy CLI is that the Trivy K8s CLI allows you to scan running workloads directly within your cluster.
|
||||
|
||||
|
||||
17
docs/docs/references/cli/module.md
Normal file
17
docs/docs/references/cli/module.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Module
|
||||
|
||||
```bash
|
||||
NAME:
|
||||
trivy module - manage modules
|
||||
|
||||
USAGE:
|
||||
trivy module command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
install, i install a module
|
||||
uninstall, u uninstall a module
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
```
|
||||
21
docs/docs/references/cli/plugin.md
Normal file
21
docs/docs/references/cli/plugin.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Plugin
|
||||
|
||||
```bash
|
||||
NAME:
|
||||
trivy plugin - manage plugins
|
||||
|
||||
USAGE:
|
||||
trivy plugin command [command options] plugin_uri
|
||||
|
||||
COMMANDS:
|
||||
install, i install a plugin
|
||||
uninstall, u uninstall a plugin
|
||||
list, l list installed plugin
|
||||
info information about a plugin
|
||||
run, r run a plugin on the fly
|
||||
update update an existing plugin
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
```
|
||||
@@ -1,173 +0,0 @@
|
||||
# Plugins
|
||||
Trivy provides a plugin feature to allow others to extend the Trivy CLI without the need to change the Trivycode base.
|
||||
This plugin system was inspired by the plugin system used in [kubectl][kubectl], [Helm][helm], and [Conftest][conftest].
|
||||
|
||||
## Overview
|
||||
Trivy plugins are add-on tools that integrate seamlessly with Trivy.
|
||||
They provide a way to extend the core feature set of Trivy, but without requiring every new feature to be written in Go and added to the core tool.
|
||||
|
||||
- They can be added and removed from a Trivy installation without impacting the core Trivy tool.
|
||||
- They can be written in any programming language.
|
||||
- They integrate with Trivy, and will show up in Trivy help and subcommands.
|
||||
|
||||
!!! warning
|
||||
Trivy plugins available in public are not audited for security.
|
||||
You should install and run third-party plugins at your own risk, since they are arbitrary programs running on your machine.
|
||||
|
||||
|
||||
## Installing a Plugin
|
||||
A plugin can be installed using the `trivy plugin install` command.
|
||||
This command takes a url and will download the plugin and install it in the plugin cache.
|
||||
|
||||
Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set.
|
||||
Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache.
|
||||
The preference order is as follows:
|
||||
|
||||
- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir
|
||||
- ~/.trivy/plugins
|
||||
|
||||
Under the hood Trivy leverages [go-getter][go-getter] to download plugins.
|
||||
This means the following protocols are supported for downloading plugins:
|
||||
|
||||
- OCI Registries
|
||||
- Local Files
|
||||
- Git
|
||||
- HTTP/HTTPS
|
||||
- Mercurial
|
||||
- Amazon S3
|
||||
- Google Cloud Storage
|
||||
|
||||
For example, to download the Kubernetes Trivy plugin you can execute the following command:
|
||||
|
||||
```bash
|
||||
$ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl
|
||||
```
|
||||
## Using Plugins
|
||||
Once the plugin is installed, Trivy will load all available plugins in the cache on the start of the next Trivy execution.
|
||||
A plugin will be made in the Trivy CLI based on the plugin name.
|
||||
To display all plugins, you can list them by `trivy --help`
|
||||
|
||||
```bash
|
||||
$ trivy --help
|
||||
NAME:
|
||||
trivy - A simple and comprehensive vulnerability scanner for containers
|
||||
|
||||
USAGE:
|
||||
trivy [global options] command [command options] target
|
||||
|
||||
VERSION:
|
||||
dev
|
||||
|
||||
COMMANDS:
|
||||
image, i scan an image
|
||||
filesystem, fs scan local filesystem
|
||||
repository, repo scan remote repository
|
||||
client, c client mode
|
||||
server, s server mode
|
||||
plugin, p manage plugins
|
||||
kubectl scan kubectl resources
|
||||
help, h Shows a list of commands or help for one command
|
||||
```
|
||||
|
||||
As shown above, `kubectl` subcommand exists in the `COMMANDS` section.
|
||||
To call the kubectl plugin and scan existing Kubernetes deployments, you can execute the following command:
|
||||
|
||||
```
|
||||
$ trivy kubectl deployment <deployment-id> -- --ignore-unfixed --severity CRITICAL
|
||||
```
|
||||
|
||||
Internally the kubectl plugin calls the kubectl binary to fetch information about that deployment and passes the using images to Trivy.
|
||||
You can see the detail [here][trivy-plugin-kubectl].
|
||||
|
||||
If you want to omit even the subcommand, you can use `TRIVY_RUN_AS_PLUGIN` environment variable.
|
||||
|
||||
```bash
|
||||
$ TRIVY_RUN_AS_PLUGIN=kubectl trivy job your-job -- --format json
|
||||
```
|
||||
|
||||
## Installing and Running Plugins on the fly
|
||||
`trivy plugin run` installs a plugin and runs it on the fly.
|
||||
If the plugin is already present in the cache, the installation is skipped.
|
||||
|
||||
```bash
|
||||
trivy plugin run github.com/aquasecurity/trivy-plugin-kubectl pod your-pod -- --exit-code 1
|
||||
```
|
||||
|
||||
## Uninstalling Plugins
|
||||
Specify a plugin name with `trivy plugin uninstall` command.
|
||||
|
||||
```bash
|
||||
$ trivy plugin uninstall kubectl
|
||||
```
|
||||
|
||||
## Building Plugins
|
||||
Each plugin has a top-level directory, and then a plugin.yaml file.
|
||||
|
||||
```bash
|
||||
your-plugin/
|
||||
|
|
||||
|- plugin.yaml
|
||||
|- your-plugin.sh
|
||||
```
|
||||
|
||||
In the example above, the plugin is contained inside of a directory named `your-plugin`.
|
||||
It has two files: plugin.yaml (required) and an executable script, your-plugin.sh (optional).
|
||||
|
||||
The core of a plugin is a simple YAML file named plugin.yaml.
|
||||
Here is an example YAML of trivy-plugin-kubectl plugin that adds support for Kubernetes scanning.
|
||||
|
||||
```yaml
|
||||
name: "kubectl"
|
||||
repository: github.com/aquasecurity/trivy-plugin-kubectl
|
||||
version: "0.1.0"
|
||||
usage: scan kubectl resources
|
||||
description: |-
|
||||
A Trivy plugin that scans the images of a kubernetes resource.
|
||||
Usage: trivy kubectl TYPE[.VERSION][.GROUP] NAME
|
||||
platforms:
|
||||
- selector: # optional
|
||||
os: darwin
|
||||
arch: amd64
|
||||
uri: ./trivy-kubectl # where the execution file is (local file, http, git, etc.)
|
||||
bin: ./trivy-kubectl # path to the execution file
|
||||
- selector: # optional
|
||||
os: linux
|
||||
arch: amd64
|
||||
uri: https://github.com/aquasecurity/trivy-plugin-kubectl/releases/download/v0.1.0/trivy-kubectl.tar.gz
|
||||
bin: ./trivy-kubectl
|
||||
```
|
||||
|
||||
The `plugin.yaml` field should contain the following information:
|
||||
|
||||
- name: The name of the plugin. This also determines how the plugin will be made available in the Trivy CLI. For example, if the plugin is named kubectl, you can call the plugin with `trivy kubectl`. (required)
|
||||
- version: The version of the plugin. (required)
|
||||
- usage: A short usage description. (required)
|
||||
- description: A long description of the plugin. This is where you could provide a helpful documentation of your plugin. (required)
|
||||
- platforms: (required)
|
||||
- selector: The OS/Architecture specific variations of a execution file. (optional)
|
||||
- os: OS information based on GOOS (linux, darwin, etc.) (optional)
|
||||
- arch: The architecture information based on GOARCH (amd64, arm64, etc.) (optional)
|
||||
- uri: Where the executable file is. Relative path from the root directory of the plugin or remote URL such as HTTP and S3. (required)
|
||||
- bin: Which file to call when the plugin is executed. Relative path from the root directory of the plugin. (required)
|
||||
|
||||
The following rules will apply in deciding which platform to select:
|
||||
|
||||
- If both `os` and `arch` under `selector` match the current platform, search will stop and the platform will be used.
|
||||
- If `selector` is not present, the platform will be used.
|
||||
- If `os` matches and there is no more specific `arch` match, the platform will be used.
|
||||
- If no `platform` match is found, Trivy will exit with an error.
|
||||
|
||||
After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache.
|
||||
When the plugin is called via Trivy CLI, `bin` command will be executed.
|
||||
|
||||
The plugin is responsible for handling flags and arguments. Any arguments are passed to the plugin from the `trivy` command.
|
||||
|
||||
## Example
|
||||
https://github.com/aquasecurity/trivy-plugin-kubectl
|
||||
|
||||
[kubectl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
|
||||
[helm]: https://helm.sh/docs/topics/plugins/
|
||||
[conftest]: https://www.conftest.dev/plugins/
|
||||
[go-getter]: https://github.com/hashicorp/go-getter
|
||||
[trivy-plugin-kubectl]: https://github.com/aquasecurity/trivy-plugin-kubectl
|
||||
|
||||
40
examples/module/spring4shell/README.md
Normal file
40
examples/module/spring4shell/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Spring4Shell module
|
||||
|
||||
This module provides a more in-depth investigation of Spring4Shell detection.
|
||||
|
||||
## Set up
|
||||
|
||||
```
|
||||
$ tinygo build -o spring4shell.wasm -scheduler=none -target=wasi --no-debug spring4shell.go
|
||||
$ mkdir -p ~/.trivy/modules
|
||||
$ cp spring4shell.wasm ~/.trivy/modules
|
||||
```
|
||||
|
||||
It is also available in [GHCR][trivy-module-spring4shell].
|
||||
You can install it via `trivy module install`.
|
||||
|
||||
```bash
|
||||
$ trivy module install ghcr.io/aquasecurity/trivy-module-spring4shell
|
||||
2022-06-13T15:32:21.972+0300 INFO Installing the module from ghcr.io/aquasecurity/trivy-module-spring4shell...
|
||||
```
|
||||
|
||||
## Run Trivy
|
||||
|
||||
```
|
||||
$ trivy image spring-core-rce-jdk8:latest
|
||||
2022-05-29T22:35:04.873+0300 INFO Loading spring4shell.wasm...
|
||||
2022-05-29T22:35:05.348+0300 INFO Registering WASM module: spring4shell@v1
|
||||
2022-05-29T22:35:07.124+0300 INFO Module spring4shell: analyzing /app/tomcat/RELEASE-NOTES...
|
||||
2022-05-29T22:35:07.139+0300 INFO Module spring4shell: analyzing /app/jdk9/release...
|
||||
2022-05-29T22:37:04.636+0300 INFO Module spring4shell: analyzing /app/jdk9/release...
|
||||
...
|
||||
2022-05-29T22:37:08.917+0300 INFO Module spring4shell: Java Version: 8, Tomcat Version: 8.5.77
|
||||
2022-05-29T22:37:08.917+0300 INFO Module spring4shell: change CVE-2022-22965 severity from CRITICAL to LOW
|
||||
```
|
||||
|
||||
In the above example, the Java version is 8 which is not affected by CVE-2022-22965, so this module changes the severity from CRITICAL to LOW.
|
||||
|
||||
## Note
|
||||
This module is also used for testing in Trivy.
|
||||
|
||||
[trivy-module-spring4shell]: https://github.com/orgs/aquasecurity/packages/container/package/trivy-module-spring4shell
|
||||
18
examples/module/spring4shell/go.mod
Normal file
18
examples/module/spring4shell/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module github.com/aquasecurity/trivy-module-spring4shell
|
||||
|
||||
go 1.18
|
||||
|
||||
// It points to local Trivy for testing. Normal WASM modules don't need the replace directive.
|
||||
replace github.com/aquasecurity/trivy => ../../../
|
||||
|
||||
require github.com/aquasecurity/trivy v0.0.0-00010101000000-000000000000
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/fanal v0.0.0-20220614123434-09d6aced4205 // indirect
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20220602091213-39d8a6798e07 // indirect
|
||||
github.com/caarlos0/env/v6 v6.9.3 // indirect
|
||||
github.com/google/go-containerregistry v0.7.1-0.20211214010025-a65b7844a475 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
1241
examples/module/spring4shell/go.sum
Normal file
1241
examples/module/spring4shell/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
283
examples/module/spring4shell/spring4shell.go
Normal file
283
examples/module/spring4shell/spring4shell.go
Normal file
@@ -0,0 +1,283 @@
|
||||
//go:generate tinygo build -o spring4shell.wasm -scheduler=none -target=wasi --no-debug spring4shell.go
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/module/wasm"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ModuleVersion = 1
|
||||
ModuleName = "spring4shell"
|
||||
TypeJavaMajor = ModuleName + "/java-major-version"
|
||||
TypeTomcatVersion = ModuleName + "/tomcat-version"
|
||||
)
|
||||
|
||||
var (
|
||||
tomcatVersionRegex = regexp.MustCompile(`Apache Tomcat Version ([\d.]+)`)
|
||||
)
|
||||
|
||||
// main is required for TinyGo to compile to Wasm.
|
||||
func main() {
|
||||
wasm.RegisterModule(Spring4Shell{})
|
||||
}
|
||||
|
||||
type Spring4Shell struct {
|
||||
// Cannot define fields as modules can't keep state.
|
||||
}
|
||||
|
||||
func (Spring4Shell) Version() int {
|
||||
return ModuleVersion
|
||||
}
|
||||
|
||||
func (Spring4Shell) Name() string {
|
||||
return ModuleName
|
||||
}
|
||||
|
||||
func (Spring4Shell) RequiredFiles() []string {
|
||||
return []string{
|
||||
`\/openjdk-\d+\/release`, // For OpenJDK version
|
||||
`\/jdk\d+\/release`, // For JDK version
|
||||
`tomcat\/RELEASE-NOTES`, // For Tomcat version
|
||||
}
|
||||
}
|
||||
|
||||
func (s Spring4Shell) Analyze(filePath string) (*serialize.AnalysisResult, error) {
|
||||
wasm.Info(fmt.Sprintf("analyzing %s...", filePath))
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(filePath, "/release"):
|
||||
return s.parseJavaRelease(f, filePath)
|
||||
case strings.HasSuffix(filePath, "/RELEASE-NOTES"):
|
||||
return s.parseTomcatReleaseNotes(f, filePath)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Parse a jdk release file like "/usr/local/openjdk-11/release"
|
||||
func (Spring4Shell) parseJavaRelease(f *os.File, filePath string) (*serialize.AnalysisResult, error) {
|
||||
var javaVersion string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "JAVA_VERSION=") {
|
||||
continue
|
||||
}
|
||||
|
||||
ss := strings.Split(line, "=")
|
||||
if len(ss) != 2 {
|
||||
return nil, fmt.Errorf("invalid java version: %s", line)
|
||||
}
|
||||
|
||||
javaVersion = strings.Trim(ss[1], `"`)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serialize.AnalysisResult{
|
||||
CustomResources: []serialize.CustomResource{
|
||||
{
|
||||
Type: TypeJavaMajor,
|
||||
FilePath: filePath,
|
||||
Data: javaVersion,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (Spring4Shell) parseTomcatReleaseNotes(f *os.File, filePath string) (*serialize.AnalysisResult, error) {
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := tomcatVersionRegex.FindStringSubmatch(string(b))
|
||||
if len(m) != 2 {
|
||||
return nil, fmt.Errorf("unknown tomcat release notes format")
|
||||
}
|
||||
|
||||
return &serialize.AnalysisResult{
|
||||
CustomResources: []serialize.CustomResource{
|
||||
{
|
||||
Type: TypeTomcatVersion,
|
||||
FilePath: filePath,
|
||||
Data: m[1],
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (Spring4Shell) PostScanSpec() serialize.PostScanSpec {
|
||||
return serialize.PostScanSpec{
|
||||
Action: api.ActionUpdate, // Update severity
|
||||
IDs: []string{"CVE-2022-22965"},
|
||||
}
|
||||
}
|
||||
|
||||
// PostScan takes results including custom resources and detected CVE-2022-22965.
|
||||
//
|
||||
// Example input:
|
||||
// [
|
||||
// {
|
||||
// "Target": "",
|
||||
// "Class": "custom",
|
||||
// "CustomResources": [
|
||||
// {
|
||||
// "Type": "spring4shell/java-major-version",
|
||||
// "FilePath": "/usr/local/openjdk-8/release",
|
||||
// "Layer": {
|
||||
// "Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791",
|
||||
// "DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1"
|
||||
// },
|
||||
// "Data": "1.8.0_322"
|
||||
// },
|
||||
// {
|
||||
// "Type": "spring4shell/tomcat-version",
|
||||
// "FilePath": "/usr/local/tomcat/RELEASE-NOTES",
|
||||
// "Layer": {
|
||||
// "Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06",
|
||||
// "DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6"
|
||||
// },
|
||||
// "Data": "8.5.77"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// "Target": "Java",
|
||||
// "Class": "lang-pkgs",
|
||||
// "Type": "jar",
|
||||
// "Vulnerabilities": [
|
||||
// {
|
||||
// "VulnerabilityID": "CVE-2022-22965",
|
||||
// "PkgName": "org.springframework.boot:spring-boot",
|
||||
// "PkgPath": "usr/local/tomcat/webapps/helloworld.war",
|
||||
// "InstalledVersion": "2.6.3",
|
||||
// "FixedVersion": "2.5.12, 2.6.6",
|
||||
// "Layer": {
|
||||
// "Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07",
|
||||
// "DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5"
|
||||
// },
|
||||
// "SeveritySource": "nvd",
|
||||
// "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965",
|
||||
// "DataSource": {
|
||||
// "ID": "glad",
|
||||
// "Name": "GitLab Advisory Database Community",
|
||||
// "URL": "https://gitlab.com/gitlab-org/advisories-community"
|
||||
// },
|
||||
// "Title": "spring-framework: RCE via Data Binding on JDK 9+",
|
||||
// "Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.",
|
||||
// "Severity": "CRITICAL",
|
||||
// "CweIDs": [
|
||||
// "CWE-94"
|
||||
// ],
|
||||
// "VendorSeverity": {
|
||||
// "ghsa": 4,
|
||||
// "nvd": 4,
|
||||
// "redhat": 3
|
||||
// },
|
||||
// "CVSS": {
|
||||
// "ghsa": {
|
||||
// "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
// "V3Score": 9.8
|
||||
// },
|
||||
// "nvd": {
|
||||
// "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
// "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
// "V2Score": 7.5,
|
||||
// "V3Score": 9.8
|
||||
// },
|
||||
// "redhat": {
|
||||
// "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
// "V3Score": 8.1
|
||||
// }
|
||||
// },
|
||||
// "References": [
|
||||
// "https://github.com/advisories/GHSA-36p3-wjmg-h94x"
|
||||
// ],
|
||||
// "PublishedDate": "2022-04-01T23:15:00Z",
|
||||
// "LastModifiedDate": "2022-05-19T14:21:00Z"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//]
|
||||
func (Spring4Shell) PostScan(results serialize.Results) (serialize.Results, error) {
|
||||
var javaMajorVersion int
|
||||
var tomcatVersion string
|
||||
for _, result := range results {
|
||||
if result.Class != types.ClassCustom {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range result.CustomResources {
|
||||
if c.Type == TypeJavaMajor {
|
||||
v := c.Data.(string)
|
||||
ss := strings.Split(v, ".")
|
||||
if len(ss) == 0 || len(ss) < 2 {
|
||||
wasm.Warn("Invalid Java version: " + v)
|
||||
continue
|
||||
}
|
||||
|
||||
ver := ss[0]
|
||||
if ver == "1" {
|
||||
ver = ss[1]
|
||||
}
|
||||
|
||||
var err error
|
||||
javaMajorVersion, err = strconv.Atoi(ver)
|
||||
if err != nil {
|
||||
wasm.Warn("Invalid Java version: " + v)
|
||||
continue
|
||||
}
|
||||
} else if c.Type == TypeTomcatVersion {
|
||||
tomcatVersion = c.Data.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wasm.Info(fmt.Sprintf("Java Version: %d, Tomcat Version: %s", javaMajorVersion, tomcatVersion))
|
||||
|
||||
vulnerable := true
|
||||
// TODO: version comparison
|
||||
if tomcatVersion == "10.0.20" || tomcatVersion == "9.0.62" || tomcatVersion == "8.5.78" {
|
||||
vulnerable = false
|
||||
} else if javaMajorVersion <= 8 {
|
||||
vulnerable = false
|
||||
}
|
||||
|
||||
for i, result := range results {
|
||||
for j, vuln := range result.Vulnerabilities {
|
||||
// Look up Spring4Shell
|
||||
if vuln.VulnerabilityID != "CVE-2022-22965" {
|
||||
continue
|
||||
}
|
||||
|
||||
// If it doesn't satisfy any of requirements, the severity should be changed to LOW.
|
||||
if !strings.Contains(vuln.PkgPath, ".war") || !vulnerable {
|
||||
wasm.Info(fmt.Sprintf("change %s CVE-2022-22965 severity from CRITICAL to LOW", vuln.PkgName))
|
||||
results[i].Vulnerabilities[j].Severity = "LOW"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
31
examples/module/wordpress/README.md
Normal file
31
examples/module/wordpress/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# WoredPress module
|
||||
|
||||
This module provides a more in-depth investigation of Wordpress detection.
|
||||
|
||||
## Set up
|
||||
|
||||
```
|
||||
$ tinygo build -o wordpress.wasm -scheduler=none -target=wasi --no-debug wordpress.go
|
||||
$ mkdir -p ~/.trivy/modules
|
||||
$ cp wordpress.wasm ~/.trivy/modules
|
||||
```
|
||||
|
||||
It is also available in [GHCR][trivy-module-wordpress].
|
||||
You can install it via `trivy module install`.
|
||||
|
||||
```bash
|
||||
$ trivy module install ghcr.io/aquasecurity/trivy-module-wordpress
|
||||
2022-06-13T15:32:21.972+0300 INFO Installing the module from ghcr.io/aquasecurity/trivy-module-wordpress...
|
||||
```
|
||||
|
||||
## Run Trivy
|
||||
|
||||
```
|
||||
$ trivy image wordpress:5.7.1
|
||||
2022-05-29T22:35:04.873+0300 INFO Loading wordpress.wasm...
|
||||
2022-05-29T22:35:05.348+0300 INFO Registering WASM module: wordpress@v1
|
||||
```
|
||||
|
||||
In the above example, CVE-2020-36326 and CVE-2018-19296 will be detected if the WordPress version is vulnerable.
|
||||
|
||||
[trivy-module-wordpress]: https://github.com/orgs/aquasecurity/packages/container/package/trivy-module-wordpress
|
||||
21
examples/module/wordpress/go.mod
Normal file
21
examples/module/wordpress/go.mod
Normal file
@@ -0,0 +1,21 @@
|
||||
module github.com/aquasecurity/trivy-module-spring4shell
|
||||
|
||||
go 1.18
|
||||
|
||||
// It points to local Trivy for testing. Normal WASM modules don't need the replace directive.
|
||||
replace github.com/aquasecurity/trivy => ../../../
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/trivy v0.0.0-00010101000000-000000000000
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20220602091213-39d8a6798e07
|
||||
github.com/hashicorp/go-version v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/fanal v0.0.0-20220614123434-09d6aced4205 // indirect
|
||||
github.com/caarlos0/env/v6 v6.9.3 // indirect
|
||||
github.com/google/go-containerregistry v0.7.1-0.20211214010025-a65b7844a475 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
1243
examples/module/wordpress/go.sum
Normal file
1243
examples/module/wordpress/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
165
examples/module/wordpress/wordpress.go
Normal file
165
examples/module/wordpress/wordpress.go
Normal file
@@ -0,0 +1,165 @@
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/module/wasm"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
moduleVersion = 1
|
||||
moduleName = "wordpress"
|
||||
typeWPVersion = "wordpress-version"
|
||||
)
|
||||
|
||||
// main is required for TinyGo to compile to Wasm.
|
||||
func main() {
|
||||
wasm.RegisterModule(WordpressModule{})
|
||||
}
|
||||
|
||||
type WordpressModule struct {
|
||||
// Cannot define fields as modules can't keep state.
|
||||
}
|
||||
|
||||
func (WordpressModule) Version() int {
|
||||
return moduleVersion
|
||||
}
|
||||
|
||||
func (WordpressModule) Name() string {
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (WordpressModule) RequiredFiles() []string {
|
||||
return []string{
|
||||
`wp-includes\/version.php`,
|
||||
}
|
||||
}
|
||||
|
||||
func (s WordpressModule) Analyze(filePath string) (*serialize.AnalysisResult, error) {
|
||||
f, err := os.Open(filePath) // e.g. filePath: /usr/src/wordpress/wp-includes/version.php
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var wpVersion string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "$wp_version") {
|
||||
continue
|
||||
}
|
||||
|
||||
ss := strings.Split(line, "=")
|
||||
if len(ss) != 2 {
|
||||
return nil, fmt.Errorf("invalid wordpress version: %s", line)
|
||||
}
|
||||
|
||||
// NOTE: it is an example; you actually need to handle comments, etc
|
||||
ss[1] = strings.TrimSpace(ss[1])
|
||||
wpVersion = strings.Trim(ss[1], `''";`)
|
||||
wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion))
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serialize.AnalysisResult{
|
||||
CustomResources: []serialize.CustomResource{
|
||||
{
|
||||
Type: typeWPVersion,
|
||||
FilePath: filePath,
|
||||
Data: wpVersion,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (WordpressModule) PostScanSpec() serialize.PostScanSpec {
|
||||
return serialize.PostScanSpec{
|
||||
Action: api.ActionInsert, // Add new vulnerabilities
|
||||
}
|
||||
}
|
||||
|
||||
func (WordpressModule) PostScan(results serialize.Results) (serialize.Results, error) {
|
||||
wasm.Info("post scan")
|
||||
|
||||
// https://wpscan.com/vulnerability/4cd46653-4470-40ff-8aac-318bee2f998d
|
||||
affectedVersion, err := version.NewConstraint(">=5.7, <5.7.2")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
vulnerable bool
|
||||
wpPath, wpVersion string
|
||||
)
|
||||
for _, result := range results {
|
||||
if result.Class != types.ClassCustom {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range result.CustomResources {
|
||||
if c.Type != typeWPVersion {
|
||||
continue
|
||||
}
|
||||
wpPath = c.FilePath
|
||||
wpVersion = c.Data.(string)
|
||||
wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion))
|
||||
|
||||
ver, err := version.NewVersion(wpVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if affectedVersion.Check(ver) {
|
||||
vulnerable = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if vulnerable {
|
||||
// Add CVE-2020-36326 and CVE-2018-19296
|
||||
results = append(results, serialize.Result{
|
||||
Target: wpPath,
|
||||
Class: types.ClassLangPkg,
|
||||
Type: "wordpress",
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2020-36326",
|
||||
PkgName: "wordpress",
|
||||
InstalledVersion: wpVersion,
|
||||
FixedVersion: "5.7.2",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "PHPMailer 6.1.8 through 6.4.0 allows object injection through Phar Deserialization via addAttachment with a UNC pathname.",
|
||||
Severity: "CRITICAL",
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-19296",
|
||||
PkgName: "wordpress",
|
||||
InstalledVersion: wpVersion,
|
||||
FixedVersion: "5.7.2",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "PHPMailer before 5.2.27 and 6.x before 6.0.6 is vulnerable to an object injection attack.",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
257
go.mod
257
go.mod
@@ -13,7 +13,9 @@ require (
|
||||
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492
|
||||
github.com/aquasecurity/table v1.5.1
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20220602091213-39d8a6798e07
|
||||
github.com/aquasecurity/trivy-kubernetes v0.3.1-0.20220613131930-79b2cb425b18
|
||||
github.com/caarlos0/env/v6 v6.9.3
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/cheggaaa/pb/v3 v3.0.8
|
||||
@@ -29,23 +31,22 @@ require (
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
||||
github.com/mailru/easyjson v0.7.6
|
||||
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/open-policy-agent/opa v0.41.0
|
||||
github.com/owenrumney/go-sarif/v2 v2.1.1
|
||||
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
|
||||
github.com/samber/lo v1.21.0
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/testcontainers/testcontainers-go v0.13.0
|
||||
github.com/tetratelabs/wazero v0.0.0-20220606011721-119b069ba23e
|
||||
github.com/twitchtv/twirp v8.1.2+incompatible
|
||||
github.com/urfave/cli/v2 v2.8.1
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
|
||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
)
|
||||
@@ -65,45 +66,90 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible // indirect
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.2 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/agnivade/levenshtein v1.0.1 // indirect
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/aquasecurity/defsec v0.63.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.25 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/briandowns/spinner v1.12.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||
github.com/container-orchestrated-devices/container-device-interface v0.3.1 // indirect
|
||||
github.com/containerd/cgroups v1.0.3 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.6.4 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/containerd/go-cni v1.1.6 // indirect
|
||||
github.com/containerd/imgcrypt v1.1.5-0.20220421044638-8ba028dca028 // indirect
|
||||
github.com/containerd/nerdctl v0.20.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter v0.11.4 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
|
||||
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
|
||||
github.com/containerd/typeurl v1.0.3-0.20220422153119-7f6e6d160d67 // indirect
|
||||
github.com/containernetworking/cni v1.1.1 // indirect
|
||||
github.com/containers/ocicrypt v1.1.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/docker/cli v20.10.16+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-yaml v1.8.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
@@ -114,160 +160,51 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/ipfs/go-cid v0.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20220607073645-842f01763e21 // indirect
|
||||
github.com/knqyf263/nested v0.0.1 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liamg/iamgo v0.0.6 // indirect
|
||||
github.com/liamg/jfather v0.0.7 // indirect
|
||||
github.com/liamg/memoryfs v1.4.2 // indirect
|
||||
github.com/liamg/memoryfs v1.4.2
|
||||
github.com/liamg/tml v0.6.0
|
||||
github.com/lib/pq v1.10.4 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/buildkit v0.10.3 // indirect
|
||||
github.com/moby/sys/mount v0.3.2 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.1 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/owenrumney/squealer v1.0.1-0.20220510063705-c0be93f0edea // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spdx/tools-golang v0.3.0
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.1.0 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220516133312-45b265872317 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
||||
google.golang.org/api v0.62.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
modernc.org/libc v1.16.7 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/sqlite v1.17.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/table v1.5.1
|
||||
github.com/aquasecurity/trivy-kubernetes v0.3.1-0.20220613131930-79b2cb425b18
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/Masterminds/squirrel v1.5.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.2 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/agnivade/levenshtein v1.0.1 // indirect
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||
github.com/container-orchestrated-devices/container-device-interface v0.3.1 // indirect
|
||||
github.com/containerd/cgroups v1.0.3 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/containerd/go-cni v1.1.6 // indirect
|
||||
github.com/containerd/imgcrypt v1.1.5-0.20220421044638-8ba028dca028 // indirect
|
||||
github.com/containerd/nerdctl v0.20.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter v0.11.4 // indirect
|
||||
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
|
||||
github.com/containernetworking/cni v1.1.1 // indirect
|
||||
github.com/containers/ocicrypt v1.1.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/ipfs/go-cid v0.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.4 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/mount v0.3.2 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.1 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/multiformats/go-base32 v0.0.3 // indirect
|
||||
github.com/multiformats/go-base36 v0.1.0 // indirect
|
||||
@@ -275,34 +212,82 @@ require (
|
||||
github.com/multiformats/go-multihash v0.0.15 // indirect
|
||||
github.com/multiformats/go-varint v0.0.6 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20220311020903-6969a0a09ab1 // indirect
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect
|
||||
github.com/opencontainers/selinux v1.10.1 // indirect
|
||||
github.com/owenrumney/squealer v1.0.1-0.20220510063705-c0be93f0edea // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rubenv/sql-migrate v1.1.1 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spdx/tools-golang v0.3.0
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tidwall/gjson v1.14.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/urfave/cli v1.22.9 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.4.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.1.0 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220516133312-45b265872317 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
||||
google.golang.org/api v0.62.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
helm.sh/helm/v3 v3.9.0 // indirect
|
||||
k8s.io/api v0.24.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.24.0 // indirect
|
||||
@@ -314,6 +299,16 @@ require (
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
|
||||
k8s.io/kubectl v0.24.1 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
modernc.org/libc v1.16.7 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/sqlite v1.17.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
oras.land/oras-go v1.1.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.11.4 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1203,6 +1203,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
|
||||
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/testcontainers/testcontainers-go v0.13.0 h1:OUujSlEGsXVo/ykPVZk3KanBNGN0TYb/7oKIPVn15JA=
|
||||
github.com/testcontainers/testcontainers-go v0.13.0/go.mod h1:z1abufU633Eb/FmSBTzV6ntZAC1eZBYPtaFsn4nPuDk=
|
||||
github.com/tetratelabs/wazero v0.0.0-20220606011721-119b069ba23e h1:ZamZ43RLjKymhv0ny0uXU1cUL8T/UEXmTQRJLAvJcF4=
|
||||
github.com/tetratelabs/wazero v0.0.0-20220606011721-119b069ba23e/go.mod h1:Y4X/zO4sC2dJjZG9GDYNRbJGogfqFYJY/BbyKlOxXGI=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
|
||||
@@ -526,6 +526,9 @@ func setup(t *testing.T, options setupOptions) (*cli.App, string, string) {
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set a temp dir so that modules will not be loaded
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
|
||||
port, err := getFreePort()
|
||||
assert.NoError(t, err)
|
||||
addr := fmt.Sprintf("localhost:%d", port)
|
||||
|
||||
@@ -199,6 +199,9 @@ func TestDockerEngine(t *testing.T) {
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set a temp dir so that modules will not be loaded
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
|
||||
ctx := context.Background()
|
||||
defer ctx.Done()
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ func TestFilesystem(t *testing.T) {
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set a temp dir so that modules will not be loaded
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
osArgs := []string{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
//go:build integration || module_integration
|
||||
|
||||
package integration
|
||||
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -108,6 +108,13 @@ func readReport(t *testing.T, filePath string) types.Report {
|
||||
for j := range result.Vulnerabilities {
|
||||
report.Results[i].Vulnerabilities[j].Layer.Digest = ""
|
||||
}
|
||||
|
||||
sort.Slice(result.CustomResources, func(i, j int) bool {
|
||||
if result.CustomResources[i].Type != result.CustomResources[j].Type {
|
||||
return result.CustomResources[i].Type < result.CustomResources[j].Type
|
||||
}
|
||||
return result.CustomResources[i].FilePath < result.CustomResources[j].FilePath
|
||||
})
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
75
integration/module_test.go
Normal file
75
integration/module_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
//go:build module_integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/commands"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
func TestModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
golden string
|
||||
}{
|
||||
{
|
||||
name: "spring4shell jre 8, severity update",
|
||||
input: "testdata/fixtures/images/spring4shell-jre8.tar.gz",
|
||||
golden: "testdata/spring4shell-jre8.json.golden",
|
||||
},
|
||||
{
|
||||
name: "spring4shell jre 11, no severity update",
|
||||
input: "testdata/fixtures/images/spring4shell-jre11.tar.gz",
|
||||
golden: "testdata/spring4shell-jre11.json.golden",
|
||||
},
|
||||
}
|
||||
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set up module dir
|
||||
moduleDir := filepath.Join(cacheDir, module.RelativeDir)
|
||||
err := os.MkdirAll(moduleDir, 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set up Spring4Shell module
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
_, err = utils.CopyFile(filepath.Join("../", "examples", "module", "spring4shell", "spring4shell.wasm"),
|
||||
filepath.Join(moduleDir, "spring4shell.wasm"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Setup CLI App
|
||||
app := commands.NewApp("dev")
|
||||
app.Writer = io.Discard
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--ignore-unfixed", "--format", "json",
|
||||
"--skip-update", "--offline-scan", "--input", tt.input}
|
||||
|
||||
// Set up the output file
|
||||
outputFile := filepath.Join(t.TempDir(), "output.json")
|
||||
if *update {
|
||||
outputFile = tt.golden
|
||||
}
|
||||
|
||||
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
||||
|
||||
// Run Trivy
|
||||
assert.Nil(t, app.Run(osArgs))
|
||||
|
||||
// Compare want and got
|
||||
compareReports(t, tt.golden, outputFile)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -221,6 +221,9 @@ func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set a temp dir so that modules will not be loaded
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
|
||||
// Setup the output file
|
||||
outputFile := filepath.Join(t.TempDir(), "output.json")
|
||||
if *update {
|
||||
|
||||
@@ -4,6 +4,8 @@ TEST_IMAGE=ghcr.io/aquasecurity/trivy-test-images
|
||||
|
||||
CURRENT=$(cd $(dirname $0);pwd)
|
||||
|
||||
mkdir -p ${CURRENT}/../testdata/fixtures/images/
|
||||
|
||||
# List the tags
|
||||
TAGS=$(crane ls ${TEST_IMAGE})
|
||||
|
||||
|
||||
@@ -261,6 +261,9 @@ func TestTar(t *testing.T) {
|
||||
// Set up testing DB
|
||||
cacheDir := initDB(t)
|
||||
|
||||
// Set a temp dir so that modules will not be loaded
|
||||
t.Setenv("XDG_DATA_HOME", cacheDir)
|
||||
|
||||
// Setup CLI App
|
||||
app := commands.NewApp("dev")
|
||||
app.Writer = io.Discard
|
||||
|
||||
8
integration/testdata/fixtures/db/java.yaml
vendored
8
integration/testdata/fixtures/db/java.yaml
vendored
@@ -8,6 +8,14 @@
|
||||
- 2.9.10.4
|
||||
VulnerableVersions:
|
||||
- ">= 2.0.0, <= 2.9.10.3"
|
||||
- bucket: org.springframework:spring-beans
|
||||
pairs:
|
||||
- key: CVE-2022-22965
|
||||
value:
|
||||
PatchedVersions:
|
||||
- 5.3.18
|
||||
VulnerableVersions:
|
||||
- ">= 5.3.0, < 5.3.18"
|
||||
- bucket: maven::GitLab Advisory Database Community
|
||||
pairs:
|
||||
- bucket: com.fasterxml.jackson.core:jackson-databind
|
||||
|
||||
@@ -1179,3 +1179,31 @@
|
||||
- https://go-review.googlesource.com/c/text/+/340830
|
||||
- https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f
|
||||
- https://pkg.go.dev/vuln/GO-2021-0113
|
||||
- key: CVE-2022-22965
|
||||
value:
|
||||
Title: "spring-framework: RCE via Data Binding on JDK 9+"
|
||||
Description: "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it."
|
||||
Severity: CRITICAL
|
||||
CweIDs:
|
||||
- CWE-94
|
||||
VendorSeverity:
|
||||
nvd: 4
|
||||
ghsa: 4
|
||||
redhat: 3
|
||||
CVSS:
|
||||
ghsa:
|
||||
V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
||||
V3Score: 9.8
|
||||
nvd:
|
||||
V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P"
|
||||
V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
||||
V2Score: 7.5
|
||||
V3Score: 9.8
|
||||
redhat:
|
||||
V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
||||
V3Score: 8.1
|
||||
References:
|
||||
- "https://github.com/advisories/GHSA-36p3-wjmg-h94x",
|
||||
PublishedDate: "2022-04-01T23:15:00Z"
|
||||
LastModifiedDate: "2022-05-19T14:21:00Z"
|
||||
|
||||
|
||||
266
integration/testdata/spring4shell-jre11.json.golden
vendored
Normal file
266
integration/testdata/spring4shell-jre11.json.golden
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"SchemaVersion": 2,
|
||||
"ArtifactName": "testdata/fixtures/images/spring4shell-jre11.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
"OS": {
|
||||
"Family": "debian",
|
||||
"Name": "11.3"
|
||||
},
|
||||
"ImageID": "sha256:ed8f0747d483b60657982f0ef1ba74482aed08795cf0eb774b00bc53022a8351",
|
||||
"DiffIDs": [
|
||||
"sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce",
|
||||
"sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72",
|
||||
"sha256:1f0e278ace87a84577de56c99e5c05c6af6f8b582d1eb8dfd7de7be4cf215775",
|
||||
"sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45",
|
||||
"sha256:8e6776c643c1db15d540016171fe04137ee2a26c7d0b18bfebdcbd31c6b0d8b3",
|
||||
"sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42",
|
||||
"sha256:19da2426772aaa344a242e474fd7906d272fc8ded6eef5b4e461a4aa0725d7e5",
|
||||
"sha256:1fdc094b0e85888d2204310083e3c09fff6a4daeecf22692aa6be5e8b4001f94",
|
||||
"sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f"
|
||||
],
|
||||
"ImageConfig": {
|
||||
"architecture": "amd64",
|
||||
"created": "2022-06-07T03:41:13.228952Z",
|
||||
"docker_version": "20.10.14",
|
||||
"history": [
|
||||
{
|
||||
"created": "2022-03-29T00:22:18.812238611Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:966d3669b40f5fbaecee1ecbeb58debe19001076da5d94717080d55efbc25971 in / "
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:22:19.186561403Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"bash\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:52:15.681202963Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates p11-kit \t; \trm -rf /var/lib/apt/lists/*"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:55:28.571451389Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/openjdk-11",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:55:29.092016566Z",
|
||||
"created_by": "/bin/sh -c { echo '#/bin/sh'; echo 'echo \"$JAVA_HOME\"'; } \u003e /usr/local/bin/docker-java-home \u0026\u0026 chmod +x /usr/local/bin/docker-java-home \u0026\u0026 [ \"$JAVA_HOME\" = \"$(docker-java-home)\" ] # backwards compatibility"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:55:29.206969756Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:55:29.302995298Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV LANG=C.UTF-8",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:55:29.392969112Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV JAVA_VERSION=11.0.14.1",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:56:45.085797918Z",
|
||||
"created_by": "/bin/sh -c set -eux; \t\tarch=\"$(dpkg --print-architecture)\"; \tcase \"$arch\" in \t\t'amd64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jre_x64_linux_11.0.14.1_1.tar.gz'; \t\t\t;; \t\t'arm64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jre_aarch64_linux_11.0.14.1_1.tar.gz'; \t\t\t;; \t\t*) echo \u003e\u00262 \"error: unsupported architecture: '$arch'\"; exit 1 ;; \tesac; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdirmngr \t\tgnupg \t\twget \t; \trm -rf /var/lib/apt/lists/*; \t\twget --progress=dot:giga -O openjdk.tgz \"$downloadUrl\"; \twget --progress=dot:giga -O openjdk.tgz.asc \"$downloadUrl.sign\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys EAC843EBD3EFDB98CC772FADA5CD6035332FA671; \tgpg --batch --keyserver keyserver.ubuntu.com --keyserver-options no-self-sigs-only --recv-keys CA5F11C6CE22644D42C6AC4492EF8D39DC13168F; \tgpg --batch --list-sigs --keyid-format 0xLONG CA5F11C6CE22644D42C6AC4492EF8D39DC13168F \t\t| tee /dev/stderr \t\t| grep '0xA5CD6035332FA671' \t\t| grep 'Andrew Haley'; \tgpg --batch --verify openjdk.tgz.asc openjdk.tgz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\"; \t\tmkdir -p \"$JAVA_HOME\"; \ttar --extract \t\t--file openjdk.tgz \t\t--directory \"$JAVA_HOME\" \t\t--strip-components 1 \t\t--no-same-owner \t; \trm openjdk.tgz*; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\t{ \t\techo '#!/usr/bin/env bash'; \t\techo 'set -Eeuo pipefail'; \t\techo 'trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth \"$JAVA_HOME/lib/security/cacerts\"'; \t} \u003e /etc/ca-certificates/update.d/docker-openjdk; \tchmod +x /etc/ca-certificates/update.d/docker-openjdk; \t/etc/ca-certificates/update.d/docker-openjdk; \t\tfind \"$JAVA_HOME/lib\" -name '*.so' -exec dirname '{}' ';' | sort -u \u003e /etc/ld.so.conf.d/docker-openjdk.conf; \tldconfig; \t\tjava -Xshare:dump; \t\tjava --version"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:56.493239413Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV CATALINA_HOME=/usr/local/tomcat",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:56.592339446Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/bin:/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:57.135799132Z",
|
||||
"created_by": "/bin/sh -c mkdir -p \"$CATALINA_HOME\""
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:57.234962251Z",
|
||||
"created_by": "/bin/sh -c #(nop) WORKDIR /usr/local/tomcat",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:57.332478398Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:16:57.423152329Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:38:59.455604207Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:38:59.550766811Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_MAJOR=8",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:38:59.643674076Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_VERSION=8.5.77",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:38:59.744285526Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:39:00.204794279Z",
|
||||
"created_by": "/bin/sh -c #(nop) COPY dir:92f3a0f303b55a048a73bf243c664f89aa86500eab95c7d20c2da44ed3fb434b in /usr/local/tomcat "
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:39:03.786979035Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tapt-get update; \txargs -rt apt-get install -y --no-install-recommends \u003c \"$TOMCAT_NATIVE_LIBDIR/.dependencies.txt\"; \trm -rf /var/lib/apt/lists/*"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:39:05.151055599Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tnativeLines=\"$(catalina.sh configtest 2\u003e\u00261)\"; \tnativeLines=\"$(echo \"$nativeLines\" | grep 'Apache Tomcat Native')\"; \tnativeLines=\"$(echo \"$nativeLines\" | sort -u)\"; \tif ! echo \"$nativeLines\" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' \u003e\u00262; then \t\techo \u003e\u00262 \"$nativeLines\"; \t\texit 1; \tfi"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:39:05.243348189Z",
|
||||
"created_by": "/bin/sh -c #(nop) EXPOSE 8080",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:39:05.342897424Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"catalina.sh\" \"run\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-06-06T13:51:56.544179Z",
|
||||
"created_by": "/bin/sh -c #(nop) COPY file:4a1136b54136f8775efe918c4cd6af1ad1e507b36a49286d4f2c6bde722d33f4 in /usr/local/tomcat/webapps/ "
|
||||
}
|
||||
],
|
||||
"os": "linux",
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce",
|
||||
"sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72",
|
||||
"sha256:1f0e278ace87a84577de56c99e5c05c6af6f8b582d1eb8dfd7de7be4cf215775",
|
||||
"sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45",
|
||||
"sha256:8e6776c643c1db15d540016171fe04137ee2a26c7d0b18bfebdcbd31c6b0d8b3",
|
||||
"sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42",
|
||||
"sha256:19da2426772aaa344a242e474fd7906d272fc8ded6eef5b4e461a4aa0725d7e5",
|
||||
"sha256:1fdc094b0e85888d2204310083e3c09fff6a4daeecf22692aa6be5e8b4001f94",
|
||||
"sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"Cmd": [
|
||||
"catalina.sh",
|
||||
"run"
|
||||
],
|
||||
"Env": [
|
||||
"PATH=/usr/local/tomcat/bin:/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"JAVA_HOME=/usr/local/openjdk-11",
|
||||
"LANG=C.UTF-8",
|
||||
"JAVA_VERSION=11.0.14.1",
|
||||
"CATALINA_HOME=/usr/local/tomcat",
|
||||
"TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
|
||||
"LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
|
||||
"GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23",
|
||||
"TOMCAT_MAJOR=8",
|
||||
"TOMCAT_VERSION=8.5.77",
|
||||
"TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260"
|
||||
],
|
||||
"Image": "sha256:8ac2c9cef8f1bb48394c1b2ee81cc1d2096323a7a7cec4781d601eeaf7c32b03",
|
||||
"WorkingDir": "/usr/local/tomcat",
|
||||
"ExposedPorts": {
|
||||
"8080/tcp": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Results": [
|
||||
{
|
||||
"Target": "testdata/fixtures/images/spring4shell-jre11.tar.gz (debian 11.3)",
|
||||
"Class": "os-pkgs",
|
||||
"Type": "debian"
|
||||
},
|
||||
{
|
||||
"Target": "Java",
|
||||
"Class": "lang-pkgs",
|
||||
"Type": "jar",
|
||||
"Vulnerabilities": [
|
||||
{
|
||||
"VulnerabilityID": "CVE-2022-22965",
|
||||
"PkgName": "org.springframework:spring-beans",
|
||||
"PkgPath": "usr/local/tomcat/webapps/helloworld.war",
|
||||
"InstalledVersion": "5.3.15",
|
||||
"FixedVersion": "5.3.18",
|
||||
"Layer": {
|
||||
"Digest": "sha256:8eeeb69b4f5af871d1bc14ebb077b478a8260542f2c2a3897e8942bd90a8a62a",
|
||||
"DiffID": "sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f"
|
||||
},
|
||||
"SeveritySource": "ghsa",
|
||||
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965",
|
||||
"DataSource": {
|
||||
"ID": "ghsa",
|
||||
"Name": "GitHub Security Advisory Maven",
|
||||
"URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven"
|
||||
},
|
||||
"Title": "spring-framework: RCE via Data Binding on JDK 9+",
|
||||
"Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.",
|
||||
"Severity": "CRITICAL",
|
||||
"CweIDs": [
|
||||
"CWE-94"
|
||||
],
|
||||
"CVSS": {
|
||||
"ghsa": {
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V3Score": 9.8
|
||||
},
|
||||
"nvd": {
|
||||
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V2Score": 7.5,
|
||||
"V3Score": 9.8
|
||||
},
|
||||
"redhat": {
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V3Score": 8.1
|
||||
}
|
||||
},
|
||||
"References": [
|
||||
"https://github.com/advisories/GHSA-36p3-wjmg-h94x"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "",
|
||||
"Class": "custom",
|
||||
"CustomResources": [
|
||||
{
|
||||
"Type": "spring4shell/java-major-version",
|
||||
"FilePath": "/usr/local/openjdk-11/release",
|
||||
"Layer": {
|
||||
"Digest": "sha256:e94fd7d3bf7a9b78b61be8303cd35eb9da3f8d121cf572a3b8878cbf11e84818",
|
||||
"DiffID": "sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45"
|
||||
},
|
||||
"Data": "11.0.14.1"
|
||||
},
|
||||
{
|
||||
"Type": "spring4shell/tomcat-version",
|
||||
"FilePath": "/usr/local/tomcat/RELEASE-NOTES",
|
||||
"Layer": {
|
||||
"Digest": "sha256:ac3639dc6fd33e9eeead58a99c277cb06b8f69ba6a30fe7028e9677a67d94bd8",
|
||||
"DiffID": "sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42"
|
||||
},
|
||||
"Data": "8.5.77"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
266
integration/testdata/spring4shell-jre8.json.golden
vendored
Normal file
266
integration/testdata/spring4shell-jre8.json.golden
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"SchemaVersion": 2,
|
||||
"ArtifactName": "testdata/fixtures/images/spring4shell-jre8.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
"OS": {
|
||||
"Family": "debian",
|
||||
"Name": "11.3"
|
||||
},
|
||||
"ImageID": "sha256:b88bc3d2f0b5aacf1d36efa498f427d923b01c854dac090acf5368c55ac04fda",
|
||||
"DiffIDs": [
|
||||
"sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce",
|
||||
"sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72",
|
||||
"sha256:0e78b1e5673e8cc7c102fdda9e6b830b7dee2b29b178f34d25d9be59387e6950",
|
||||
"sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1",
|
||||
"sha256:053db4876c0df3df3294ee00e32e140b130ba33807d088750cb69b0e6fad158e",
|
||||
"sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6",
|
||||
"sha256:868d710aa4dc5fc4793508564fc45c991ed8d5f6ab3e4cf52bb856f29546f3d8",
|
||||
"sha256:77b2d158369254d5055183f5483f8b6661170857b61768d1d95d18c2ec1714b6",
|
||||
"sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5"
|
||||
],
|
||||
"ImageConfig": {
|
||||
"architecture": "amd64",
|
||||
"created": "2022-06-06T13:51:57.120019Z",
|
||||
"docker_version": "20.10.14",
|
||||
"history": [
|
||||
{
|
||||
"created": "2022-03-29T00:22:18.812238611Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:966d3669b40f5fbaecee1ecbeb58debe19001076da5d94717080d55efbc25971 in / "
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:22:19.186561403Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"bash\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:52:15.681202963Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates p11-kit \t; \trm -rf /var/lib/apt/lists/*"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:57:17.289858958Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/openjdk-8",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:57:17.842070347Z",
|
||||
"created_by": "/bin/sh -c { echo '#/bin/sh'; echo 'echo \"$JAVA_HOME\"'; } \u003e /usr/local/bin/docker-java-home \u0026\u0026 chmod +x /usr/local/bin/docker-java-home \u0026\u0026 [ \"$JAVA_HOME\" = \"$(docker-java-home)\" ] # backwards compatibility"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:57:17.930626834Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:57:18.024334574Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV LANG=C.UTF-8",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:57:18.115954625Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV JAVA_VERSION=8u322",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-29T00:58:15.033935002Z",
|
||||
"created_by": "/bin/sh -c set -eux; \t\tarch=\"$(dpkg --print-architecture)\"; \tcase \"$arch\" in \t\t'amd64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jre_x64_linux_8u322b06.tar.gz'; \t\t\t;; \t\t'arm64') \t\t\tdownloadUrl='https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jre_aarch64_linux_8u322b06.tar.gz'; \t\t\t;; \t\t*) echo \u003e\u00262 \"error: unsupported architecture: '$arch'\"; exit 1 ;; \tesac; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdirmngr \t\tgnupg \t\twget \t; \trm -rf /var/lib/apt/lists/*; \t\twget --progress=dot:giga -O openjdk.tgz \"$downloadUrl\"; \twget --progress=dot:giga -O openjdk.tgz.asc \"$downloadUrl.sign\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys EAC843EBD3EFDB98CC772FADA5CD6035332FA671; \tgpg --batch --keyserver keyserver.ubuntu.com --keyserver-options no-self-sigs-only --recv-keys CA5F11C6CE22644D42C6AC4492EF8D39DC13168F; \tgpg --batch --list-sigs --keyid-format 0xLONG CA5F11C6CE22644D42C6AC4492EF8D39DC13168F \t\t| tee /dev/stderr \t\t| grep '0xA5CD6035332FA671' \t\t| grep 'Andrew Haley'; \tgpg --batch --verify openjdk.tgz.asc openjdk.tgz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\"; \t\tmkdir -p \"$JAVA_HOME\"; \ttar --extract \t\t--file openjdk.tgz \t\t--directory \"$JAVA_HOME\" \t\t--strip-components 1 \t\t--no-same-owner \t; \trm openjdk.tgz*; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\t{ \t\techo '#!/usr/bin/env bash'; \t\techo 'set -Eeuo pipefail'; \t\techo 'trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth \"$JAVA_HOME/lib/security/cacerts\"'; \t} \u003e /etc/ca-certificates/update.d/docker-openjdk; \tchmod +x /etc/ca-certificates/update.d/docker-openjdk; \t/etc/ca-certificates/update.d/docker-openjdk; \t\tfind \"$JAVA_HOME/lib\" -name '*.so' -exec dirname '{}' ';' | sort -u \u003e /etc/ld.so.conf.d/docker-openjdk.conf; \tldconfig; \t\tjava -version"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:58.62001394Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV CATALINA_HOME=/usr/local/tomcat",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:58.713370816Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/bin:/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:59.23051474Z",
|
||||
"created_by": "/bin/sh -c mkdir -p \"$CATALINA_HOME\""
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:59.327279736Z",
|
||||
"created_by": "/bin/sh -c #(nop) WORKDIR /usr/local/tomcat",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:59.421537441Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:24:59.517366785Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:09.694872721Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:09.790546995Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_MAJOR=8",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:09.882411219Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_VERSION=8.5.77",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:09.972728447Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:10.425808771Z",
|
||||
"created_by": "/bin/sh -c #(nop) COPY dir:4b2b2ad5d081ef9a92d92841b7b643214ae6e21bbb961e3197c3615dd08813ab in /usr/local/tomcat "
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:13.894391571Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tapt-get update; \txargs -rt apt-get install -y --no-install-recommends \u003c \"$TOMCAT_NATIVE_LIBDIR/.dependencies.txt\"; \trm -rf /var/lib/apt/lists/*"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:15.098598192Z",
|
||||
"created_by": "/bin/sh -c set -eux; \tnativeLines=\"$(catalina.sh configtest 2\u003e\u00261)\"; \tnativeLines=\"$(echo \"$nativeLines\" | grep 'Apache Tomcat Native')\"; \tnativeLines=\"$(echo \"$nativeLines\" | sort -u)\"; \tif ! echo \"$nativeLines\" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' \u003e\u00262; then \t\techo \u003e\u00262 \"$nativeLines\"; \t\texit 1; \tfi"
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:15.18926788Z",
|
||||
"created_by": "/bin/sh -c #(nop) EXPOSE 8080",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-03-30T05:42:15.283787651Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"catalina.sh\" \"run\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-06-06T13:51:57.120019Z",
|
||||
"created_by": "/bin/sh -c #(nop) COPY file:4a1136b54136f8775efe918c4cd6af1ad1e507b36a49286d4f2c6bde722d33f4 in /usr/local/tomcat/webapps/ "
|
||||
}
|
||||
],
|
||||
"os": "linux",
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce",
|
||||
"sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72",
|
||||
"sha256:0e78b1e5673e8cc7c102fdda9e6b830b7dee2b29b178f34d25d9be59387e6950",
|
||||
"sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1",
|
||||
"sha256:053db4876c0df3df3294ee00e32e140b130ba33807d088750cb69b0e6fad158e",
|
||||
"sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6",
|
||||
"sha256:868d710aa4dc5fc4793508564fc45c991ed8d5f6ab3e4cf52bb856f29546f3d8",
|
||||
"sha256:77b2d158369254d5055183f5483f8b6661170857b61768d1d95d18c2ec1714b6",
|
||||
"sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"Cmd": [
|
||||
"catalina.sh",
|
||||
"run"
|
||||
],
|
||||
"Env": [
|
||||
"PATH=/usr/local/tomcat/bin:/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"JAVA_HOME=/usr/local/openjdk-8",
|
||||
"LANG=C.UTF-8",
|
||||
"JAVA_VERSION=8u322",
|
||||
"CATALINA_HOME=/usr/local/tomcat",
|
||||
"TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
|
||||
"LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
|
||||
"GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23",
|
||||
"TOMCAT_MAJOR=8",
|
||||
"TOMCAT_VERSION=8.5.77",
|
||||
"TOMCAT_SHA512=50f96584cbbbeeda92a3b573e7fe7e2c49e57ed4bc5246257dc1409abac0710b49fa7049a0dd9a3b8467bca2aa078ef608f49b676c1abf12529528ff71bb0260"
|
||||
],
|
||||
"Image": "sha256:d69eef03ed55cfa0d799181c477762549e7ce94deb23193f04e6bec0c23d0dfd",
|
||||
"WorkingDir": "/usr/local/tomcat",
|
||||
"ExposedPorts": {
|
||||
"8080/tcp": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Results": [
|
||||
{
|
||||
"Target": "testdata/fixtures/images/spring4shell-jre8.tar.gz (debian 11.3)",
|
||||
"Class": "os-pkgs",
|
||||
"Type": "debian"
|
||||
},
|
||||
{
|
||||
"Target": "Java",
|
||||
"Class": "lang-pkgs",
|
||||
"Type": "jar",
|
||||
"Vulnerabilities": [
|
||||
{
|
||||
"VulnerabilityID": "CVE-2022-22965",
|
||||
"PkgName": "org.springframework:spring-beans",
|
||||
"PkgPath": "usr/local/tomcat/webapps/helloworld.war",
|
||||
"InstalledVersion": "5.3.15",
|
||||
"FixedVersion": "5.3.18",
|
||||
"Layer": {
|
||||
"Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07",
|
||||
"DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5"
|
||||
},
|
||||
"SeveritySource": "ghsa",
|
||||
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-22965",
|
||||
"DataSource": {
|
||||
"ID": "ghsa",
|
||||
"Name": "GitHub Security Advisory Maven",
|
||||
"URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven"
|
||||
},
|
||||
"Title": "spring-framework: RCE via Data Binding on JDK 9+",
|
||||
"Description": "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.",
|
||||
"Severity": "LOW",
|
||||
"CweIDs": [
|
||||
"CWE-94"
|
||||
],
|
||||
"CVSS": {
|
||||
"ghsa": {
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V3Score": 9.8
|
||||
},
|
||||
"nvd": {
|
||||
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V2Score": 7.5,
|
||||
"V3Score": 9.8
|
||||
},
|
||||
"redhat": {
|
||||
"V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"V3Score": 8.1
|
||||
}
|
||||
},
|
||||
"References": [
|
||||
"https://github.com/advisories/GHSA-36p3-wjmg-h94x"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "",
|
||||
"Class": "custom",
|
||||
"CustomResources": [
|
||||
{
|
||||
"Type": "spring4shell/java-major-version",
|
||||
"FilePath": "/usr/local/openjdk-8/release",
|
||||
"Layer": {
|
||||
"Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791",
|
||||
"DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1"
|
||||
},
|
||||
"Data": "1.8.0_322"
|
||||
},
|
||||
{
|
||||
"Type": "spring4shell/tomcat-version",
|
||||
"FilePath": "/usr/local/tomcat/RELEASE-NOTES",
|
||||
"Layer": {
|
||||
"Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06",
|
||||
"DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6"
|
||||
},
|
||||
"Data": "8.5.77"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -95,6 +95,7 @@ nav:
|
||||
- AWS CodePipeline: docs/integrations/aws-codepipeline.md
|
||||
- AWS Security Hub: docs/integrations/aws-security-hub.md
|
||||
- Advanced:
|
||||
- Modules: docs/advanced/modules.md
|
||||
- Plugins: docs/advanced/plugins.md
|
||||
- Air-Gapped Environment: docs/advanced/air-gap.md
|
||||
- Container Image:
|
||||
@@ -120,7 +121,7 @@ nav:
|
||||
- Repository: docs/references/cli/repo.md
|
||||
- Client: docs/references/cli/client.md
|
||||
- Server: docs/references/cli/server.md
|
||||
- Plugins: docs/references/cli/plugins.md
|
||||
- Plugin: docs/references/cli/plugin.md
|
||||
- SBOM: docs/references/cli/sbom.md
|
||||
- Modes:
|
||||
- Standalone: docs/references/modes/standalone.md
|
||||
|
||||
2
pkg/cache/remote_test.go
vendored
2
pkg/cache/remote_test.go
vendored
@@ -328,7 +328,7 @@ func TestRemoteCache_PutArtifactInsecure(t *testing.T) {
|
||||
imageInfo: types.ArtifactInfo{},
|
||||
insecure: false,
|
||||
},
|
||||
wantErr: "certificate signed by unknown authority",
|
||||
wantErr: "failed to do request",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/module"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/plugin"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||
@@ -415,6 +416,7 @@ func NewApp(version string) *cli.App {
|
||||
NewServerCommand(),
|
||||
NewConfigCommand(),
|
||||
NewPluginCommand(),
|
||||
NewModuleCommand(),
|
||||
NewK8sCommand(),
|
||||
NewSbomCommand(),
|
||||
NewVersionCommand(),
|
||||
@@ -809,6 +811,31 @@ func NewPluginCommand() *cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// NewModuleCommand is the factory method to add module subcommand
|
||||
func NewModuleCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "module",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "manage modules",
|
||||
Subcommands: cli.Commands{
|
||||
{
|
||||
Name: "install",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "install a module",
|
||||
ArgsUsage: "REPOSITORY",
|
||||
Action: module.Install,
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "uninstall a module",
|
||||
ArgsUsage: "REPOSITORY",
|
||||
Action: module.Uninstall,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewK8sCommand is the factory method to add k8s subcommand
|
||||
func NewK8sCommand() *cli.Command {
|
||||
k8sSecurityChecksFlag := withValue(
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
)
|
||||
@@ -50,11 +49,6 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
func initializeResultClient() result.Client {
|
||||
wire.Build(result.SuperSet)
|
||||
return result.Client{}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Client/Server
|
||||
/////////////////
|
||||
@@ -82,8 +76,3 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac
|
||||
wire.Build(scanner.RemoteFilesystemSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
func initializeRemoteResultClient() result.Client {
|
||||
wire.Build(result.SuperSet)
|
||||
return result.Client{}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
tcache "github.com/aquasecurity/trivy/pkg/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
@@ -78,12 +80,15 @@ type Runner interface {
|
||||
// Report a writes a report
|
||||
Report(opt Option, report types.Report) error
|
||||
// Close closes runner
|
||||
Close() error
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
type runner struct {
|
||||
cache cache.Cache
|
||||
dbOpen bool
|
||||
|
||||
// WASM modules
|
||||
module *module.Manager
|
||||
}
|
||||
|
||||
type runnerOption func(*runner)
|
||||
@@ -116,11 +121,19 @@ func NewRunner(cliOption Option, opts ...runnerOption) (Runner, error) {
|
||||
return nil, xerrors.Errorf("DB error: %w", err)
|
||||
}
|
||||
|
||||
// Initialize WASM modules
|
||||
m, err := module.NewManager(cliOption.Context.Context)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("WASM module error: %w", err)
|
||||
}
|
||||
m.Register()
|
||||
r.module = m
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Close closes everything
|
||||
func (r *runner) Close() error {
|
||||
func (r *runner) Close(ctx context.Context) error {
|
||||
var errs error
|
||||
if err := r.cache.Close(); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
@@ -131,6 +144,10 @@ func (r *runner) Close() error {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.module.Close(ctx); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -204,14 +221,11 @@ func (r *runner) scanArtifact(ctx context.Context, opt Option, initializeScanner
|
||||
}
|
||||
|
||||
func (r *runner) Filter(ctx context.Context, opt Option, report types.Report) (types.Report, error) {
|
||||
resultClient := initializeResultClient()
|
||||
results := report.Results
|
||||
|
||||
// Filter results
|
||||
for i := range results {
|
||||
// Fill vulnerability info only in standalone mode
|
||||
if opt.RemoteAddr == "" {
|
||||
resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
|
||||
}
|
||||
vulns, misconfSummary, misconfs, secrets, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
|
||||
vulns, misconfSummary, misconfs, secrets, err := result.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
|
||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
||||
@@ -324,41 +338,41 @@ func run(ctx context.Context, opt Option, artifactType ArtifactType) (err error)
|
||||
}
|
||||
}()
|
||||
|
||||
runner, err := NewRunner(opt)
|
||||
r, err := NewRunner(opt)
|
||||
if err != nil {
|
||||
if errors.Is(err, SkipScan) {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("init error: %w", err)
|
||||
}
|
||||
defer runner.Close()
|
||||
defer r.Close(ctx)
|
||||
|
||||
var report types.Report
|
||||
switch artifactType {
|
||||
case containerImageArtifact, imageArchiveArtifact:
|
||||
if report, err = runner.ScanImage(ctx, opt); err != nil {
|
||||
if report, err = r.ScanImage(ctx, opt); err != nil {
|
||||
return xerrors.Errorf("image scan error: %w", err)
|
||||
}
|
||||
case filesystemArtifact:
|
||||
if report, err = runner.ScanFilesystem(ctx, opt); err != nil {
|
||||
if report, err = r.ScanFilesystem(ctx, opt); err != nil {
|
||||
return xerrors.Errorf("filesystem scan error: %w", err)
|
||||
}
|
||||
case rootfsArtifact:
|
||||
if report, err = runner.ScanRootfs(ctx, opt); err != nil {
|
||||
if report, err = r.ScanRootfs(ctx, opt); err != nil {
|
||||
return xerrors.Errorf("rootfs scan error: %w", err)
|
||||
}
|
||||
case repositoryArtifact:
|
||||
if report, err = runner.ScanRepository(ctx, opt); err != nil {
|
||||
if report, err = r.ScanRepository(ctx, opt); err != nil {
|
||||
return xerrors.Errorf("repository scan error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
report, err = runner.Filter(ctx, opt, report)
|
||||
report, err = r.Filter(ctx, opt, report)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("filter error: %w", err)
|
||||
}
|
||||
|
||||
if err = runner.Report(opt, report); err != nil {
|
||||
if err = r.Report(opt, report); err != nil {
|
||||
return xerrors.Errorf("report error: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
@@ -31,7 +31,9 @@ import (
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
localScanner := local.NewScanner(applierApplier, detector, client)
|
||||
typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, dockerOpt)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
@@ -52,7 +54,9 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
localScanner := local.NewScanner(applierApplier, detector, client)
|
||||
typesImage, err := image.NewArchiveImage(filePath)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
@@ -69,7 +73,9 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
localScanner := local.NewScanner(applierApplier, detector, client)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
@@ -82,7 +88,9 @@ func initializeFilesystemScanner(ctx context.Context, path string, artifactCache
|
||||
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
localScanner := local.NewScanner(applierApplier, detector, client)
|
||||
artifactArtifact, cleanup, err := remote.NewArtifact(url, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
@@ -93,12 +101,6 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initializeResultClient() result.Client {
|
||||
config := db.Config{}
|
||||
client := result.NewClient(config)
|
||||
return client
|
||||
}
|
||||
|
||||
// initializeRemoteDockerScanner is for container image scanning in client/server mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
@@ -152,9 +154,3 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac
|
||||
return scannerScanner, func() {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initializeRemoteResultClient() result.Client {
|
||||
config := db.Config{}
|
||||
resultClient := result.NewClient(config)
|
||||
return resultClient
|
||||
}
|
||||
|
||||
58
pkg/commands/module/module.go
Normal file
58
pkg/commands/module/module.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
)
|
||||
|
||||
// Install installs a module
|
||||
func Install(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
||||
}
|
||||
|
||||
if err := initLogger(c); err != nil {
|
||||
return xerrors.Errorf("log initialization error: %w", err)
|
||||
}
|
||||
|
||||
repo := c.Args().First()
|
||||
if err := module.Install(c.Context, repo, c.Bool("quiet"), c.Bool("insecure")); err != nil {
|
||||
return xerrors.Errorf("module installation error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uninstall uninstalls a module
|
||||
func Uninstall(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
||||
}
|
||||
|
||||
if err := initLogger(c); err != nil {
|
||||
return xerrors.Errorf("log initialization error: %w", err)
|
||||
}
|
||||
|
||||
repo := c.Args().First()
|
||||
if err := module.Uninstall(c.Context, repo); err != nil {
|
||||
return xerrors.Errorf("module uninstall error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initLogger(ctx *cli.Context) error {
|
||||
conf, err := option.NewGlobalOption(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("config error: %w", err)
|
||||
}
|
||||
|
||||
if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
|
||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (c *ReportOption) populateVulnTypes() error {
|
||||
}
|
||||
|
||||
for _, v := range strings.Split(c.vulnType, ",") {
|
||||
if types.NewVulnType(v) == types.VulnTypeUnknown {
|
||||
if !slices.Contains(types.VulnTypes, v) {
|
||||
return xerrors.Errorf("unknown vulnerability type (%s)", v)
|
||||
}
|
||||
c.VulnType = append(c.VulnType, v)
|
||||
@@ -128,7 +128,7 @@ func (c *ReportOption) populateSecurityChecks() error {
|
||||
}
|
||||
|
||||
for _, v := range strings.Split(c.securityChecks, ",") {
|
||||
if types.NewSecurityCheck(v) == types.SecurityCheckUnknown {
|
||||
if !slices.Contains(types.SecurityChecks, v) {
|
||||
return xerrors.Errorf("unknown security check (%s)", v)
|
||||
}
|
||||
c.SecurityChecks = append(c.SecurityChecks, v)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
@@ -52,6 +53,13 @@ func run(c Option) (err error) {
|
||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||
}
|
||||
|
||||
// Initialize WASM modules
|
||||
m, err := module.NewManager(c.Context.Context)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("WASM module error: %w", err)
|
||||
}
|
||||
m.Register()
|
||||
|
||||
server := rpcServer.NewServer(c.AppVersion, c.Listen, c.CacheDir, c.Token, c.TokenHeader)
|
||||
return server.ListenAndServe(cache, c.Insecure)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
fakei "github.com/google/go-containerregistry/pkg/v1/fake"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
@@ -201,6 +201,20 @@ func TestClient_Download(t *testing.T) {
|
||||
// Mock image
|
||||
img := new(fakei.FakeImage)
|
||||
img.LayersReturns([]v1.Layer{newFakeLayer(t, tt.input)}, nil)
|
||||
img.ManifestReturns(&v1.Manifest{
|
||||
Layers: []v1.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip",
|
||||
Size: 100,
|
||||
Digest: v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "aec482bc254b5dd025d3eaf5bb35997d3dba783e394e8f91d5a415963151bfb8"},
|
||||
Annotations: map[string]string{
|
||||
"org.opencontainers.image.title": "db.tar.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
// Mock OCI artifact
|
||||
art, err := oci.NewArtifact("db", mediaType, true, false, oci.WithImage(img))
|
||||
|
||||
@@ -3,7 +3,6 @@ package ospkg
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
fos "github.com/aquasecurity/fanal/analyzer/os"
|
||||
@@ -27,12 +26,6 @@ var (
|
||||
// ErrUnsupportedOS defines error for unsupported OS
|
||||
ErrUnsupportedOS = xerrors.New("unsupported os")
|
||||
|
||||
// SuperSet binds dependencies for OS scan
|
||||
SuperSet = wire.NewSet(
|
||||
wire.Struct(new(Detector)),
|
||||
wire.Bind(new(Operation), new(Detector)),
|
||||
)
|
||||
|
||||
drivers = map[string]Driver{
|
||||
fos.Alpine: alpine.NewScanner(),
|
||||
fos.Alma: alma.NewScanner(),
|
||||
@@ -55,11 +48,6 @@ func RegisterDriver(name string, driver Driver) {
|
||||
drivers[name] = driver
|
||||
}
|
||||
|
||||
// Operation defines operation of OSpkg scan
|
||||
type Operation interface {
|
||||
Detect(string, string, string, *ftypes.Repository, time.Time, []ftypes.Package) ([]types.DetectedVulnerability, bool, error)
|
||||
}
|
||||
|
||||
// Driver defines operations for OS package scan
|
||||
type Driver interface {
|
||||
Detect(string, *ftypes.Repository, []ftypes.Package) ([]types.DetectedVulnerability, error)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type DetectInput struct {
|
||||
ImageName string
|
||||
OSFamily string
|
||||
OSName string
|
||||
Created time.Time
|
||||
Pkgs []ftypes.Package
|
||||
}
|
||||
type DetectOutput struct {
|
||||
Vulns []types.DetectedVulnerability
|
||||
Eosl bool
|
||||
Err error
|
||||
}
|
||||
type DetectExpectation struct {
|
||||
Args DetectInput
|
||||
ReturnArgs DetectOutput
|
||||
}
|
||||
|
||||
func NewMockDetector(detectExpectations []DetectExpectation) *MockDetector {
|
||||
mockDetector := new(MockDetector)
|
||||
for _, e := range detectExpectations {
|
||||
mockDetector.On("Detect", e.Args.ImageName, e.Args.OSFamily, e.Args.OSName, e.Args.Created, e.Args.Pkgs).Return(
|
||||
e.ReturnArgs.Vulns, e.ReturnArgs.Eosl, e.ReturnArgs.Err)
|
||||
}
|
||||
return mockDetector
|
||||
}
|
||||
|
||||
func (_m *MockDetector) Detect(a string, b string, c string, d time.Time, e []ftypes.Package) ([]types.DetectedVulnerability, bool, error) {
|
||||
ret := _m.Called(a, b, c, d, e)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, false, ret.Error(2)
|
||||
}
|
||||
vulns, ok := ret0.([]types.DetectedVulnerability)
|
||||
if !ok {
|
||||
return nil, false, ret.Error(2)
|
||||
}
|
||||
return vulns, ret.Bool(1), ret.Error(2)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
|
||||
return xerrors.Errorf("init error: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := runner.Close(); err != nil {
|
||||
if err := runner.Close(ctx); err != nil {
|
||||
log.Logger.Errorf("failed to close runner: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
26
pkg/module/api/api.go
Normal file
26
pkg/module/api/api.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package api
|
||||
|
||||
import "github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
|
||||
const (
|
||||
Version = 1
|
||||
|
||||
ActionInsert serialize.PostScanAction = "INSERT"
|
||||
ActionUpdate serialize.PostScanAction = "UPDATE"
|
||||
ActionDelete serialize.PostScanAction = "DELETE"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Version() int
|
||||
Name() string
|
||||
}
|
||||
|
||||
type Analyzer interface {
|
||||
RequiredFiles() []string
|
||||
Analyze(filePath string) (*serialize.AnalysisResult, error)
|
||||
}
|
||||
|
||||
type PostScanner interface {
|
||||
PostScanSpec() serialize.PostScanSpec
|
||||
PostScan(serialize.Results) (serialize.Results, error)
|
||||
}
|
||||
54
pkg/module/command.go
Normal file
54
pkg/module/command.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/oci"
|
||||
)
|
||||
|
||||
const mediaType = "application/vnd.module.wasm.content.layer.v1+wasm"
|
||||
|
||||
// Install installs a module
|
||||
func Install(ctx context.Context, repo string, quiet, insecure bool) error {
|
||||
ref, err := name.ParseReference(repo)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("repository parse error: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Infof("Installing the module from %s...", repo)
|
||||
artifact, err := oci.NewArtifact(repo, mediaType, quiet, insecure)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("module initialize error: %w", err)
|
||||
}
|
||||
|
||||
dst := filepath.Join(dir(), ref.Context().Name())
|
||||
log.Logger.Debugf("Installing the module to %s...", dst)
|
||||
|
||||
if err = artifact.Download(ctx, dst); err != nil {
|
||||
return xerrors.Errorf("module download error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uninstall uninstalls a module
|
||||
func Uninstall(_ context.Context, repo string) error {
|
||||
ref, err := name.ParseReference(repo)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("repository parse error: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Infof("Uninstalling %s ...", repo)
|
||||
dst := filepath.Join(dir(), ref.Context().Name())
|
||||
if err = os.RemoveAll(dst); err != nil {
|
||||
return xerrors.Errorf("remove error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
715
pkg/module/module.go
Normal file
715
pkg/module/module.go
Normal file
@@ -0,0 +1,715 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/liamg/memoryfs"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/samber/lo"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
wasi "github.com/tetratelabs/wazero/wasi_snapshot_preview1"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
tapi "github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/post"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
exportFunctions = map[string]interface{}{
|
||||
"debug": logDebug,
|
||||
"info": logInfo,
|
||||
"warn": logWarn,
|
||||
"error": logError,
|
||||
}
|
||||
|
||||
RelativeDir = filepath.Join(".trivy", "modules")
|
||||
)
|
||||
|
||||
func logDebug(ctx context.Context, m api.Module, offset, size uint32) {
|
||||
buf := readMemory(ctx, m, offset, size)
|
||||
if buf != nil {
|
||||
log.Logger.Debug(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func logInfo(ctx context.Context, m api.Module, offset, size uint32) {
|
||||
buf := readMemory(ctx, m, offset, size)
|
||||
if buf != nil {
|
||||
log.Logger.Info(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func logWarn(ctx context.Context, m api.Module, offset, size uint32) {
|
||||
buf := readMemory(ctx, m, offset, size)
|
||||
if buf != nil {
|
||||
log.Logger.Warn(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func logError(ctx context.Context, m api.Module, offset, size uint32) {
|
||||
buf := readMemory(ctx, m, offset, size)
|
||||
if buf != nil {
|
||||
log.Logger.Error(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func readMemory(ctx context.Context, m api.Module, offset, size uint32) []byte {
|
||||
buf, ok := m.Memory().Read(ctx, offset, size)
|
||||
if !ok {
|
||||
log.Logger.Errorf("Memory.Read(%d, %d) out of range", offset, size)
|
||||
return nil
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
runtime wazero.Runtime
|
||||
modules []*wasmModule
|
||||
}
|
||||
|
||||
func NewManager(ctx context.Context) (*Manager, error) {
|
||||
m := &Manager{}
|
||||
|
||||
// Create a new WebAssembly Runtime.
|
||||
m.runtime = wazero.NewRuntime()
|
||||
|
||||
// Load WASM modules in local
|
||||
if err := m.loadModules(ctx); err != nil {
|
||||
return nil, xerrors.Errorf("module load error: %w", err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Manager) loadModules(ctx context.Context) error {
|
||||
moduleDir := dir()
|
||||
_, err := os.Stat(moduleDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
log.Logger.Debugf("Module dir: %s", moduleDir)
|
||||
|
||||
err = filepath.Walk(moduleDir, func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if info.IsDir() || filepath.Ext(info.Name()) != ".wasm" {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(moduleDir, path)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get a relative path: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Infof("Loading %s...", rel)
|
||||
wasmCode, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("file read error: %w", err)
|
||||
}
|
||||
|
||||
p, err := newWASMPlugin(ctx, m.runtime, wasmCode)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("WASM module init error %s: %w", rel, err)
|
||||
}
|
||||
|
||||
m.modules = append(m.modules, p)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("module walk error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Register() {
|
||||
for _, mod := range m.modules {
|
||||
mod.Register()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Close(ctx context.Context) error {
|
||||
var errs error
|
||||
if err := m.runtime.Close(ctx); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
for _, p := range m.modules {
|
||||
if err := p.Close(ctx); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func splitPtrSize(u uint64) (uint32, uint32) {
|
||||
ptr := uint32(u >> 32)
|
||||
size := uint32(u)
|
||||
return ptr, size
|
||||
}
|
||||
|
||||
func ptrSizeToString(ctx context.Context, m api.Module, ptrSize uint64) (string, error) {
|
||||
ptr, size := splitPtrSize(ptrSize)
|
||||
buf := readMemory(ctx, m, ptr, size)
|
||||
if buf == nil {
|
||||
return "", xerrors.New("unable to read memory")
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// stringToPtr returns a pointer and size pair for the given string in a way compatible with WebAssembly numeric types.
|
||||
func stringToPtrSize(ctx context.Context, s string, mod api.Module, malloc api.Function) (uint64, uint64, error) {
|
||||
size := uint64(len(s))
|
||||
results, err := malloc.Call(ctx, size)
|
||||
if err != nil {
|
||||
return 0, 0, xerrors.Errorf("malloc error: %w", err)
|
||||
}
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the string.
|
||||
ptr := results[0]
|
||||
if !mod.Memory().Write(ctx, uint32(ptr), []byte(s)) {
|
||||
return 0, 0, xerrors.Errorf("Memory.Write(%d, %d) out of range of memory size %d",
|
||||
ptr, size, mod.Memory().Size(ctx))
|
||||
}
|
||||
|
||||
return ptr, size, nil
|
||||
}
|
||||
|
||||
func unmarshal(ctx context.Context, m api.Module, ptrSize uint64, v any) error {
|
||||
ptr, size := splitPtrSize(ptrSize)
|
||||
buf := readMemory(ctx, m, ptr, size)
|
||||
if buf == nil {
|
||||
return xerrors.New("unable to read memory")
|
||||
}
|
||||
if err := json.Unmarshal(buf, v); err != nil {
|
||||
return xerrors.Errorf("unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshal(ctx context.Context, m api.Module, malloc api.Function, v easyjson.Marshaler) (uint64, uint64, error) {
|
||||
b, err := easyjson.Marshal(v)
|
||||
if err != nil {
|
||||
return 0, 0, xerrors.Errorf("marshal error: %w", err)
|
||||
}
|
||||
|
||||
size := uint64(len(b))
|
||||
results, err := malloc.Call(ctx, size)
|
||||
if err != nil {
|
||||
return 0, 0, xerrors.Errorf("malloc error: %w", err)
|
||||
}
|
||||
|
||||
// The pointer is a linear memory offset, which is where we write the marshaled value.
|
||||
ptr := results[0]
|
||||
if !m.Memory().Write(ctx, uint32(ptr), b) {
|
||||
return 0, 0, xerrors.Errorf("Memory.Write(%d, %d) out of range of memory size %d",
|
||||
ptr, size, m.Memory().Size(ctx))
|
||||
}
|
||||
|
||||
return ptr, size, nil
|
||||
}
|
||||
|
||||
type wasmModule struct {
|
||||
mod api.Module
|
||||
|
||||
name string
|
||||
version int
|
||||
requiredFiles []*regexp.Regexp
|
||||
|
||||
isAnalyzer bool
|
||||
isPostScanner bool
|
||||
postScanSpec serialize.PostScanSpec
|
||||
|
||||
// Exported functions
|
||||
analyze api.Function
|
||||
postScan api.Function
|
||||
malloc api.Function // TinyGo specific
|
||||
free api.Function // TinyGo specific
|
||||
}
|
||||
|
||||
func newWASMPlugin(ctx context.Context, r wazero.Runtime, code []byte) (*wasmModule, error) {
|
||||
// Combine the above into our baseline config, overriding defaults (which discard stdout and have no file system).
|
||||
config := wazero.NewModuleConfig().WithStdout(os.Stdout).WithFS(memoryfs.New())
|
||||
|
||||
// Create an empty namespace so that multiple modules will not conflict
|
||||
ns := r.NewNamespace(ctx)
|
||||
|
||||
// Instantiate a Go-defined module named "env" that exports functions.
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
ExportMemory("mem", 100).
|
||||
ExportFunctions(exportFunctions).
|
||||
Instantiate(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("wasm module build error: %w", err)
|
||||
}
|
||||
|
||||
if _, err = wasi.NewBuilder(r).Instantiate(ctx, ns); err != nil {
|
||||
return nil, xerrors.Errorf("WASI init error: %w", err)
|
||||
}
|
||||
|
||||
// Compile the WebAssembly module using the default configuration.
|
||||
compiled, err := r.CompileModule(ctx, code, wazero.NewCompileConfig())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("module compile error: %w", err)
|
||||
}
|
||||
|
||||
// InstantiateModule runs the "_start" function which is what TinyGo compiles "main" to.
|
||||
mod, err := ns.InstantiateModule(ctx, compiled, config)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("module init error: %w", err)
|
||||
}
|
||||
|
||||
// These are undocumented, but exported. See tinygo-org/tinygo#2788
|
||||
// TODO: improve TinyGo specific code
|
||||
malloc := mod.ExportedFunction("malloc")
|
||||
free := mod.ExportedFunction("free")
|
||||
|
||||
// Get a module name
|
||||
name, err := moduleName(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get a module name: %w", err)
|
||||
}
|
||||
|
||||
// Get a module version
|
||||
version, err := moduleVersion(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get a module version: %w", err)
|
||||
}
|
||||
|
||||
// Get a module API version
|
||||
apiVersion, err := moduleAPIVersion(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get a module version: %w", err)
|
||||
}
|
||||
|
||||
if apiVersion != tapi.Version {
|
||||
log.Logger.Infof("Ignore %s@v%d module due to API version mismatch, got: %d, want: %d",
|
||||
name, version, apiVersion, tapi.Version)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isAnalyzer, err := moduleIsAnalyzer(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to check if the module is an analyzer: %w", err)
|
||||
}
|
||||
|
||||
isPostScanner, err := moduleIsPostScanner(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to check if the module is a post scanner: %w", err)
|
||||
}
|
||||
|
||||
// Get exported functions by WASM module
|
||||
analyzeFunc := mod.ExportedFunction("analyze")
|
||||
if analyzeFunc == nil {
|
||||
return nil, xerrors.New("analyze() must be exported")
|
||||
}
|
||||
postScanFunc := mod.ExportedFunction("post_scan")
|
||||
if postScanFunc == nil {
|
||||
return nil, xerrors.New("post_scan() must be exported")
|
||||
}
|
||||
|
||||
var requiredFiles []*regexp.Regexp
|
||||
if isAnalyzer {
|
||||
// Get required files
|
||||
requiredFiles, err = moduleRequiredFiles(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get required files: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var postScanSpec serialize.PostScanSpec
|
||||
if isPostScanner {
|
||||
// This spec defines how the module works in post scanning like INSERT, UPDATE and DELETE.
|
||||
postScanSpec, err = modulePostScanSpec(ctx, mod)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get a post scan spec: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &wasmModule{
|
||||
mod: mod,
|
||||
name: name,
|
||||
version: version,
|
||||
requiredFiles: requiredFiles,
|
||||
|
||||
isAnalyzer: isAnalyzer,
|
||||
isPostScanner: isPostScanner,
|
||||
postScanSpec: postScanSpec,
|
||||
|
||||
analyze: analyzeFunc,
|
||||
postScan: postScanFunc,
|
||||
malloc: malloc,
|
||||
free: free,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *wasmModule) Register() {
|
||||
log.Logger.Infof("Registering WASM module: %s@v%d", m.name, m.version)
|
||||
if m.isAnalyzer {
|
||||
log.Logger.Debugf("Registering custom analyzer in %s@v%d", m.name, m.version)
|
||||
analyzer.RegisterAnalyzer(m)
|
||||
}
|
||||
if m.isPostScanner {
|
||||
log.Logger.Debugf("Registering custom post scanner in %s@v%d", m.name, m.version)
|
||||
post.RegisterPostScanner(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *wasmModule) Close(ctx context.Context) error {
|
||||
return m.mod.Close(ctx)
|
||||
}
|
||||
|
||||
func (m *wasmModule) Type() analyzer.Type {
|
||||
return analyzer.Type(m.name)
|
||||
}
|
||||
|
||||
func (m *wasmModule) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func (m *wasmModule) Version() int {
|
||||
return m.version
|
||||
}
|
||||
|
||||
func (m *wasmModule) Required(filePath string, _ os.FileInfo) bool {
|
||||
for _, r := range m.requiredFiles {
|
||||
if r.MatchString(filePath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *wasmModule) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
|
||||
filePath := "/" + filepath.ToSlash(input.FilePath)
|
||||
log.Logger.Debugf("Module %s: analyzing %s...", m.name, filePath)
|
||||
|
||||
memfs := memoryfs.New()
|
||||
if err := memfs.MkdirAll(filepath.Dir(filePath), fs.ModePerm); err != nil {
|
||||
return nil, xerrors.Errorf("memory fs mkdir error: %w", err)
|
||||
}
|
||||
err := memfs.WriteLazyFile(filePath, func() (io.Reader, error) {
|
||||
return input.Content, nil
|
||||
}, fs.ModePerm)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("memory fs write error: %w", err)
|
||||
}
|
||||
|
||||
// Pass memory fs to the analyze() function
|
||||
ctx, closer, err := experimental.WithFS(ctx, memfs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fs error: %w", err)
|
||||
}
|
||||
defer closer.Close(ctx)
|
||||
|
||||
inputPtr, inputSize, err := stringToPtrSize(ctx, filePath, m.mod, m.malloc)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to write string to memory: %w", err)
|
||||
}
|
||||
defer m.free.Call(ctx, inputPtr) // nolint: errcheck
|
||||
|
||||
analyzeRes, err := m.analyze.Call(ctx, inputPtr, inputSize)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("analyze error: %w", err)
|
||||
} else if len(analyzeRes) != 1 {
|
||||
return nil, xerrors.New("invalid signature: analyze")
|
||||
}
|
||||
|
||||
var result analyzer.AnalysisResult
|
||||
if err = unmarshal(ctx, m.mod, analyzeRes[0], &result); err != nil {
|
||||
return nil, xerrors.Errorf("invalid return value: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// PostScan performs post scanning
|
||||
// e.g. Remove a vulnerability, change severity, etc.
|
||||
func (m *wasmModule) PostScan(ctx context.Context, results types.Results) (types.Results, error) {
|
||||
// Find custom resources
|
||||
var custom serialize.Result
|
||||
for _, result := range results {
|
||||
if result.Class == types.ClassCustom {
|
||||
custom = serialize.Result(result)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
arg := serialize.Results{custom}
|
||||
switch m.postScanSpec.Action {
|
||||
case tapi.ActionUpdate, tapi.ActionDelete:
|
||||
// Pass the relevant results to the module
|
||||
arg = append(arg, findIDs(m.postScanSpec.IDs, results)...)
|
||||
}
|
||||
|
||||
// Marshal the argument into WASM memory so that the WASM module can read it.
|
||||
inputPtr, inputSize, err := marshal(ctx, m.mod, m.malloc, arg)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("post scan marshal error: %w", err)
|
||||
}
|
||||
defer m.free.Call(ctx, inputPtr) //nolint: errcheck
|
||||
|
||||
analyzeRes, err := m.postScan.Call(ctx, inputPtr, inputSize)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("post scan invocation error: %w", err)
|
||||
} else if len(analyzeRes) != 1 {
|
||||
return nil, xerrors.New("invalid signature: post_scan")
|
||||
}
|
||||
|
||||
var got types.Results
|
||||
if err = unmarshal(ctx, m.mod, analyzeRes[0], &got); err != nil {
|
||||
return nil, xerrors.Errorf("post scan unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
switch m.postScanSpec.Action {
|
||||
case tapi.ActionInsert:
|
||||
results = append(results, lo.Filter(got, func(r types.Result, _ int) bool {
|
||||
return r.Class != types.ClassCustom
|
||||
})...)
|
||||
case tapi.ActionUpdate:
|
||||
updateResults(got, results)
|
||||
case tapi.ActionDelete:
|
||||
deleteResults(got, results)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func findIDs(ids []string, results types.Results) serialize.Results {
|
||||
var filtered serialize.Results
|
||||
for _, result := range results {
|
||||
if result.Class == types.ClassCustom {
|
||||
continue
|
||||
}
|
||||
vulns := lo.Filter(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) bool {
|
||||
return slices.Contains(ids, v.VulnerabilityID)
|
||||
})
|
||||
misconfs := lo.Filter(result.Misconfigurations, func(m types.DetectedMisconfiguration, _ int) bool {
|
||||
return slices.Contains(ids, m.ID)
|
||||
})
|
||||
if len(vulns) > 0 || len(misconfs) > 0 {
|
||||
filtered = append(filtered, serialize.Result{
|
||||
Target: result.Target,
|
||||
Class: result.Class,
|
||||
Type: result.Type,
|
||||
Vulnerabilities: vulns,
|
||||
Misconfigurations: misconfs,
|
||||
})
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func updateResults(gotResults, results types.Results) {
|
||||
for _, g := range gotResults {
|
||||
for i, result := range results {
|
||||
if g.Target == result.Target && g.Class == result.Class && g.Type == result.Type {
|
||||
results[i].Vulnerabilities = lo.Map(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) types.DetectedVulnerability {
|
||||
// Update vulnerabilities in the existing result
|
||||
for _, got := range g.Vulnerabilities {
|
||||
if got.VulnerabilityID == v.VulnerabilityID && got.PkgName == v.PkgName &&
|
||||
got.PkgPath == v.PkgPath && got.InstalledVersion == v.InstalledVersion {
|
||||
|
||||
// Override vulnerability details
|
||||
v.SeveritySource = got.SeveritySource
|
||||
v.Vulnerability = got.Vulnerability
|
||||
}
|
||||
}
|
||||
return v
|
||||
})
|
||||
|
||||
results[i].Misconfigurations = lo.Map(result.Misconfigurations, func(m types.DetectedMisconfiguration, _ int) types.DetectedMisconfiguration {
|
||||
// Update misconfigurations in the existing result
|
||||
for _, got := range g.Misconfigurations {
|
||||
if got.ID == m.ID &&
|
||||
got.CauseMetadata.StartLine == m.CauseMetadata.StartLine &&
|
||||
got.CauseMetadata.EndLine == m.CauseMetadata.EndLine {
|
||||
|
||||
// Override misconfiguration details
|
||||
m.CauseMetadata = got.CauseMetadata
|
||||
m.Severity = got.Severity
|
||||
m.Status = got.Status
|
||||
}
|
||||
}
|
||||
return m
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteResults(gotResults, results types.Results) {
|
||||
for _, gotResult := range gotResults {
|
||||
for i, result := range results {
|
||||
// Remove vulnerabilities and misconfigurations from the existing result
|
||||
if gotResult.Target == result.Target && gotResult.Class == result.Class && gotResult.Type == result.Type {
|
||||
results[i].Vulnerabilities = lo.Reject(result.Vulnerabilities, func(v types.DetectedVulnerability, _ int) bool {
|
||||
for _, got := range gotResult.Vulnerabilities {
|
||||
if got.VulnerabilityID == v.VulnerabilityID && got.PkgName == v.PkgName &&
|
||||
got.PkgPath == v.PkgPath && got.InstalledVersion == v.InstalledVersion {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
results[i].Misconfigurations = lo.Reject(result.Misconfigurations, func(v types.DetectedMisconfiguration, _ int) bool {
|
||||
for _, got := range gotResult.Misconfigurations {
|
||||
if got.ID == v.ID && got.Status == v.Status &&
|
||||
got.CauseMetadata.StartLine == v.CauseMetadata.StartLine &&
|
||||
got.CauseMetadata.EndLine == v.CauseMetadata.EndLine {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func moduleName(ctx context.Context, mod api.Module) (string, error) {
|
||||
nameFunc := mod.ExportedFunction("name")
|
||||
if nameFunc == nil {
|
||||
return "", xerrors.New("name() must be exported")
|
||||
}
|
||||
|
||||
nameRes, err := nameFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("wasm function name() invocation error: %w", err)
|
||||
} else if len(nameRes) != 1 {
|
||||
return "", xerrors.New("invalid signature: name()")
|
||||
}
|
||||
|
||||
name, err := ptrSizeToString(ctx, mod, nameRes[0])
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("invalid return value: %w", err)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func moduleVersion(ctx context.Context, mod api.Module) (int, error) {
|
||||
versionFunc := mod.ExportedFunction("version")
|
||||
if versionFunc == nil {
|
||||
return 0, xerrors.New("version() must be exported")
|
||||
}
|
||||
|
||||
versionRes, err := versionFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("wasm function version() invocation error: %w", err)
|
||||
} else if len(versionRes) != 1 {
|
||||
return 0, xerrors.New("invalid signature: version")
|
||||
}
|
||||
|
||||
return int(versionRes[0]), nil
|
||||
}
|
||||
|
||||
func moduleAPIVersion(ctx context.Context, mod api.Module) (int, error) {
|
||||
versionFunc := mod.ExportedFunction("api_version")
|
||||
if versionFunc == nil {
|
||||
return 0, xerrors.New("api_version() must be exported")
|
||||
}
|
||||
|
||||
versionRes, err := versionFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("wasm function api_version() invocation error: %w", err)
|
||||
} else if len(versionRes) != 1 {
|
||||
return 0, xerrors.New("invalid signature: api_version")
|
||||
}
|
||||
|
||||
return int(versionRes[0]), nil
|
||||
}
|
||||
|
||||
func moduleRequiredFiles(ctx context.Context, mod api.Module) ([]*regexp.Regexp, error) {
|
||||
requiredFilesFunc := mod.ExportedFunction("required")
|
||||
if requiredFilesFunc == nil {
|
||||
return nil, xerrors.New("required() must be exported")
|
||||
}
|
||||
|
||||
requiredFilesRes, err := requiredFilesFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("wasm function required() invocation error: %w", err)
|
||||
} else if len(requiredFilesRes) != 1 {
|
||||
return nil, xerrors.New("invalid signature: required_files")
|
||||
}
|
||||
|
||||
var fileRegexps serialize.StringSlice
|
||||
if err = unmarshal(ctx, mod, requiredFilesRes[0], &fileRegexps); err != nil {
|
||||
return nil, xerrors.Errorf("invalid return value: %w", err)
|
||||
}
|
||||
|
||||
var requiredFiles []*regexp.Regexp
|
||||
for _, file := range fileRegexps {
|
||||
re, err := regexp.Compile(file)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("regexp compile error: %w", err)
|
||||
}
|
||||
requiredFiles = append(requiredFiles, re)
|
||||
}
|
||||
|
||||
return requiredFiles, nil
|
||||
}
|
||||
|
||||
func moduleIsAnalyzer(ctx context.Context, mod api.Module) (bool, error) {
|
||||
return isType(ctx, mod, "is_analyzer")
|
||||
}
|
||||
|
||||
func moduleIsPostScanner(ctx context.Context, mod api.Module) (bool, error) {
|
||||
return isType(ctx, mod, "is_post_scanner")
|
||||
}
|
||||
|
||||
func isType(ctx context.Context, mod api.Module, name string) (bool, error) {
|
||||
isFunc := mod.ExportedFunction(name)
|
||||
if isFunc == nil {
|
||||
return false, xerrors.Errorf("%s() must be exported", name)
|
||||
}
|
||||
|
||||
isRes, err := isFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("wasm function %s() invocation error: %w", name, err)
|
||||
} else if len(isRes) != 1 {
|
||||
return false, xerrors.Errorf("invalid signature: %s", name)
|
||||
}
|
||||
|
||||
return isRes[0] > 0, nil
|
||||
}
|
||||
|
||||
func dir() string {
|
||||
return filepath.Join(utils.HomeDir(), RelativeDir)
|
||||
}
|
||||
|
||||
func modulePostScanSpec(ctx context.Context, mod api.Module) (serialize.PostScanSpec, error) {
|
||||
postScanSpecFunc := mod.ExportedFunction("post_scan_spec")
|
||||
if postScanSpecFunc == nil {
|
||||
return serialize.PostScanSpec{}, xerrors.New("post_scan_spec() must be exported")
|
||||
}
|
||||
|
||||
postScanSpecRes, err := postScanSpecFunc.Call(ctx)
|
||||
if err != nil {
|
||||
return serialize.PostScanSpec{}, xerrors.Errorf("wasm function post_scan_spec() invocation error: %w", err)
|
||||
} else if len(postScanSpecRes) != 1 {
|
||||
return serialize.PostScanSpec{}, xerrors.New("invalid signature: post_scan_spec")
|
||||
}
|
||||
|
||||
var spec serialize.PostScanSpec
|
||||
if err = unmarshal(ctx, mod, postScanSpecRes[0], &spec); err != nil {
|
||||
return serialize.PostScanSpec{}, xerrors.Errorf("invalid return value: %w", err)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
103
pkg/module/module_test.go
Normal file
103
pkg/module/module_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package module_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/post"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
func TestManager_Register(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
noModuleDir bool
|
||||
moduleName string
|
||||
wantAnalyzerVersions map[string]int
|
||||
wantPostScannerVersions map[string]int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
moduleName: "happy",
|
||||
wantAnalyzerVersions: map[string]int{
|
||||
"happy": 1,
|
||||
},
|
||||
wantPostScannerVersions: map[string]int{
|
||||
"happy": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only analyzer",
|
||||
moduleName: "analyzer",
|
||||
wantAnalyzerVersions: map[string]int{
|
||||
"analyzer": 1,
|
||||
},
|
||||
wantPostScannerVersions: map[string]int{},
|
||||
},
|
||||
{
|
||||
name: "only post scanner",
|
||||
moduleName: "scanner",
|
||||
wantAnalyzerVersions: map[string]int{},
|
||||
wantPostScannerVersions: map[string]int{
|
||||
"scanner": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no module dir",
|
||||
noModuleDir: true,
|
||||
moduleName: "happy",
|
||||
wantAnalyzerVersions: map[string]int{},
|
||||
wantPostScannerVersions: map[string]int{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
modulePath := filepath.Join("testdata", tt.moduleName, tt.moduleName+".wasm")
|
||||
|
||||
// WASM modules must be generated before running this test.
|
||||
if _, err := os.Stat(modulePath); os.IsNotExist(err) {
|
||||
require.Fail(t, "missing WASM modules, try 'make test' or 'make generate-test-modules'")
|
||||
}
|
||||
|
||||
// Set up a temp dir for modules
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("XDG_DATA_HOME", tmpDir)
|
||||
moduleDir := filepath.Join(tmpDir, module.RelativeDir)
|
||||
|
||||
if !tt.noModuleDir {
|
||||
err := os.MkdirAll(moduleDir, 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Copy the wasm module for testing
|
||||
_, err = utils.CopyFile(modulePath, filepath.Join(moduleDir, filepath.Base(modulePath)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
m, err := module.NewManager(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Register analyzer and post scanner from WASM module
|
||||
m.Register()
|
||||
defer func() {
|
||||
analyzer.DeregisterAnalyzer(analyzer.Type(tt.moduleName))
|
||||
post.DeregisterPostScanner(tt.moduleName)
|
||||
}()
|
||||
|
||||
// Confirm the analyzer is registered
|
||||
got := analyzer.NewAnalyzerGroup("", nil).AnalyzerVersions()
|
||||
assert.Equal(t, tt.wantAnalyzerVersions, got)
|
||||
|
||||
// Confirm the post scanner is registered
|
||||
got = post.ScannerVersions()
|
||||
assert.Equal(t, tt.wantPostScannerVersions, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
52
pkg/module/serialize/types.go
Normal file
52
pkg/module/serialize/types.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package serialize
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
// TinyGo doesn't support encoding/json, but github.com/mailru/easyjson for now.
|
||||
// We need to generate JSON-related methods like MarshalEasyJSON implementing easyjson.Marshaler.
|
||||
//
|
||||
// $ make easyjson
|
||||
|
||||
//easyjson:json
|
||||
type StringSlice []string
|
||||
|
||||
//easyjson:json
|
||||
type AnalysisResult struct {
|
||||
// TODO: support other fields as well
|
||||
//OS *types.OS
|
||||
//Repository *types.Repository
|
||||
//PackageInfos []types.PackageInfo
|
||||
//Applications []types.Application
|
||||
//Secrets []types.Secret
|
||||
//SystemInstalledFiles []string // A list of files installed by OS package manager
|
||||
|
||||
// Currently it supports custom resources only
|
||||
CustomResources []CustomResource
|
||||
}
|
||||
|
||||
type CustomResource struct {
|
||||
Type string
|
||||
FilePath string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type PostScanAction string
|
||||
|
||||
//easyjson:json
|
||||
type PostScanSpec struct {
|
||||
// What action the module will do in post scanning.
|
||||
// value: INSERT, UPDATE and DELETE
|
||||
Action PostScanAction
|
||||
|
||||
// IDs represent which vulnerability and misconfiguration ID will be updated or deleted in post scanning.
|
||||
// When the action is UPDATE, the matched result will be passed to the module.
|
||||
IDs []string
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type Results []Result
|
||||
|
||||
//easyjson:json
|
||||
type Result types.Result
|
||||
2508
pkg/module/serialize/types_easyjson.go
Normal file
2508
pkg/module/serialize/types_easyjson.go
Normal file
File diff suppressed because it is too large
Load Diff
38
pkg/module/testdata/analyzer/analyzer.go
vendored
Normal file
38
pkg/module/testdata/analyzer/analyzer.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:generate tinygo build -o analyzer.wasm -scheduler=none -target=wasi --no-debug analyzer.go
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/module/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
moduleVersion = 1
|
||||
moduleName = "analyzer"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wasm.RegisterModule(AnalyzerModule{})
|
||||
}
|
||||
|
||||
type AnalyzerModule struct{}
|
||||
|
||||
func (AnalyzerModule) Version() int {
|
||||
return moduleVersion
|
||||
}
|
||||
|
||||
func (AnalyzerModule) Name() string {
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (AnalyzerModule) RequiredFiles() []string {
|
||||
return []string{
|
||||
`foo(.?)`,
|
||||
}
|
||||
}
|
||||
|
||||
func (s AnalyzerModule) Analyze(_ string) (*serialize.AnalysisResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
47
pkg/module/testdata/happy/happy.go
vendored
Normal file
47
pkg/module/testdata/happy/happy.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
//go:generate tinygo build -o happy.wasm -scheduler=none -target=wasi --no-debug happy.go
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/module/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
moduleVersion = 1
|
||||
moduleName = "happy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wasm.RegisterModule(HappyModule{})
|
||||
}
|
||||
|
||||
type HappyModule struct{}
|
||||
|
||||
func (HappyModule) Version() int {
|
||||
return moduleVersion
|
||||
}
|
||||
|
||||
func (HappyModule) Name() string {
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (HappyModule) RequiredFiles() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s HappyModule) Analyze(_ string) (*serialize.AnalysisResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (HappyModule) PostScanSpec() serialize.PostScanSpec {
|
||||
return serialize.PostScanSpec{
|
||||
Action: api.ActionInsert, // Add new vulnerabilities
|
||||
}
|
||||
}
|
||||
|
||||
func (HappyModule) PostScan(_ serialize.Results) (serialize.Results, error) {
|
||||
return nil, nil
|
||||
}
|
||||
39
pkg/module/testdata/scanner/scanner.go
vendored
Normal file
39
pkg/module/testdata/scanner/scanner.go
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
//go:generate tinygo build -o scanner.wasm -scheduler=none -target=wasi --no-debug scanner.go
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
"github.com/aquasecurity/trivy/pkg/module/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
moduleVersion = 2
|
||||
moduleName = "scanner"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wasm.RegisterModule(PostScannerModule{})
|
||||
}
|
||||
|
||||
type PostScannerModule struct{}
|
||||
|
||||
func (PostScannerModule) Version() int {
|
||||
return moduleVersion
|
||||
}
|
||||
|
||||
func (PostScannerModule) Name() string {
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (PostScannerModule) PostScanSpec() serialize.PostScanSpec {
|
||||
return serialize.PostScanSpec{
|
||||
Action: api.ActionInsert, // Add new vulnerabilities
|
||||
}
|
||||
}
|
||||
|
||||
func (PostScannerModule) PostScan(_ serialize.Results) (serialize.Results, error) {
|
||||
return nil, nil
|
||||
}
|
||||
179
pkg/module/wasm/sdk.go
Normal file
179
pkg/module/wasm/sdk.go
Normal file
@@ -0,0 +1,179 @@
|
||||
//go:build tinygo.wasm
|
||||
|
||||
package wasm
|
||||
|
||||
// This package is designed to be imported by WASM modules.
|
||||
// TinyGo can build this package, but Go cannot.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/module/api"
|
||||
"github.com/aquasecurity/trivy/pkg/module/serialize"
|
||||
)
|
||||
|
||||
func Debug(message string) {
|
||||
message = fmt.Sprintf("Module %s: %s", module.Name(), message)
|
||||
ptr, size := stringToPtr(message)
|
||||
_debug(ptr, size)
|
||||
}
|
||||
|
||||
func Info(message string) {
|
||||
message = fmt.Sprintf("Module %s: %s", module.Name(), message)
|
||||
ptr, size := stringToPtr(message)
|
||||
_info(ptr, size)
|
||||
}
|
||||
|
||||
func Warn(message string) {
|
||||
message = fmt.Sprintf("Module %s: %s", module.Name(), message)
|
||||
ptr, size := stringToPtr(message)
|
||||
_warn(ptr, size)
|
||||
}
|
||||
|
||||
func Error(message string) {
|
||||
message = fmt.Sprintf("Module %s: %s", module.Name(), message)
|
||||
ptr, size := stringToPtr(message)
|
||||
_error(ptr, size)
|
||||
}
|
||||
|
||||
//go:wasm-module env
|
||||
//export debug
|
||||
func _debug(ptr uint32, size uint32)
|
||||
|
||||
//go:wasm-module env
|
||||
//export info
|
||||
func _info(ptr uint32, size uint32)
|
||||
|
||||
//go:wasm-module env
|
||||
//export warn
|
||||
func _warn(ptr uint32, size uint32)
|
||||
|
||||
//go:wasm-module env
|
||||
//export error
|
||||
func _error(ptr uint32, size uint32)
|
||||
|
||||
var module api.Module
|
||||
|
||||
func RegisterModule(p api.Module) {
|
||||
module = p
|
||||
}
|
||||
|
||||
//export name
|
||||
func _name() uint64 {
|
||||
name := module.Name()
|
||||
ptr, size := stringToPtr(name)
|
||||
return (uint64(ptr) << uint64(32)) | uint64(size)
|
||||
}
|
||||
|
||||
//export api_version
|
||||
func _apiVersion() uint32 {
|
||||
return api.Version
|
||||
}
|
||||
|
||||
//export version
|
||||
func _version() uint32 {
|
||||
return uint32(module.Version())
|
||||
}
|
||||
|
||||
//export is_analyzer
|
||||
func _isAnalyzer() uint64 {
|
||||
if _, ok := module.(api.Analyzer); !ok {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
//export required
|
||||
func _required() uint64 {
|
||||
files := module.(api.Analyzer).RequiredFiles()
|
||||
ss := serialize.StringSlice(files)
|
||||
return marshal(ss)
|
||||
}
|
||||
|
||||
//export analyze
|
||||
func _analyze(ptr, size uint32) uint64 {
|
||||
filePath := ptrToString(ptr, size)
|
||||
custom, err := module.(api.Analyzer).Analyze(filePath)
|
||||
if err != nil {
|
||||
Error(fmt.Sprintf("analyze error: %s", err))
|
||||
return 0
|
||||
}
|
||||
return marshal(custom)
|
||||
}
|
||||
|
||||
//export is_post_scanner
|
||||
func _isPostScanner() uint64 {
|
||||
if _, ok := module.(api.PostScanner); !ok {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
//export post_scan_spec
|
||||
func _post_scan_spec() uint64 {
|
||||
return marshal(module.(api.PostScanner).PostScanSpec())
|
||||
}
|
||||
|
||||
//export post_scan
|
||||
func _post_scan(ptr, size uint32) uint64 {
|
||||
var results serialize.Results
|
||||
if err := unmarshal(ptr, size, &results); err != nil {
|
||||
Error(fmt.Sprintf("post scan error: %s", err))
|
||||
return 0
|
||||
}
|
||||
|
||||
results, err := module.(api.PostScanner).PostScan(results)
|
||||
if err != nil {
|
||||
Error(fmt.Sprintf("post scan error: %s", err))
|
||||
return 0
|
||||
}
|
||||
return marshal(results)
|
||||
}
|
||||
|
||||
func marshal(v easyjson.Marshaler) uint64 {
|
||||
b, err := easyjson.Marshal(v)
|
||||
if err != nil {
|
||||
Error(fmt.Sprintf("marshal error: %s", err))
|
||||
return 0
|
||||
}
|
||||
|
||||
p := uintptr(unsafe.Pointer(&b[0]))
|
||||
return (uint64(p) << uint64(32)) | uint64(len(b))
|
||||
}
|
||||
|
||||
func unmarshal(ptr, size uint32, v easyjson.Unmarshaler) error {
|
||||
var b []byte
|
||||
s := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
s.Len = uintptr(size)
|
||||
s.Cap = uintptr(size)
|
||||
s.Data = uintptr(ptr)
|
||||
|
||||
if err := easyjson.Unmarshal(b, v); err != nil {
|
||||
return fmt.Errorf("unmarshal error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ptrToString returns a string from WebAssembly compatible numeric types representing its pointer and length.
|
||||
func ptrToString(ptr uint32, size uint32) string {
|
||||
// Get a slice view of the underlying bytes in the stream. We use SliceHeader, not StringHeader
|
||||
// as it allows us to fix the capacity to what was allocated.
|
||||
return *(*string)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(ptr),
|
||||
Len: uintptr(size), // Tinygo requires these as uintptrs even if they are int fields.
|
||||
Cap: uintptr(size), // ^^ See https://github.com/tinygo-org/tinygo/issues/1284
|
||||
}))
|
||||
}
|
||||
|
||||
// stringToPtr returns a pointer and size pair for the given string in a way compatible with WebAssembly numeric types.
|
||||
func stringToPtr(s string) (uint32, uint32) {
|
||||
buf := []byte(s)
|
||||
ptr := &buf[0]
|
||||
unsafePtr := uintptr(unsafe.Pointer(ptr))
|
||||
return uint32(unsafePtr), uint32(len(buf))
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/downloader"
|
||||
)
|
||||
|
||||
const titleAnnotation = "org.opencontainers.image.title"
|
||||
|
||||
type options struct {
|
||||
img v1.Image
|
||||
}
|
||||
@@ -33,6 +36,7 @@ func WithImage(img v1.Image) Option {
|
||||
|
||||
// Artifact is used to download artifacts such as vulnerability database and policies from OCI registries.
|
||||
type Artifact struct {
|
||||
fileName string
|
||||
image v1.Image
|
||||
layer v1.Layer // Take the first layer as OCI artifact
|
||||
quiet bool
|
||||
@@ -52,7 +56,7 @@ func NewArtifact(repo, mediaType string, quiet, insecure bool, opts ...Option) (
|
||||
return nil, xerrors.Errorf("repository name error (%s): %w", repo, err)
|
||||
}
|
||||
|
||||
var remoteOpts []remote.Option
|
||||
remoteOpts := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)}
|
||||
if insecure {
|
||||
t := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
@@ -71,14 +75,25 @@ func NewArtifact(repo, mediaType string, quiet, insecure bool, opts ...Option) (
|
||||
return nil, xerrors.Errorf("OCI layer error: %w", err)
|
||||
}
|
||||
|
||||
manifest, err := o.img.Manifest()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("OCI manifest error: %w", err)
|
||||
}
|
||||
|
||||
// A single layer is only supported now.
|
||||
if len(layers) != 1 {
|
||||
if len(layers) != 1 || len(manifest.Layers) != 1 {
|
||||
return nil, xerrors.Errorf("OCI artifact must be a single layer")
|
||||
}
|
||||
|
||||
// Take the first layer
|
||||
layer := layers[0]
|
||||
|
||||
// Take the file name of the first layer
|
||||
fileName, ok := manifest.Layers[0].Annotations[titleAnnotation]
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("annotation %s is missing", titleAnnotation)
|
||||
}
|
||||
|
||||
layerMediaType, err := layer.MediaType()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("media type error: %w", err)
|
||||
@@ -87,6 +102,7 @@ func NewArtifact(repo, mediaType string, quiet, insecure bool, opts ...Option) (
|
||||
}
|
||||
|
||||
return &Artifact{
|
||||
fileName: fileName,
|
||||
image: o.img,
|
||||
layer: layer,
|
||||
quiet: quiet,
|
||||
@@ -114,13 +130,18 @@ func (a Artifact) Download(ctx context.Context, dir string) error {
|
||||
defer bar.Finish()
|
||||
|
||||
// https://github.com/hashicorp/go-getter/issues/326
|
||||
f, err := os.CreateTemp("", "artifact-*.tar.gz")
|
||||
tempDir, err := os.MkdirTemp("", "trivy")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create a temp dir: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(filepath.Join(tempDir, a.fileName))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create a temp file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(f.Name())
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
// Download the layer content into a temporal file
|
||||
@@ -128,7 +149,7 @@ func (a Artifact) Download(ctx context.Context, dir string) error {
|
||||
return xerrors.Errorf("copy error: %w", err)
|
||||
}
|
||||
|
||||
// Decompress artifact-xxx.tar.gz and copy it into the cache dir
|
||||
// Decompress the downloaded file if it is compressed and copy it into the dst
|
||||
if err = downloader.Download(ctx, f.Name(), dir, dir); err != nil {
|
||||
return xerrors.Errorf("download error: %w", err)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,20 @@ func TestNewArtifact(t *testing.T) {
|
||||
// Mock image
|
||||
img := new(fakei.FakeImage)
|
||||
img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err)
|
||||
img.ManifestReturns(&v1.Manifest{
|
||||
Layers: []v1.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip",
|
||||
Size: 100,
|
||||
Digest: v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8"},
|
||||
Annotations: map[string]string{
|
||||
"org.opencontainers.image.title": "bundle.tar.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
_, err = oci.NewArtifact("repo", tt.mediaType, true, false, oci.WithImage(img))
|
||||
if tt.wantErr != "" {
|
||||
@@ -126,6 +140,20 @@ func TestArtifact_Download(t *testing.T) {
|
||||
// Mock image
|
||||
img := new(fakei.FakeImage)
|
||||
img.LayersReturns([]v1.Layer{flayer}, nil)
|
||||
img.ManifestReturns(&v1.Manifest{
|
||||
Layers: []v1.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip",
|
||||
Size: 100,
|
||||
Digest: v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8"},
|
||||
Annotations: map[string]string{
|
||||
"org.opencontainers.image.title": "bundle.tar.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
mediaType := "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip"
|
||||
artifact, err := oci.NewArtifact("repo", mediaType, true, false, oci.WithImage(img))
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
const (
|
||||
configFile = "plugin.yaml"
|
||||
xdgDataHome = "XDG_DATA_HOME"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -297,13 +296,7 @@ func loadMetadata(dir string) (Plugin, error) {
|
||||
}
|
||||
|
||||
func dir() string {
|
||||
dataHome := os.Getenv(xdgDataHome)
|
||||
if dataHome != "" {
|
||||
return filepath.Join(dataHome, pluginsRelativeDir)
|
||||
}
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
return filepath.Join(homeDir, pluginsRelativeDir)
|
||||
return filepath.Join(utils.HomeDir(), pluginsRelativeDir)
|
||||
}
|
||||
|
||||
func isInstalled(url string) (Plugin, bool) {
|
||||
|
||||
@@ -228,16 +228,16 @@ func TestWriter_Write(t *testing.T) {
|
||||
PackageLicenseDeclared: "GPLv2+",
|
||||
IsFilesAnalyzedTagPresent: true,
|
||||
},
|
||||
spdx.ElementID("ee8bb4e8354184d"): {
|
||||
PackageSPDXIdentifier: spdx.ElementID("ee8bb4e8354184d"),
|
||||
spdx.ElementID("216407676208fcb1"): {
|
||||
PackageSPDXIdentifier: spdx.ElementID("216407676208fcb1"),
|
||||
PackageName: "actionpack",
|
||||
PackageVersion: "7.0.1",
|
||||
PackageLicenseConcluded: "NONE",
|
||||
PackageLicenseDeclared: "NONE",
|
||||
IsFilesAnalyzedTagPresent: true,
|
||||
},
|
||||
spdx.ElementID("216407676208fcb1"): {
|
||||
PackageSPDXIdentifier: spdx.ElementID("216407676208fcb1"),
|
||||
spdx.ElementID("ee8bb4e8354184d"): {
|
||||
PackageSPDXIdentifier: spdx.ElementID("ee8bb4e8354184d"),
|
||||
PackageName: "actionpack",
|
||||
PackageVersion: "7.0.1",
|
||||
PackageLicenseConcluded: "NONE",
|
||||
|
||||
@@ -45,6 +45,10 @@ type TableWriter struct {
|
||||
// Write writes the result on standard output
|
||||
func (tw TableWriter) Write(report types.Report) error {
|
||||
for _, result := range report.Results {
|
||||
// Not display a table of custom resources
|
||||
if result.Class == types.ClassCustom {
|
||||
continue
|
||||
}
|
||||
tw.write(result)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -8,16 +8,13 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
@@ -27,112 +24,8 @@ const (
|
||||
DefaultIgnoreFile = ".trivyignore"
|
||||
)
|
||||
|
||||
var (
|
||||
primaryURLPrefixes = map[dbTypes.SourceID][]string{
|
||||
vulnerability.Debian: {"http://www.debian.org", "https://www.debian.org"},
|
||||
vulnerability.Ubuntu: {"http://www.ubuntu.com", "https://usn.ubuntu.com"},
|
||||
vulnerability.RedHat: {"https://access.redhat.com"},
|
||||
vulnerability.SuseCVRF: {"http://lists.opensuse.org", "https://lists.opensuse.org"},
|
||||
vulnerability.OracleOVAL: {"http://linux.oracle.com/errata", "https://linux.oracle.com/errata"},
|
||||
vulnerability.NodejsSecurityWg: {"https://www.npmjs.com", "https://hackerone.com"},
|
||||
vulnerability.RubySec: {"https://groups.google.com"},
|
||||
}
|
||||
)
|
||||
|
||||
// SuperSet binds the dependencies
|
||||
var SuperSet = wire.NewSet(
|
||||
wire.Struct(new(db.Config)),
|
||||
NewClient,
|
||||
)
|
||||
|
||||
// Client implements db operations
|
||||
type Client struct {
|
||||
dbc db.Operation
|
||||
}
|
||||
|
||||
// NewClient is the factory method for Client
|
||||
func NewClient(dbc db.Config) Client {
|
||||
return Client{dbc: dbc}
|
||||
}
|
||||
|
||||
// FillVulnerabilityInfo fills extra info in vulnerability objects
|
||||
func (c Client) FillVulnerabilityInfo(vulns []types.DetectedVulnerability, reportType string) {
|
||||
for i := range vulns {
|
||||
vulnID := vulns[i].VulnerabilityID
|
||||
vuln, err := c.dbc.GetVulnerability(vulnID)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("Error while getting vulnerability details: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect the data source
|
||||
var source dbTypes.SourceID
|
||||
if vulns[i].DataSource != nil {
|
||||
source = vulns[i].DataSource.ID
|
||||
}
|
||||
|
||||
// Select the severity according to the detected source.
|
||||
severity, severitySource := c.getVendorSeverity(&vuln, source)
|
||||
|
||||
// The vendor might provide package-specific severity like Debian.
|
||||
// For example, CVE-2015-2328 in Debian has "unimportant" for mongodb and "low" for pcre3.
|
||||
// In that case, we keep the severity as is.
|
||||
if vulns[i].SeveritySource != "" {
|
||||
severity = vulns[i].Severity
|
||||
severitySource = vulns[i].SeveritySource
|
||||
}
|
||||
|
||||
// Add the vulnerability detail
|
||||
vulns[i].Vulnerability = vuln
|
||||
|
||||
vulns[i].Severity = severity
|
||||
vulns[i].SeveritySource = severitySource
|
||||
vulns[i].PrimaryURL = c.getPrimaryURL(vulnID, vuln.References, source)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) getVendorSeverity(vuln *dbTypes.Vulnerability, source dbTypes.SourceID) (string, dbTypes.SourceID) {
|
||||
if vs, ok := vuln.VendorSeverity[source]; ok {
|
||||
return vs.String(), source
|
||||
}
|
||||
|
||||
// Try NVD as a fallback if it exists
|
||||
if vs, ok := vuln.VendorSeverity[vulnerability.NVD]; ok {
|
||||
return vs.String(), vulnerability.NVD
|
||||
}
|
||||
|
||||
if vuln.Severity == "" {
|
||||
return dbTypes.SeverityUnknown.String(), ""
|
||||
}
|
||||
|
||||
return vuln.Severity, ""
|
||||
}
|
||||
|
||||
func (c Client) getPrimaryURL(vulnID string, refs []string, source dbTypes.SourceID) string {
|
||||
switch {
|
||||
case strings.HasPrefix(vulnID, "CVE-"):
|
||||
return "https://avd.aquasec.com/nvd/" + strings.ToLower(vulnID)
|
||||
case strings.HasPrefix(vulnID, "RUSTSEC-"):
|
||||
return "https://osv.dev/vulnerability/" + vulnID
|
||||
case strings.HasPrefix(vulnID, "GHSA-"):
|
||||
return "https://github.com/advisories/" + vulnID
|
||||
case strings.HasPrefix(vulnID, "TEMP-"):
|
||||
return "https://security-tracker.debian.org/tracker/" + vulnID
|
||||
}
|
||||
|
||||
prefixes := primaryURLPrefixes[source]
|
||||
for _, pre := range prefixes {
|
||||
for _, ref := range refs {
|
||||
if strings.HasPrefix(ref, pre) {
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Filter filter out the vulnerabilities
|
||||
func (c Client) Filter(ctx context.Context, vulns []types.DetectedVulnerability, misconfs []types.DetectedMisconfiguration, secrets []ftypes.SecretFinding,
|
||||
// Filter filters out the vulnerabilities
|
||||
func Filter(ctx context.Context, vulns []types.DetectedVulnerability, misconfs []types.DetectedMisconfiguration, secrets []ftypes.SecretFinding,
|
||||
severities []dbTypes.Severity, ignoreUnfixed, includeNonFailures bool, ignoreFile, policyFile string) (
|
||||
[]types.DetectedVulnerability, *types.MisconfSummary, []types.DetectedMisconfiguration, []ftypes.SecretFinding, error) {
|
||||
ignoredIDs := getIgnoredIDs(ignoreFile)
|
||||
@@ -1,4 +1,4 @@
|
||||
package result
|
||||
package result_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,358 +7,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
fos "github.com/aquasecurity/fanal/analyzer/os"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/utils"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/dbtest"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestClient_FillVulnerabilityInfo(t *testing.T) {
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
reportType string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtures []string
|
||||
args args
|
||||
expectedVulnerabilities []types.DetectedVulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no vendor severity, no NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
},
|
||||
reportType: fos.RedHat,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no vendor severity, yes NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Ubuntu,
|
||||
Name: "Ubuntu CVE Tracker",
|
||||
URL: "https://git.launchpad.net/ubuntu-cve-tracker",
|
||||
},
|
||||
},
|
||||
},
|
||||
reportType: fos.Ubuntu,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.NVD: dbTypes.SeverityLow,
|
||||
},
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
SeveritySource: vulnerability.NVD,
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0002",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Ubuntu,
|
||||
Name: "Ubuntu CVE Tracker",
|
||||
URL: "https://git.launchpad.net/ubuntu-cve-tracker",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no severity, no vendor severity, no NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0003",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Ubuntu,
|
||||
Name: "Ubuntu CVE Tracker",
|
||||
URL: "https://git.launchpad.net/ubuntu-cve-tracker",
|
||||
},
|
||||
},
|
||||
},
|
||||
reportType: fos.Ubuntu,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0003",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0003",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.Ubuntu,
|
||||
Name: "Ubuntu CVE Tracker",
|
||||
URL: "https://git.launchpad.net/ubuntu-cve-tracker",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0004",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.RedHat,
|
||||
Name: "Red Hat OVAL v2",
|
||||
URL: "https://www.redhat.com/security/data/oval/v2/",
|
||||
},
|
||||
},
|
||||
},
|
||||
reportType: fos.CentOS,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0004",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.RedHat: dbTypes.SeverityLow,
|
||||
},
|
||||
CweIDs: []string{"CWE-311"},
|
||||
References: []string{"http://example.com"},
|
||||
CVSS: map[dbTypes.SourceID]dbTypes.CVSS{
|
||||
vulnerability.NVD: {
|
||||
V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
V2Score: 4.5,
|
||||
V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 5.6,
|
||||
},
|
||||
vulnerability.RedHat: {
|
||||
V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N",
|
||||
V2Score: 7.8,
|
||||
V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 9.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
SeveritySource: vulnerability.RedHat,
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0004",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.RedHat,
|
||||
Name: "Red Hat OVAL v2",
|
||||
URL: "https://www.redhat.com/security/data/oval/v2/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only library vulnerability",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0005",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
reportType: ftypes.Poetry,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0005",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "COVID-19",
|
||||
Description: "a nasty virus vulnerability for humans",
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.GHSA: dbTypes.SeverityCritical,
|
||||
},
|
||||
References: []string{"https://www.who.int/emergencies/diseases/novel-coronavirus-2019"},
|
||||
},
|
||||
SeveritySource: vulnerability.GHSA,
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0005",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with package-specific severity",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
SeveritySource: vulnerability.Debian,
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
reportType: fos.Debian,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
SeveritySource: vulnerability.Debian,
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetVulnerability returns an error",
|
||||
fixtures: []string{"testdata/fixtures/sad.yaml"},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dbtest.InitDB(t, tt.fixtures)
|
||||
defer db.Close()
|
||||
|
||||
c := Client{
|
||||
dbc: db.Config{},
|
||||
}
|
||||
|
||||
c.FillVulnerabilityInfo(tt.args.vulns, tt.args.reportType)
|
||||
assert.Equal(t, tt.expectedVulnerabilities, tt.args.vulns, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_getPrimaryURL(t *testing.T) {
|
||||
type args struct {
|
||||
vulnID string
|
||||
refs []string
|
||||
source dbTypes.SourceID
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "CVE-ID",
|
||||
args: args{
|
||||
vulnID: "CVE-2014-8484",
|
||||
refs: []string{"http://linux.oracle.com/cve/CVE-2014-8484.html"},
|
||||
source: vulnerability.OracleOVAL,
|
||||
},
|
||||
want: "https://avd.aquasec.com/nvd/cve-2014-8484",
|
||||
},
|
||||
{
|
||||
name: "RUSTSEC",
|
||||
args: args{
|
||||
vulnID: "RUSTSEC-2018-0017",
|
||||
refs: []string{"https://github.com/rust-lang-deprecated/tempdir/pull/46"},
|
||||
source: vulnerability.OSV,
|
||||
},
|
||||
want: "https://osv.dev/vulnerability/RUSTSEC-2018-0017",
|
||||
},
|
||||
{
|
||||
name: "GHSA",
|
||||
args: args{
|
||||
vulnID: "GHSA-28fw-88hq-6jmm",
|
||||
refs: []string{},
|
||||
source: vulnerability.PhpSecurityAdvisories,
|
||||
},
|
||||
want: "https://github.com/advisories/GHSA-28fw-88hq-6jmm",
|
||||
},
|
||||
{
|
||||
name: "Debian temp vulnerability",
|
||||
args: args{
|
||||
vulnID: "TEMP-0841856-B18BAF",
|
||||
refs: []string{},
|
||||
source: vulnerability.Debian,
|
||||
},
|
||||
want: "https://security-tracker.debian.org/tracker/TEMP-0841856-B18BAF",
|
||||
},
|
||||
{
|
||||
name: "npm",
|
||||
args: args{
|
||||
vulnID: "NSWG-ECO-516",
|
||||
refs: []string{
|
||||
"https://hackerone.com/reports/712065",
|
||||
"https://github.com/lodash/lodash/pull/4759",
|
||||
"https://www.npmjs.com/advisories/1523",
|
||||
},
|
||||
source: vulnerability.NodejsSecurityWg,
|
||||
},
|
||||
want: "https://www.npmjs.com/advisories/1523",
|
||||
},
|
||||
{
|
||||
name: "suse",
|
||||
args: args{
|
||||
vulnID: "openSUSE-SU-2019:2596-1",
|
||||
refs: []string{
|
||||
"http://lists.opensuse.org/opensuse-security-announce/2019-11/msg00076.html",
|
||||
"https://www.suse.com/support/security/rating/",
|
||||
},
|
||||
source: vulnerability.SuseCVRF,
|
||||
},
|
||||
want: "http://lists.opensuse.org/opensuse-security-announce/2019-11/msg00076.html",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := Client{}
|
||||
got := c.getPrimaryURL(tt.args.vulnID, tt.args.refs, tt.args.source)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Filter(t *testing.T) {
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
@@ -799,8 +453,7 @@ func TestClient_Filter(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := Client{}
|
||||
gotVulns, gotMisconfSummary, gotMisconfs, gotSecrets, err := c.Filter(context.Background(), tt.args.vulns, tt.args.misconfs, tt.args.secrets,
|
||||
gotVulns, gotMisconfSummary, gotMisconfs, gotSecrets, err := result.Filter(context.Background(), tt.args.vulns, tt.args.misconfs, tt.args.secrets,
|
||||
tt.args.severities, tt.args.ignoreUnfixed, false, tt.args.ignoreFile, tt.args.policyFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantVulns, gotVulns)
|
||||
@@ -61,8 +61,8 @@ func NewScanner(scannerOptions ScannerOption, opts ...Option) Scanner {
|
||||
}
|
||||
|
||||
// Scan scans the image
|
||||
func (s Scanner) Scan(target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
||||
ctx := WithCustomHeaders(context.Background(), s.customHeaders)
|
||||
func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
||||
ctx = WithCustomHeaders(ctx, s.customHeaders)
|
||||
|
||||
var res *rpc.ScanResponse
|
||||
err := r.Retry(func() error {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -197,7 +198,7 @@ func TestScanner_Scan(t *testing.T) {
|
||||
|
||||
s := NewScanner(ScannerOption{CustomHeaders: tt.customHeaders}, WithRPCClient(client))
|
||||
|
||||
gotResults, gotOS, err := s.Scan(tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options)
|
||||
gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
@@ -228,7 +229,7 @@ func TestScanner_ScanServerInsecure(t *testing.T) {
|
||||
{
|
||||
name: "sad path",
|
||||
insecure: false,
|
||||
wantErr: "certificate signed by unknown authority",
|
||||
wantErr: "failed to do request",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -241,7 +242,7 @@ func TestScanner_ScanServerInsecure(t *testing.T) {
|
||||
},
|
||||
})
|
||||
s := NewScanner(ScannerOption{Insecure: tt.insecure}, WithRPCClient(c))
|
||||
_, _, err := s.Scan("dummy", "", nil, types.ScanOptions{})
|
||||
_, _, err := s.Scan(context.Background(), "dummy", "", nil, types.ScanOptions{})
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
@@ -21,36 +20,31 @@ import (
|
||||
var ScanSuperSet = wire.NewSet(
|
||||
local.SuperSet,
|
||||
wire.Bind(new(scanner.Driver), new(local.Scanner)),
|
||||
result.SuperSet,
|
||||
NewScanServer,
|
||||
)
|
||||
|
||||
// ScanServer implements the scanner
|
||||
type ScanServer struct {
|
||||
localScanner scanner.Driver
|
||||
resultClient result.Client
|
||||
}
|
||||
|
||||
// NewScanServer is the factory method for scanner
|
||||
func NewScanServer(s scanner.Driver, vulnClient result.Client) *ScanServer {
|
||||
return &ScanServer{localScanner: s, resultClient: vulnClient}
|
||||
func NewScanServer(s scanner.Driver) *ScanServer {
|
||||
return &ScanServer{localScanner: s}
|
||||
}
|
||||
|
||||
// Scan scans and return response
|
||||
func (s *ScanServer) Scan(_ context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) {
|
||||
func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) {
|
||||
options := types.ScanOptions{
|
||||
VulnType: in.Options.VulnType,
|
||||
SecurityChecks: in.Options.SecurityChecks,
|
||||
ListAllPackages: in.Options.ListAllPackages,
|
||||
}
|
||||
results, os, err := s.localScanner.Scan(in.Target, in.ArtifactId, in.BlobIds, options)
|
||||
results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed scan, %s: %w", in.Target, err)
|
||||
}
|
||||
|
||||
for i := range results {
|
||||
s.resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
|
||||
}
|
||||
return rpc.ConvertToRPCScanResponse(results, os), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,9 @@ import (
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/utils"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/dbtest"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
rpcCache "github.com/aquasecurity/trivy/rpc/cache"
|
||||
@@ -39,7 +36,6 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtures []string
|
||||
args args
|
||||
scanExpectation scanner.DriverScanExpectation
|
||||
want *rpcScanner.ScanResponse
|
||||
@@ -47,7 +43,6 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml", "testdata/fixtures/data-source.yaml"},
|
||||
args: args{
|
||||
in: &rpcScanner.ScanRequest{
|
||||
Target: "alpine:3.11",
|
||||
@@ -58,6 +53,7 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
},
|
||||
scanExpectation: scanner.DriverScanExpectation{
|
||||
Args: scanner.DriverScanArgs{
|
||||
CtxAnything: true,
|
||||
Target: "alpine:3.11",
|
||||
ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
@@ -72,10 +68,19 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
SeveritySource: "nvd",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{
|
||||
vulnerability.NVD: dbTypes.SeverityMedium,
|
||||
},
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
Name: "DOS vulnerabilities",
|
||||
URL: "https://vuld-db-example.com/",
|
||||
@@ -147,6 +152,7 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
},
|
||||
scanExpectation: scanner.DriverScanExpectation{
|
||||
Args: scanner.DriverScanArgs{
|
||||
CtxAnything: true,
|
||||
Target: "alpine:3.11",
|
||||
ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
@@ -161,21 +167,17 @@ func TestScanServer_Scan(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dbtest.InitDB(t, tt.fixtures)
|
||||
defer db.Close()
|
||||
|
||||
mockDriver := new(scanner.MockDriver)
|
||||
mockDriver.ApplyScanExpectation(tt.scanExpectation)
|
||||
|
||||
s := NewScanServer(mockDriver, result.NewClient(db.Config{}))
|
||||
s := NewScanServer(mockDriver)
|
||||
got, err := s.Scan(context.Background(), tt.args.in)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
assert.NoError(t, err, tt.name)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
- bucket: data-source
|
||||
pairs:
|
||||
- key: vulnerability
|
||||
value:
|
||||
Name: "DOS vulnerabilities"
|
||||
URL: "https://vuld-db-example.com/"
|
||||
@@ -1,13 +0,0 @@
|
||||
- bucket: vulnerability
|
||||
pairs:
|
||||
- key: CVE-2019-0001
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
Severity: MEDIUM
|
||||
VendorSeverity:
|
||||
nvd: 2
|
||||
References:
|
||||
- http://example.com
|
||||
LastModifiedDate: "2020-01-01T01:01:00Z"
|
||||
PublishedDate: "2001-01-01T01:01:00Z"
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
scanner := local.NewScanner(applierApplier, detector)
|
||||
config := db.Config{}
|
||||
client := result.NewClient(config)
|
||||
scanServer := NewScanServer(scanner, client)
|
||||
client := vulnerability.NewClient(config)
|
||||
scanner := local.NewScanner(applierApplier, detector, client)
|
||||
scanServer := NewScanServer(scanner)
|
||||
return scanServer
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
pkgtypes "github.com/aquasecurity/trivy/pkg/types"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
time "time"
|
||||
|
||||
types "github.com/aquasecurity/fanal/types"
|
||||
)
|
||||
|
||||
// MockOspkgDetector is an autogenerated mock type for the OspkgDetector type
|
||||
type MockOspkgDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type OspkgDetectorDetectArgs struct {
|
||||
ImageName string
|
||||
ImageNameAnything bool
|
||||
OsFamily string
|
||||
OsFamilyAnything bool
|
||||
OsName string
|
||||
OsNameAnything bool
|
||||
Repo *types.Repository
|
||||
RepoAnything bool
|
||||
Created time.Time
|
||||
CreatedAnything bool
|
||||
Pkgs []types.Package
|
||||
PkgsAnything bool
|
||||
}
|
||||
|
||||
type OspkgDetectorDetectReturns struct {
|
||||
DetectedVulns []pkgtypes.DetectedVulnerability
|
||||
Eosl bool
|
||||
Err error
|
||||
}
|
||||
|
||||
type OspkgDetectorDetectExpectation struct {
|
||||
Args OspkgDetectorDetectArgs
|
||||
Returns OspkgDetectorDetectReturns
|
||||
}
|
||||
|
||||
func (_m *MockOspkgDetector) ApplyDetectExpectation(e OspkgDetectorDetectExpectation) {
|
||||
var args []interface{}
|
||||
if e.Args.ImageNameAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.ImageName)
|
||||
}
|
||||
if e.Args.OsFamilyAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.OsFamily)
|
||||
}
|
||||
if e.Args.OsNameAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.OsName)
|
||||
}
|
||||
if e.Args.RepoAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Repo)
|
||||
}
|
||||
if e.Args.CreatedAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Created)
|
||||
}
|
||||
if e.Args.PkgsAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Pkgs)
|
||||
}
|
||||
_m.On("Detect", args...).Return(e.Returns.DetectedVulns, e.Returns.Eosl, e.Returns.Err)
|
||||
}
|
||||
|
||||
func (_m *MockOspkgDetector) ApplyDetectExpectations(expectations []OspkgDetectorDetectExpectation) {
|
||||
for _, e := range expectations {
|
||||
_m.ApplyDetectExpectation(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect provides a mock function with given fields: imageName, osFamily, osName, created, pkgs
|
||||
func (_m *MockOspkgDetector) Detect(imageName string, osFamily string, osName string, repo *types.Repository, created time.Time, pkgs []types.Package) ([]pkgtypes.DetectedVulnerability, bool, error) {
|
||||
ret := _m.Called(imageName, osFamily, osName, repo, created, pkgs)
|
||||
|
||||
var r0 []pkgtypes.DetectedVulnerability
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, *types.Repository, time.Time, []types.Package) []pkgtypes.DetectedVulnerability); ok {
|
||||
r0 = rf(imageName, osFamily, osName, repo, created, pkgs)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]pkgtypes.DetectedVulnerability)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 bool
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, *types.Repository, time.Time, []types.Package) bool); ok {
|
||||
r1 = rf(imageName, osFamily, osName, repo, created, pkgs)
|
||||
} else {
|
||||
r1 = ret.Get(1).(bool)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(string, string, string, *types.Repository, time.Time, []types.Package) error); ok {
|
||||
r2 = rf(imageName, osFamily, osName, repo, created, pkgs)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
@@ -12,16 +13,17 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/all"
|
||||
"github.com/aquasecurity/fanal/applier"
|
||||
_ "github.com/aquasecurity/fanal/handler/all"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/post"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
|
||||
_ "github.com/aquasecurity/fanal/analyzer/all"
|
||||
_ "github.com/aquasecurity/fanal/handler/all"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,9 +37,10 @@ var (
|
||||
|
||||
// SuperSet binds dependencies for Local scan
|
||||
var SuperSet = wire.NewSet(
|
||||
vulnerability.SuperSet,
|
||||
applier.NewApplier,
|
||||
wire.Bind(new(Applier), new(applier.Applier)),
|
||||
ospkgDetector.SuperSet,
|
||||
wire.Struct(new(ospkgDetector.Detector)),
|
||||
wire.Bind(new(OspkgDetector), new(ospkgDetector.Detector)),
|
||||
NewScanner,
|
||||
)
|
||||
@@ -56,15 +59,19 @@ type OspkgDetector interface {
|
||||
type Scanner struct {
|
||||
applier Applier
|
||||
ospkgDetector OspkgDetector
|
||||
vulnClient vulnerability.Client
|
||||
}
|
||||
|
||||
// NewScanner is the factory method for Scanner
|
||||
func NewScanner(applier Applier, ospkgDetector OspkgDetector) Scanner {
|
||||
return Scanner{applier: applier, ospkgDetector: ospkgDetector}
|
||||
func NewScanner(applier Applier, ospkgDetector OspkgDetector, vulnClient vulnerability.Client) Scanner {
|
||||
return Scanner{
|
||||
applier: applier,
|
||||
ospkgDetector: ospkgDetector,
|
||||
vulnClient: vulnClient}
|
||||
}
|
||||
|
||||
// Scan scans the artifact and return results.
|
||||
func (s Scanner) Scan(target string, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
||||
func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
||||
artifactDetail, err := s.applier.ApplyLayers(artifactKey, blobKeys)
|
||||
switch {
|
||||
case errors.Is(err, analyzer.ErrUnknownOS):
|
||||
@@ -114,6 +121,25 @@ func (s Scanner) Scan(target string, artifactKey string, blobKeys []string, opti
|
||||
results = append(results, secretResults...)
|
||||
}
|
||||
|
||||
// For WASM plugins and custom analyzers
|
||||
if len(artifactDetail.CustomResources) != 0 {
|
||||
results = append(results, types.Result{
|
||||
Class: types.ClassCustom,
|
||||
CustomResources: artifactDetail.CustomResources,
|
||||
})
|
||||
}
|
||||
|
||||
for i := range results {
|
||||
// Fill vulnerability details
|
||||
s.vulnClient.FillInfo(results[i].Vulnerabilities)
|
||||
}
|
||||
|
||||
// Post scanning
|
||||
results, err = post.Scan(ctx, results)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("post scan error: %w", err)
|
||||
}
|
||||
|
||||
return results, artifactDetail.OS, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -11,9 +14,11 @@ import (
|
||||
fos "github.com/aquasecurity/fanal/analyzer/os"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/dbtest"
|
||||
ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
@@ -27,7 +32,6 @@ func TestScanner_Scan(t *testing.T) {
|
||||
args args
|
||||
fixtures []string
|
||||
applyLayersExpectation ApplierApplyLayersExpectation
|
||||
ospkgDetectExpectations []OspkgDetectorDetectExpectation
|
||||
wantResults types.Results
|
||||
wantOS *ftypes.OS
|
||||
wantErr string
|
||||
@@ -57,6 +61,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "musl",
|
||||
SrcVersion: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
@@ -80,40 +86,11 @@ func TestScanner_Scan(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ospkgDetectExpectations: []OspkgDetectorDetectExpectation{
|
||||
{
|
||||
Args: OspkgDetectorDetectArgs{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.11",
|
||||
Pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Returns: OspkgDetectorDetectReturns{
|
||||
DetectedVulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2020-9999",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
Eosl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResults: types.Results{
|
||||
{
|
||||
Target: "alpine:latest (alpine 3.11)",
|
||||
Class: types.ClassOSPkg,
|
||||
Type: fos.Alpine,
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2020-9999",
|
||||
@@ -123,13 +100,19 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassOSPkg,
|
||||
Type: fos.Alpine,
|
||||
},
|
||||
{
|
||||
Target: "/app/Gemfile.lock",
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.Bundler,
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2014-0081",
|
||||
@@ -139,10 +122,19 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.Bundler,
|
||||
},
|
||||
},
|
||||
wantOS: &ftypes.OS{
|
||||
@@ -177,6 +169,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "musl",
|
||||
SrcVersion: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
@@ -184,6 +178,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
{
|
||||
Name: "ausl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "ausl",
|
||||
SrcVersion: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
@@ -207,51 +203,17 @@ func TestScanner_Scan(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ospkgDetectExpectations: []OspkgDetectorDetectExpectation{
|
||||
{
|
||||
Args: OspkgDetectorDetectArgs{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.11",
|
||||
Pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ausl",
|
||||
Version: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Returns: OspkgDetectorDetectReturns{
|
||||
DetectedVulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2020-9999",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
Eosl: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResults: types.Results{
|
||||
{
|
||||
Target: "alpine:latest (alpine 3.11)",
|
||||
Class: types.ClassOSPkg,
|
||||
Type: fos.Alpine,
|
||||
Packages: []ftypes.Package{
|
||||
{
|
||||
Name: "ausl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "ausl",
|
||||
SrcVersion: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
@@ -259,6 +221,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "musl",
|
||||
SrcVersion: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
@@ -273,13 +237,19 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassOSPkg,
|
||||
Type: fos.Alpine,
|
||||
},
|
||||
{
|
||||
Target: "/app/Gemfile.lock",
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.Bundler,
|
||||
Packages: []ftypes.Package{
|
||||
{
|
||||
Name: "rails",
|
||||
@@ -298,15 +268,25 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
Type: ftypes.Bundler,
|
||||
},
|
||||
},
|
||||
wantOS: &ftypes.OS{
|
||||
Family: "alpine",
|
||||
Name: "3.11",
|
||||
Eosl: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -357,6 +337,17 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
@@ -405,17 +396,6 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Err: analyzer.ErrNoPkgsDetected,
|
||||
},
|
||||
},
|
||||
ospkgDetectExpectations: []OspkgDetectorDetectExpectation{
|
||||
{
|
||||
Args: OspkgDetectorDetectArgs{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.11",
|
||||
},
|
||||
Returns: OspkgDetectorDetectReturns{
|
||||
Eosl: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResults: types.Results{
|
||||
{
|
||||
Target: "alpine:latest (alpine 3.11)",
|
||||
@@ -433,6 +413,17 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
@@ -442,6 +433,7 @@ func TestScanner_Scan(t *testing.T) {
|
||||
wantOS: &ftypes.OS{
|
||||
Family: "alpine",
|
||||
Name: "3.11",
|
||||
Eosl: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -483,17 +475,6 @@ func TestScanner_Scan(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ospkgDetectExpectations: []OspkgDetectorDetectExpectation{
|
||||
{
|
||||
Args: OspkgDetectorDetectArgs{
|
||||
OsFamily: "fedora",
|
||||
OsName: "27",
|
||||
},
|
||||
Returns: OspkgDetectorDetectReturns{
|
||||
Err: ospkgDetector.ErrUnsupportedOS,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResults: types.Results{
|
||||
{
|
||||
Target: "/app/Gemfile.lock",
|
||||
@@ -506,6 +487,17 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
@@ -564,7 +556,12 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Name: "3.11",
|
||||
},
|
||||
Packages: []ftypes.Package{
|
||||
{Name: "musl", Version: "1.2.3"},
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
SrcName: "musl",
|
||||
SrcVersion: "1.2.3",
|
||||
},
|
||||
},
|
||||
Applications: []ftypes.Application{
|
||||
{
|
||||
@@ -609,6 +606,17 @@ func TestScanner_Scan(t *testing.T) {
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0",
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "xss",
|
||||
Description: "xss vulnerability",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{
|
||||
"http://example.com",
|
||||
},
|
||||
LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)),
|
||||
PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: types.ClassLangPkg,
|
||||
@@ -806,61 +814,6 @@ func TestScanner_Scan(t *testing.T) {
|
||||
},
|
||||
wantErr: "failed to apply layers",
|
||||
},
|
||||
{
|
||||
name: "sad path: ospkgDetector.Detect returns an error",
|
||||
args: args{
|
||||
target: "alpine:latest",
|
||||
layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
options: types.ScanOptions{
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
},
|
||||
fixtures: []string{"testdata/fixtures/happy.yaml"},
|
||||
applyLayersExpectation: ApplierApplyLayersExpectation{
|
||||
Args: ApplierApplyLayersArgs{
|
||||
BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
},
|
||||
Returns: ApplierApplyLayersReturns{
|
||||
Detail: ftypes.ArtifactDetail{
|
||||
OS: &ftypes.OS{
|
||||
Family: "alpine",
|
||||
Name: "3.11",
|
||||
},
|
||||
Packages: []ftypes.Package{
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ospkgDetectExpectations: []OspkgDetectorDetectExpectation{
|
||||
{
|
||||
Args: OspkgDetectorDetectArgs{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.11",
|
||||
Pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "musl",
|
||||
Version: "1.2.3",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Returns: OspkgDetectorDetectReturns{
|
||||
Err: errors.New("error"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "failed to scan OS packages",
|
||||
},
|
||||
{
|
||||
name: "sad path: library.Detect returns an error",
|
||||
args: args{
|
||||
@@ -921,11 +874,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
applier := new(MockApplier)
|
||||
applier.ApplyApplyLayersExpectation(tt.applyLayersExpectation)
|
||||
|
||||
ospkgDetector := new(MockOspkgDetector)
|
||||
ospkgDetector.ApplyDetectExpectations(tt.ospkgDetectExpectations)
|
||||
|
||||
s := NewScanner(applier, ospkgDetector)
|
||||
gotResults, gotOS, err := s.Scan(tt.args.target, "", tt.args.layerIDs, tt.args.options)
|
||||
s := NewScanner(applier, ospkg.Detector{}, vulnerability.NewClient(db.Config{}))
|
||||
gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, "", tt.args.layerIDs, tt.args.options)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
@@ -937,7 +887,6 @@ func TestScanner_Scan(t *testing.T) {
|
||||
assert.Equal(t, tt.wantOS, gotOS)
|
||||
|
||||
applier.AssertExpectations(t)
|
||||
ospkgDetector.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
24
pkg/scanner/local/testdata/fixtures/happy.yaml
vendored
24
pkg/scanner/local/testdata/fixtures/happy.yaml
vendored
@@ -1,3 +1,10 @@
|
||||
- bucket: alpine 3.11
|
||||
pairs:
|
||||
- bucket: musl
|
||||
pairs:
|
||||
- key: CVE-2020-9999
|
||||
value:
|
||||
FixedVersion: 1.2.4
|
||||
- bucket: "rubygems::GitHub Security Advisory RubyGems"
|
||||
pairs:
|
||||
- bucket: rails
|
||||
@@ -25,4 +32,19 @@
|
||||
- ">= 8.0.0, < 8.22.1"
|
||||
- ">= 7.0.0, < 7.30.3"
|
||||
- "< 6.20.12"
|
||||
|
||||
- bucket: vulnerability
|
||||
pairs:
|
||||
- key: CVE-2020-9999
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
Severity: HIGH
|
||||
- key: CVE-2014-0081
|
||||
value:
|
||||
Title: xss
|
||||
Description: xss vulnerability
|
||||
Severity: MEDIUM
|
||||
References:
|
||||
- http://example.com
|
||||
LastModifiedDate: "2020-02-01T01:01:00Z"
|
||||
PublishedDate: "2020-01-01T01:01:00Z"
|
||||
@@ -3,6 +3,8 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
fanaltypes "github.com/aquasecurity/fanal/types"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
@@ -15,6 +17,8 @@ type MockDriver struct {
|
||||
}
|
||||
|
||||
type DriverScanArgs struct {
|
||||
Ctx context.Context
|
||||
CtxAnything bool
|
||||
Target string
|
||||
TargetAnything bool
|
||||
ImageID string
|
||||
@@ -38,6 +42,11 @@ type DriverScanExpectation struct {
|
||||
|
||||
func (_m *MockDriver) ApplyScanExpectation(e DriverScanExpectation) {
|
||||
var args []interface{}
|
||||
if e.Args.CtxAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Ctx)
|
||||
}
|
||||
if e.Args.TargetAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
@@ -67,13 +76,13 @@ func (_m *MockDriver) ApplyScanExpectations(expectations []DriverScanExpectation
|
||||
}
|
||||
}
|
||||
|
||||
// Scan provides a mock function with given fields: target, imageID, layerIDs, options
|
||||
func (_m *MockDriver) Scan(target string, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *fanaltypes.OS, error) {
|
||||
ret := _m.Called(target, artifactKey, blobKeys, options)
|
||||
// Scan provides a mock function with given fields: ctx, target, imageID, layerIDs, options
|
||||
func (_m *MockDriver) Scan(ctx context.Context, target string, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *fanaltypes.OS, error) {
|
||||
ret := _m.Called(ctx, target, artifactKey, blobKeys, options)
|
||||
|
||||
var r0 types.Results
|
||||
if rf, ok := ret.Get(0).(func(string, string, []string, types.ScanOptions) types.Results); ok {
|
||||
r0 = rf(target, artifactKey, blobKeys, options)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []string, types.ScanOptions) types.Results); ok {
|
||||
r0 = rf(ctx, target, artifactKey, blobKeys, options)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(types.Results)
|
||||
@@ -81,8 +90,8 @@ func (_m *MockDriver) Scan(target string, artifactKey string, blobKeys []string,
|
||||
}
|
||||
|
||||
var r1 *fanaltypes.OS
|
||||
if rf, ok := ret.Get(1).(func(string, string, []string, types.ScanOptions) *fanaltypes.OS); ok {
|
||||
r1 = rf(target, artifactKey, blobKeys, options)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, []string, types.ScanOptions) *fanaltypes.OS); ok {
|
||||
r1 = rf(ctx, target, artifactKey, blobKeys, options)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*fanaltypes.OS)
|
||||
@@ -90,8 +99,8 @@ func (_m *MockDriver) Scan(target string, artifactKey string, blobKeys []string,
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(string, string, []string, types.ScanOptions) error); ok {
|
||||
r2 = rf(target, artifactKey, blobKeys, options)
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, []string, types.ScanOptions) error); ok {
|
||||
r2 = rf(ctx, target, artifactKey, blobKeys, options)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
45
pkg/scanner/post/post_scan.go
Normal file
45
pkg/scanner/post/post_scan.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
type Scanner interface {
|
||||
Name() string
|
||||
Version() int
|
||||
PostScan(ctx context.Context, results types.Results) (types.Results, error)
|
||||
}
|
||||
|
||||
func RegisterPostScanner(s Scanner) {
|
||||
// Avoid duplication
|
||||
postScanners[s.Name()] = s
|
||||
}
|
||||
|
||||
func DeregisterPostScanner(name string) {
|
||||
delete(postScanners, name)
|
||||
}
|
||||
|
||||
func ScannerVersions() map[string]int {
|
||||
versions := map[string]int{}
|
||||
for _, s := range postScanners {
|
||||
versions[s.Name()] = s.Version()
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
var postScanners = map[string]Scanner{}
|
||||
|
||||
func Scan(ctx context.Context, results types.Results) (types.Results, error) {
|
||||
var err error
|
||||
for _, s := range postScanners {
|
||||
results, err = s.PostScan(ctx, results)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("%s post scan error: %w", s.Name(), err)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
105
pkg/scanner/post/post_scan_test.go
Normal file
105
pkg/scanner/post/post_scan_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package post_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/post"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
type testPostScanner struct{}
|
||||
|
||||
func (testPostScanner) Name() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func (testPostScanner) Version() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (testPostScanner) PostScan(ctx context.Context, results types.Results) (types.Results, error) {
|
||||
for i, r := range results {
|
||||
if r.Target == "bad" {
|
||||
return nil, errors.New("bad")
|
||||
}
|
||||
for j := range r.Vulnerabilities {
|
||||
results[i].Vulnerabilities[j].Severity = "LOW"
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
results types.Results
|
||||
want types.Results
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
results: types.Results{
|
||||
{
|
||||
Target: "test",
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2022-0001",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: "CRITICAL",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: types.Results{
|
||||
{
|
||||
Target: "test",
|
||||
Vulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2022-0001",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: "LOW",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad path",
|
||||
results: types.Results{
|
||||
{
|
||||
Target: "bad",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := testPostScanner{}
|
||||
post.RegisterPostScanner(s)
|
||||
defer func() {
|
||||
post.DeregisterPostScanner(s.Name())
|
||||
}()
|
||||
|
||||
results, err := post.Scan(context.Background(), tt.results)
|
||||
require.Equal(t, err != nil, tt.wantErr)
|
||||
assert.Equal(t, results, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ type Scanner struct {
|
||||
|
||||
// Driver defines operations of scanner
|
||||
type Driver interface {
|
||||
Scan(target string, artifactKey string, blobKeys []string, options types.ScanOptions) (
|
||||
Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (
|
||||
results types.Results, osFound *ftypes.OS, err error)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (t
|
||||
}
|
||||
}()
|
||||
|
||||
results, osFound, err := s.driver.Scan(artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)
|
||||
results, osFound, err := s.driver.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("scan failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
||||
},
|
||||
scanExpectation: DriverScanExpectation{
|
||||
Args: DriverScanArgs{
|
||||
CtxAnything: true,
|
||||
Target: "alpine:3.11",
|
||||
ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
@@ -173,6 +174,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
||||
},
|
||||
scanExpectation: DriverScanExpectation{
|
||||
Args: DriverScanArgs{
|
||||
CtxAnything: true,
|
||||
Target: "alpine:3.11",
|
||||
ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
|
||||
@@ -40,6 +40,7 @@ const (
|
||||
ClassLangPkg = "lang-pkgs"
|
||||
ClassConfig = "config"
|
||||
ClassSecret = "secret"
|
||||
ClassCustom = "custom"
|
||||
)
|
||||
|
||||
// Result holds a target and detected vulnerabilities
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// VulnType represents vulnerability type
|
||||
type VulnType = string
|
||||
|
||||
@@ -34,22 +30,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
vulnTypes = []string{VulnTypeOS, VulnTypeLibrary}
|
||||
securityChecks = []string{SecurityCheckVulnerability, SecurityCheckConfig, SecurityCheckSecret}
|
||||
VulnTypes = []string{VulnTypeOS, VulnTypeLibrary}
|
||||
SecurityChecks = []string{SecurityCheckVulnerability, SecurityCheckConfig, SecurityCheckSecret}
|
||||
)
|
||||
|
||||
// NewVulnType returns an instance of VulnType
|
||||
func NewVulnType(s string) VulnType {
|
||||
if slices.Contains(vulnTypes, s) {
|
||||
return s
|
||||
}
|
||||
return VulnTypeUnknown
|
||||
}
|
||||
|
||||
// NewSecurityCheck returns an instance of SecurityCheck
|
||||
func NewSecurityCheck(s string) SecurityCheck {
|
||||
if slices.Contains(securityChecks, s) {
|
||||
return s
|
||||
}
|
||||
return SecurityCheckUnknown
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
xdgDataHome = "XDG_DATA_HOME"
|
||||
)
|
||||
|
||||
var cacheDir string
|
||||
|
||||
// DefaultCacheDir returns/creates the cache-dir to be used for trivy operations
|
||||
@@ -32,6 +36,16 @@ func SetCacheDir(dir string) {
|
||||
cacheDir = dir
|
||||
}
|
||||
|
||||
func HomeDir() string {
|
||||
dataHome := os.Getenv(xdgDataHome)
|
||||
if dataHome != "" {
|
||||
return dataHome
|
||||
}
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
return homeDir
|
||||
}
|
||||
|
||||
// CopyFile copies the file content from scr to dst
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
|
||||
6
pkg/vulnerability/testdata/.trivyignore
vendored
6
pkg/vulnerability/testdata/.trivyignore
vendored
@@ -1,6 +0,0 @@
|
||||
# vulnerabilities
|
||||
CVE-2019-0001
|
||||
CVE-2019-0002
|
||||
|
||||
# misconfigurations
|
||||
ID100
|
||||
6
pkg/vulnerability/testdata/fixtures/sad.yaml
vendored
Normal file
6
pkg/vulnerability/testdata/fixtures/sad.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
- bucket: vulnerability
|
||||
pairs:
|
||||
- key: CVE-2019-0001
|
||||
value:
|
||||
Title:
|
||||
- aaa
|
||||
57
pkg/vulnerability/testdata/fixtures/vulnerability.yaml
vendored
Normal file
57
pkg/vulnerability/testdata/fixtures/vulnerability.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
- bucket: vulnerability
|
||||
pairs:
|
||||
- key: CVE-2019-0001
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
Severity: MEDIUM
|
||||
References:
|
||||
- http://example.com
|
||||
LastModifiedDate: "2020-01-01T01:01:00Z"
|
||||
PublishedDate: "2001-01-01T01:01:00Z"
|
||||
- key: CVE-2019-0004
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
Severity: MEDIUM
|
||||
VendorSeverity:
|
||||
redhat: 1
|
||||
CVSS:
|
||||
nvd:
|
||||
V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P
|
||||
V2Score: 4.5
|
||||
V3Vector: CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H
|
||||
V3Score: 5.6
|
||||
redhat:
|
||||
V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N
|
||||
V2Score: 7.8
|
||||
V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
|
||||
V3Score: 9.8
|
||||
References:
|
||||
- http://example.com
|
||||
CweIDs:
|
||||
- CWE-311
|
||||
- key: CVE-2019-0005
|
||||
value:
|
||||
Title: COVID-19
|
||||
Description: a nasty virus vulnerability for humans
|
||||
Severity: MEDIUM
|
||||
VendorSeverity:
|
||||
ghsa: 4
|
||||
References:
|
||||
- "https://www.who.int/emergencies/diseases/novel-coronavirus-2019"
|
||||
- key: RUSTSEC-2018-0017
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
Severity: UNKNOWN
|
||||
VendorSeverity:
|
||||
nvd: 1
|
||||
LastModifiedDate: "2020-01-01T01:01:00Z"
|
||||
PublishedDate: "2001-01-01T01:01:00Z"
|
||||
- key: GHSA-28fw-88hq-6jmm
|
||||
value:
|
||||
Title: dos
|
||||
Description: dos vulnerability
|
||||
References:
|
||||
- http://example.com
|
||||
117
pkg/vulnerability/vulnerability.go
Normal file
117
pkg/vulnerability/vulnerability.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package vulnerability
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
primaryURLPrefixes = map[dbTypes.SourceID][]string{
|
||||
vulnerability.Debian: {"http://www.debian.org", "https://www.debian.org"},
|
||||
vulnerability.Ubuntu: {"http://www.ubuntu.com", "https://usn.ubuntu.com"},
|
||||
vulnerability.RedHat: {"https://access.redhat.com"},
|
||||
vulnerability.SuseCVRF: {"http://lists.opensuse.org", "https://lists.opensuse.org"},
|
||||
vulnerability.OracleOVAL: {"http://linux.oracle.com/errata", "https://linux.oracle.com/errata"},
|
||||
vulnerability.NodejsSecurityWg: {"https://www.npmjs.com", "https://hackerone.com"},
|
||||
vulnerability.RubySec: {"https://groups.google.com"},
|
||||
}
|
||||
)
|
||||
|
||||
// SuperSet binds the dependencies
|
||||
var SuperSet = wire.NewSet(
|
||||
wire.Struct(new(db.Config)),
|
||||
NewClient,
|
||||
)
|
||||
|
||||
// Client manipulates vulnerabilities
|
||||
type Client struct {
|
||||
dbc db.Operation
|
||||
}
|
||||
|
||||
// NewClient is the factory method for Client
|
||||
func NewClient(dbc db.Config) Client {
|
||||
return Client{dbc: dbc}
|
||||
}
|
||||
|
||||
// FillInfo fills extra info in vulnerability objects
|
||||
func (c Client) FillInfo(vulns []types.DetectedVulnerability) {
|
||||
for i := range vulns {
|
||||
vulnID := vulns[i].VulnerabilityID
|
||||
vuln, err := c.dbc.GetVulnerability(vulnID)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("Error while getting vulnerability details: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect the data source
|
||||
var source dbTypes.SourceID
|
||||
if vulns[i].DataSource != nil {
|
||||
source = vulns[i].DataSource.ID
|
||||
}
|
||||
|
||||
// Select the severity according to the detected source.
|
||||
severity, severitySource := c.getVendorSeverity(&vuln, source)
|
||||
|
||||
// The vendor might provide package-specific severity like Debian.
|
||||
// For example, CVE-2015-2328 in Debian has "unimportant" for mongodb and "low" for pcre3.
|
||||
// In that case, we keep the severity as is.
|
||||
if vulns[i].SeveritySource != "" {
|
||||
severity = vulns[i].Severity
|
||||
severitySource = vulns[i].SeveritySource
|
||||
}
|
||||
|
||||
// Add the vulnerability detail
|
||||
vulns[i].Vulnerability = vuln
|
||||
|
||||
vulns[i].Severity = severity
|
||||
vulns[i].SeveritySource = severitySource
|
||||
vulns[i].PrimaryURL = c.getPrimaryURL(vulnID, vuln.References, source)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) getVendorSeverity(vuln *dbTypes.Vulnerability, source dbTypes.SourceID) (string, dbTypes.SourceID) {
|
||||
if vs, ok := vuln.VendorSeverity[source]; ok {
|
||||
return vs.String(), source
|
||||
}
|
||||
|
||||
// Try NVD as a fallback if it exists
|
||||
if vs, ok := vuln.VendorSeverity[vulnerability.NVD]; ok {
|
||||
return vs.String(), vulnerability.NVD
|
||||
}
|
||||
|
||||
if vuln.Severity == "" {
|
||||
return dbTypes.SeverityUnknown.String(), ""
|
||||
}
|
||||
|
||||
return vuln.Severity, ""
|
||||
}
|
||||
|
||||
func (c Client) getPrimaryURL(vulnID string, refs []string, source dbTypes.SourceID) string {
|
||||
switch {
|
||||
case strings.HasPrefix(vulnID, "CVE-"):
|
||||
return "https://avd.aquasec.com/nvd/" + strings.ToLower(vulnID)
|
||||
case strings.HasPrefix(vulnID, "RUSTSEC-"):
|
||||
return "https://osv.dev/vulnerability/" + vulnID
|
||||
case strings.HasPrefix(vulnID, "GHSA-"):
|
||||
return "https://github.com/advisories/" + vulnID
|
||||
case strings.HasPrefix(vulnID, "TEMP-"):
|
||||
return "https://security-tracker.debian.org/tracker/" + vulnID
|
||||
}
|
||||
|
||||
prefixes := primaryURLPrefixes[source]
|
||||
for _, pre := range prefixes {
|
||||
for _, ref := range refs {
|
||||
if strings.HasPrefix(ref, pre) {
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
246
pkg/vulnerability/vulnerability_test.go
Normal file
246
pkg/vulnerability/vulnerability_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package vulnerability_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/utils"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/dbtest"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
vuln "github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClient_FillInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtures []string
|
||||
vulns []types.DetectedVulnerability
|
||||
expectedVulnerabilities []types.DetectedVulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no vendor severity, no NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no vendor severity, yes NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "RUSTSEC-2018-0017",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.OSV,
|
||||
Name: "RustSec Advisory Database",
|
||||
URL: "https://github.com/RustSec/advisory-db",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "RUSTSEC-2018-0017",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.NVD: dbTypes.SeverityLow,
|
||||
},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
SeveritySource: vulnerability.NVD,
|
||||
PrimaryURL: "https://osv.dev/vulnerability/RUSTSEC-2018-0017",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.OSV,
|
||||
Name: "RustSec Advisory Database",
|
||||
URL: "https://github.com/RustSec/advisory-db",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability but no severity, no vendor severity, no NVD",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "GHSA-28fw-88hq-6jmm",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "GHSA-28fw-88hq-6jmm",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
PrimaryURL: "https://github.com/advisories/GHSA-28fw-88hq-6jmm",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0004",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.RedHat,
|
||||
Name: "Red Hat OVAL v2",
|
||||
URL: "https://www.redhat.com/security/data/oval/v2/",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0004",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.RedHat: dbTypes.SeverityLow,
|
||||
},
|
||||
CweIDs: []string{"CWE-311"},
|
||||
References: []string{"http://example.com"},
|
||||
CVSS: map[dbTypes.SourceID]dbTypes.CVSS{
|
||||
vulnerability.NVD: {
|
||||
V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
V2Score: 4.5,
|
||||
V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 5.6,
|
||||
},
|
||||
vulnerability.RedHat: {
|
||||
V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N",
|
||||
V2Score: 7.8,
|
||||
V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 9.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
SeveritySource: vulnerability.RedHat,
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0004",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.RedHat,
|
||||
Name: "Red Hat OVAL v2",
|
||||
URL: "https://www.redhat.com/security/data/oval/v2/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only library vulnerability",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0005",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0005",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "COVID-19",
|
||||
Description: "a nasty virus vulnerability for humans",
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.GHSA: dbTypes.SeverityCritical,
|
||||
},
|
||||
References: []string{"https://www.who.int/emergencies/diseases/novel-coronavirus-2019"},
|
||||
},
|
||||
SeveritySource: vulnerability.GHSA,
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0005",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: vulnerability.GHSA,
|
||||
Name: "GitHub Security Advisory Pip",
|
||||
URL: "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with package-specific severity",
|
||||
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
SeveritySource: vulnerability.Debian,
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
SeveritySource: vulnerability.Debian,
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
References: []string{"http://example.com"},
|
||||
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
|
||||
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
|
||||
},
|
||||
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetVulnerability returns an error",
|
||||
fixtures: []string{"testdata/fixtures/sad.yaml"},
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dbtest.InitDB(t, tt.fixtures)
|
||||
defer db.Close()
|
||||
|
||||
c := vuln.NewClient(db.Config{})
|
||||
c.FillInfo(tt.vulns)
|
||||
assert.Equal(t, tt.expectedVulnerabilities, tt.vulns, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user