feat(echo): Add Echo Support (#8833)

Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
Ori
2025-05-29 13:33:29 +03:00
committed by GitHub
parent 906b037cff
commit c7b8cc392e
25 changed files with 454 additions and 9 deletions

View File

@@ -63,6 +63,7 @@ jobs:
amazon
suse
photon
echo
distroless
windows

View File

@@ -0,0 +1,30 @@
# Echo
Trivy supports these scanners for OS packages.
| Scanner | Supported |
| :-----------: | :-------: |
| SBOM | ✓ |
| Vulnerability | ✓ |
| License | ✓ |
The table below outlines the features offered by Trivy.
| Feature | Supported |
|:------------------------------------:|:---------:|
| Unfixed vulnerabilities | ✓ |
| [Dependency graph][dependency-graph] | ✓ |
## SBOM
Same as [Debian](debian.md#sbom).
## Vulnerability
Echo offers its own security advisories, and these are utilized when scanning Echo for vulnerabilities.
### Data Source
See [here](../../scanner/vulnerability.md#data-sources).
## License
Same as [Debian](debian.md#license).
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
[advisory]: https://advisory.echohq.com/data.json

View File

@@ -26,6 +26,7 @@ Trivy supports operating systems for
| [SUSE Linux Enterprise](suse.md) | 11, 12, 15 | zypper/rpm |
| [SUSE Linux Enterprise Micro](suse.md)| 5, 6 | zypper/rpm |
| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm |
| [Echo](echo.md) | (n/a) | apt/dpkg |
| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg |
| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg |
| [Bottlerocket](bottlerocket.md) | 1.7.0 and upper | bottlerocket |

View File

@@ -167,6 +167,7 @@ trivy filesystem [flags] PATH
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -188,6 +188,7 @@ trivy image [flags] IMAGE_NAME
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -176,6 +176,7 @@ trivy kubernetes [flags] [CONTEXT]
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -166,6 +166,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL)
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -168,6 +168,7 @@ trivy rootfs [flags] ROOTDIR
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -137,6 +137,7 @@ trivy sbom [flags] SBOM_PATH
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -153,6 +153,7 @@ trivy vm [flags] VM_IMAGE
- chainguard
- bitnami
- govulndb
- echo
- auto
(default [auto])
```

View File

@@ -26,6 +26,7 @@ See [here](../coverage/os/index.md#supported-os) for the supported OSes.
| Wolfi Linux | [secdb][wolfi] |
| Chainguard | [secdb][chainguard] |
| Amazon Linux | [Amazon Linux Security Center][amazon] |
| Echo | [Echo][echo] |
| Debian | [Security Bug Tracker][debian-tracker] / [OVAL][debian-oval] |
| Ubuntu | [Ubuntu CVE Tracker][ubuntu] |
| RHEL/CentOS | [OVAL][rhel-oval] / [Security Data][rhel-api] |
@@ -379,6 +380,7 @@ Example logic for the following vendor severity levels when scanning an Alpine i
[wolfi]: https://packages.wolfi.dev/os/security.json
[chainguard]: https://packages.cgr.dev/chainguard/security.json
[amazon]: https://alas.aws.amazon.com/
[echo]: https://advisory.echohq.com/data.json
[debian-tracker]: https://security-tracker.debian.org/tracker/
[debian-oval]: https://www.debian.org/security/oval/
[ubuntu]: https://ubuntu.com/security/cve

4
go.mod
View File

@@ -24,7 +24,7 @@ require (
github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8
github.com/aquasecurity/tml v0.6.1
github.com/aquasecurity/trivy-checks v1.10.0
github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d
github.com/aquasecurity/trivy-db v0.0.0-20250529090941-0ee57d439c7a
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48
github.com/aquasecurity/trivy-kubernetes v0.9.0
github.com/aws/aws-sdk-go-v2 v1.36.3
@@ -352,7 +352,7 @@ require (
github.com/rubenv/sql-migrate v1.7.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/samber/oops v1.15.0 // indirect
github.com/samber/oops v1.16.1 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sassoftware/relic v7.2.1+incompatible // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect

8
go.sum
View File

@@ -802,8 +802,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw
github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY=
github.com/aquasecurity/trivy-checks v1.10.0 h1:Q0FWsYy/uwvr/icRSOzNu55yDZ1ME8hZlpglNs62ZfE=
github.com/aquasecurity/trivy-checks v1.10.0/go.mod h1:/b633SOFNp8RjkxSq+FOg4SgxjklUp+BIQEyTWCnN1k=
github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d h1:T16WrTi21YsMLQVhtp1r1hOIYK3x4BjnftpL9cp64Eo=
github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d/go.mod h1:4bTsQPtMBN8v+UfUlE1aQBN1imftefnDafHBF85+aT8=
github.com/aquasecurity/trivy-db v0.0.0-20250529090941-0ee57d439c7a h1:VGmQ5tx5vVo4zBk8F8b/rf9s57C9bOEfWmIzF2kLFE8=
github.com/aquasecurity/trivy-db v0.0.0-20250529090941-0ee57d439c7a/go.mod h1:4zd4qZcjhNAHASz5I0O7qapv5h5gSJzSEaZXv/IPOGc=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
github.com/aquasecurity/trivy-kubernetes v0.9.0 h1:rp8RuXwKfFWUPR/ULksA2WpD0z6rslVkzLmPGQr61Wc=
@@ -1777,8 +1777,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
github.com/samber/oops v1.15.0 h1:/mF33KAqA2TugU6y/tomFpK6G6mJB7g0aqRyHkaSIeg=
github.com/samber/oops v1.15.0/go.mod h1:9LpLZkpjojEt/of7EpG5o65i/Lp23ddDvGhg2L871Ow=
github.com/samber/oops v1.16.1 h1:XlKkXsWM5g8hE4C+sEV9n0X282fZn3XabVmAKU2RiHI=
github.com/samber/oops v1.16.1/go.mod h1:8eXgMAJcDXRAijQsFRhfy/EHDOTiSvwkg6khFqFK078=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=

View File

@@ -80,6 +80,7 @@ nav:
- CentOS: docs/coverage/os/centos.md
- Chainguard: docs/coverage/os/chainguard.md
- Debian: docs/coverage/os/debian.md
- Echo: docs/coverage/os/echo.md
- Oracle Linux: docs/coverage/os/oracle.md
- Photon OS: docs/coverage/os/photon.md
- Red Hat: docs/coverage/os/rhel.md

View File

@@ -14,6 +14,7 @@ import (
"github.com/aquasecurity/trivy/pkg/detector/ospkg/bottlerocket"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/debian"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/echo"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/photon"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
@@ -50,6 +51,7 @@ var (
ftypes.Photon: photon.NewScanner(),
ftypes.Wolfi: wolfi.NewScanner(),
ftypes.Chainguard: chainguard.NewScanner(),
ftypes.Echo: echo.NewScanner(),
}
)

View File

@@ -0,0 +1,80 @@
package echo
import (
"context"
version "github.com/knqyf263/go-deb-version"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
echoDb "github.com/aquasecurity/trivy-db/pkg/vulnsrc/echo"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scan/utils"
"github.com/aquasecurity/trivy/pkg/types"
)
type Scanner struct {
vs echoDb.VulnSrc
}
func NewScanner() *Scanner {
return &Scanner{
vs: echoDb.NewVulnSrc(),
}
}
func (s *Scanner) Detect(ctx context.Context, _ string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
log.InfoContext(ctx, "Detecting vulnerabilities...", log.Int("pkg_num", len(pkgs)))
var detectedVulns []types.DetectedVulnerability
for _, pkg := range pkgs {
advisories, err := s.vs.Get("", pkg.SrcName)
if err != nil {
return nil, xerrors.Errorf("failed to get echo advisories: %w", err)
}
formattedInstalledVersion := utils.FormatVersion(pkg)
installedVersion, err := version.NewVersion(formattedInstalledVersion)
if err != nil {
return nil, xerrors.Errorf("failed to parse installed version: %w", err)
}
for _, advisory := range advisories {
vuln := types.DetectedVulnerability{
PkgID: pkg.ID,
VulnerabilityID: advisory.VulnerabilityID,
InstalledVersion: formattedInstalledVersion,
FixedVersion: advisory.FixedVersion,
PkgName: pkg.Name,
PkgIdentifier: pkg.Identifier,
Status: advisory.Status,
Layer: pkg.Layer,
Custom: advisory.Custom,
DataSource: advisory.DataSource,
}
if advisory.Severity != dbTypes.SeverityUnknown {
vuln.SeveritySource = vulnerability.Echo
vuln.Vulnerability = dbTypes.Vulnerability{
Severity: advisory.Severity.String(),
}
}
if advisory.FixedVersion != "" {
fixedVersion, err := version.NewVersion(advisory.FixedVersion)
if err != nil {
return nil, xerrors.Errorf("failed to parse fixed version: %w", err)
}
if !installedVersion.LessThan(fixedVersion) {
continue
}
}
detectedVulns = append(detectedVulns, vuln)
}
}
return detectedVulns, nil
}
// Echo is a rolling distro, meaning there are no versions, and therefor no need to check the version
func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool {
return true
}

View File

@@ -0,0 +1,267 @@
package echo
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/internal/dbtest"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestScanner_Detect(t *testing.T) {
type args struct {
pkgs []ftypes.Package
}
tests := []struct {
name string
args args
want []types.DetectedVulnerability
wantErr string
fixtures []string
}{
{
name: "happy path - detect vulnerabilities",
fixtures: []string{
"testdata/fixtures/echo.yaml",
"testdata/fixtures/data-source.yaml",
},
args: args{
pkgs: []ftypes.Package{
{
ID: "echo",
Name: "echo",
Version: "1.0.0",
SrcName: "echo",
SrcVersion: "1.0.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
ID: "python3",
Name: "python3",
Version: "3.6.8",
SrcName: "python3",
SrcVersion: "3.6.8",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
ID: "apache2",
Name: "htpasswd",
SrcName: "apache2",
Version: "2.4.24",
SrcVersion: "2.4.24",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-11985",
PkgID: "apache2",
InstalledVersion: "2.4.24",
FixedVersion: "2.4.25-1",
PkgName: "htpasswd",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
Vulnerability: dbTypes.Vulnerability{
Severity: "LOW",
},
SeveritySource: vulnerability.Echo,
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
{
VulnerabilityID: "CVE-2020-26116",
PkgID: "python3",
InstalledVersion: "3.6.8",
FixedVersion: "3.6.9",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
Vulnerability: dbTypes.Vulnerability{
Severity: "MEDIUM",
},
PkgName: "python3",
SeveritySource: vulnerability.Echo,
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
{
VulnerabilityID: "CVE-2021-11111",
PkgID: "apache2",
InstalledVersion: "2.4.24",
FixedVersion: "2.4.25-1",
PkgName: "htpasswd",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
{
VulnerabilityID: "CVE-2021-11113",
PkgID: "apache2",
InstalledVersion: "2.4.24",
FixedVersion: "",
PkgName: "htpasswd",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
},
},
{
name: "happy path - package with release",
fixtures: []string{
"testdata/fixtures/echo.yaml",
"testdata/fixtures/data-source.yaml",
},
args: args{
pkgs: []ftypes.Package{
{
ID: "nginx",
Name: "nginx",
Version: "1.14.2",
SrcName: "nginx",
SrcVersion: "1.14.2",
Release: "1ubuntu1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
ID: "apache2",
Name: "apache2",
SrcName: "apache2",
Version: "2.4.24",
SrcVersion: "2.4.24",
Release: "2",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-11985",
PkgID: "apache2",
InstalledVersion: "2.4.24-2",
FixedVersion: "2.4.25-1",
PkgName: "apache2",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
Vulnerability: dbTypes.Vulnerability{
Severity: "LOW",
},
SeveritySource: vulnerability.Echo,
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
{
VulnerabilityID: "CVE-2021-11111",
PkgID: "apache2",
InstalledVersion: "2.4.24-2",
FixedVersion: "2.4.25-1",
PkgName: "apache2",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
{
VulnerabilityID: "CVE-2021-11113",
PkgID: "apache2",
InstalledVersion: "2.4.24-2",
FixedVersion: "",
PkgName: "apache2",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
DataSource: &dbTypes.DataSource{
ID: "echo",
Name: "Echo",
URL: "https://advisory.echohq.com/data.json",
},
},
},
},
{
name: "happy path - no matching packages",
args: args{
pkgs: []ftypes.Package{
{ID: "echo", Version: "1.0.0"},
},
},
want: nil,
},
{
name: "sad path - invalid",
fixtures: []string{
"testdata/fixtures/echo.yaml",
"testdata/fixtures/invalid.yaml",
},
args: args{
pkgs: []ftypes.Package{
{SrcName: "apache2", Version: "1.0.0"},
},
},
wantErr: "failed to get echo advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
s := NewScanner()
got, err := s.Detect(t.Context(), "", nil, tt.args.pkgs)
if tt.wantErr != "" {
require.ErrorContains(t, err, tt.wantErr)
return
}
sort.Slice(got, func(i, j int) bool {
return got[i].VulnerabilityID < got[j].VulnerabilityID
})
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,7 @@
- bucket: data-source
pairs:
- key: echo
value:
ID: "echo"
Name: "Echo"
URL: "https://advisory.echohq.com/data.json"

View File

@@ -0,0 +1,22 @@
- bucket: echo
pairs:
- bucket: python3
pairs:
- key: CVE-2020-26116
value:
FixedVersion: "3.6.9"
Severity: 2
- bucket: apache2
pairs:
- key: CVE-2020-11985
value:
FixedVersion: "2.4.25-1"
Severity: 1
- key: CVE-2021-11111
value:
FixedVersion: "2.4.25-1"
- key: CVE-2021-11112
value:
FixedVersion: "0"
- key: CVE-2021-11113
value:

View File

@@ -0,0 +1,9 @@
- bucket: echo
pairs:
- bucket: apache2
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -74,6 +74,8 @@ func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp
family = types.Azure
case "mariner":
family = types.CBLMariner
case "echo":
family = types.Echo
}
if family != "" && versionID != "" {

View File

@@ -149,6 +149,16 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) {
},
},
},
{
name: "Echo",
inputFile: "testdata/echo",
want: &analyzer.AnalysisResult{
OS: types.OS{
Family: types.Echo,
Name: "1",
},
},
},
{
name: "Bottlerocket",
inputFile: "testdata/bottlerocket",

View File

@@ -0,0 +1,2 @@
ID=echo
VERSION_ID=1

View File

@@ -30,6 +30,7 @@ const (
CentOS OSType = "centos"
Chainguard OSType = "chainguard"
Debian OSType = "debian"
Echo OSType = "echo"
Fedora OSType = "fedora"
OpenSUSE OSType = "opensuse"
OpenSUSELeap OSType = "opensuse-leap"
@@ -113,6 +114,7 @@ var (
CentOS,
Chainguard,
Debian,
Echo,
Fedora,
OpenSUSE,
OpenSUSELeap,

View File

@@ -482,7 +482,7 @@ func purlType(t ftypes.TargetType) string {
return packageurl.TypeCargo
case ftypes.Alpine, ftypes.Chainguard, ftypes.Wolfi:
return packageurl.TypeApk
case ftypes.Debian, ftypes.Ubuntu:
case ftypes.Debian, ftypes.Ubuntu, ftypes.Echo:
return packageurl.TypeDebian
case ftypes.RedHat, ftypes.CentOS, ftypes.Rocky, ftypes.Alma,
ftypes.Amazon, ftypes.Fedora, ftypes.Oracle, ftypes.OpenSUSE,