mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-13 08:00:53 -08:00
feat(seal): add seal support (#9370)
This commit is contained in:
1
.github/workflows/semantic-pr.yaml
vendored
1
.github/workflows/semantic-pr.yaml
vendored
@@ -68,6 +68,7 @@ jobs:
|
||||
windows
|
||||
minimos
|
||||
rootio
|
||||
seal
|
||||
|
||||
# Languages
|
||||
ruby
|
||||
|
||||
@@ -16,6 +16,7 @@ Trivy supports them for
|
||||
| [Conda](conda.md) | `<conda-root>/envs/<env>/conda-meta/<package>.json` | ✅ | ✅ | - | - |
|
||||
| | `environment.yml` | - | - | ✅ | ✅ |
|
||||
| [Root.io images](rootio.md) | - | ✅ | ✅ | - | - |
|
||||
| [Seal Security](seal.md) | - | ✅ | ✅ | - | - |
|
||||
| [RPM Archives](rpm.md) | `*.rpm` | ✅[^5] | ✅[^5] | ✅[^5] | ✅[^5] |
|
||||
|
||||
[sbom]: ../../supply-chain/sbom.md
|
||||
|
||||
27
docs/docs/coverage/others/seal.md
Normal file
27
docs/docs/coverage/others/seal.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Seal Security
|
||||
|
||||
!!! warning "EXPERIMENTAL"
|
||||
Scanning results may be inaccurate.
|
||||
|
||||
While it is not an OS, this page describes the details of the [Seal Security]( https://sealsecurity.io/) vulnerability feed.
|
||||
Seal provides security advisories and patched versions for multiple Linux distributions, including [Debian](../os/debian.md), [Ubuntu](../os/ubuntu.md), [Alpine](../os/alpine.md), [Red Hat Enterprise Linux](../os/rhel.md), [CentOS](../os/centos.md), [Oracle Linux](../os/oracle.md), and [Azure Linux (CBL‑Mariner)](../os/azure.md).
|
||||
|
||||
Seal advisories are used when Trivy finds packages that indicate Seal-provided components:
|
||||
|
||||
- Packages whose name or source name starts with `seal-` (for example, `seal-wget`, `seal-zlib`).
|
||||
|
||||
When such Seal packages are detected, Trivy automatically enables Seal scanning for those packages while continuing to use the base OS scanner for the rest.
|
||||
|
||||
!!! note
|
||||
For vulnerabilities, Trivy prefers severity from the base OS vendor when available.
|
||||
|
||||
For details on supported scanners, features, and behavior for each base OS, refer to their respective pages:
|
||||
|
||||
- [Debian](../os/debian.md)
|
||||
- [Ubuntu](../os/ubuntu.md)
|
||||
- [Alpine](../os/alpine.md)
|
||||
- [Red Hat Enterprise Linux](../os/rhel.md)
|
||||
- [CentOS](../os/centos.md)
|
||||
- [Oracle Linux](../os/oracle.md)
|
||||
- [Azure Linux (CBL‑Mariner)](../os/azure.md)
|
||||
|
||||
@@ -38,6 +38,7 @@ See [here](../coverage/os/index.md#supported-os) for the supported OSes.
|
||||
| OpenSUSE/SLES | [CVRF][suse] |
|
||||
| Photon OS | [Photon Security Advisory][photon] |
|
||||
| Root.io | [Root.io Patch Feed][rootio] |
|
||||
| Seal Security | [Seal Security vulnerability feed][seal] |
|
||||
|
||||
#### Data Source Selection
|
||||
Trivy **only** consumes security advisories from the sources listed in the above table.
|
||||
@@ -404,6 +405,7 @@ Example logic for the following vendor severity levels when scanning an Alpine i
|
||||
[photon]: https://packages.vmware.com/photon/photon_cve_metadata/
|
||||
[azure]: https://github.com/microsoft/AzureLinuxVulnerabilityData/
|
||||
[rootio]: https://api.root.io/external/patch_feed
|
||||
[seal]: http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip
|
||||
|
||||
[php-ghsa]: https://github.com/advisories?query=ecosystem%3Acomposer
|
||||
[python-ghsa]: https://github.com/advisories?query=ecosystem%3Apip
|
||||
@@ -433,4 +435,4 @@ Example logic for the following vendor severity levels when scanning an Alpine i
|
||||
[RHSA-2023:4520]: https://access.redhat.com/errata/RHSA-2023:4520
|
||||
[ghsa]: https://github.com/advisories
|
||||
[requests]: https://pypi.org/project/requests/
|
||||
[precision-recall]: https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall
|
||||
[precision-recall]: https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall
|
||||
|
||||
3
go.mod
3
go.mod
@@ -24,7 +24,7 @@ require (
|
||||
github.com/aquasecurity/testdocker v0.0.0-20250616060700-ba6845ac6d17
|
||||
github.com/aquasecurity/tml v0.6.1
|
||||
github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250912085155-990a6528209a
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250929072116-eba1ced2340a
|
||||
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48
|
||||
github.com/aquasecurity/trivy-kubernetes v0.9.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.0
|
||||
@@ -369,6 +369,7 @@ require (
|
||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/owenrumney/squealer v1.2.11 // indirect
|
||||
github.com/pandatix/go-cvss v0.6.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -219,8 +219,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.11.3-0.20250604022615-9a7efa7c9169 h1:TckzIxUX7lZaU9f2lNxCN0noYYP8fzmSQf6a4JdV83w=
|
||||
github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169/go.mod h1:nT69xgRcBD4NlHwTBpWMYirpK5/Zpl8M+XDOgmjMn2k=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250912085155-990a6528209a h1:mcPk1ovUuUFnJwbRMRKtSIe3j0BQfJ33RQdB/kB5QZY=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250912085155-990a6528209a/go.mod h1:upAJqDQkN5FdIJbtJMpokncGNhYAPGkpoCbaGciWPt4=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250929072116-eba1ced2340a h1:Wmvjq3zQGsZ8Wlqh75zvujh7LZNTXU4YoEf8tyL1LoM=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20250929072116-eba1ced2340a/go.mod h1:upAJqDQkN5FdIJbtJMpokncGNhYAPGkpoCbaGciWPt4=
|
||||
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.1 h1:bSErQcavKXDh7XMwbGX7Vy//jR5+xhe/bOgfn9G+9lQ=
|
||||
@@ -1027,6 +1027,8 @@ github.com/owenrumney/squealer v1.2.11 h1:vMudrj70VeOzY+t7Phz9Yo0wAgm4kXes9DcTLB
|
||||
github.com/owenrumney/squealer v1.2.11/go.mod h1:8KOuitfOfmS/OtzgxQbxnnrbngAGopfgKB/BiGGpqGA=
|
||||
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs=
|
||||
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
|
||||
github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI=
|
||||
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
|
||||
@@ -119,6 +119,7 @@ nav:
|
||||
- Bitnami Images: docs/coverage/others/bitnami.md
|
||||
- Conda: docs/coverage/others/conda.md
|
||||
- Root.io Images: docs/coverage/others/rootio.md
|
||||
- Seal Security: docs/coverage/others/seal.md
|
||||
- RPM Archives: docs/coverage/others/rpm.md
|
||||
- Kubernetes: docs/coverage/kubernetes.md
|
||||
- Configuration:
|
||||
@@ -291,4 +292,3 @@ extra:
|
||||
plugins:
|
||||
- search
|
||||
- macros
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy-db/pkg/ecosystem"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/compare"
|
||||
@@ -23,63 +24,63 @@ import (
|
||||
|
||||
// NewDriver returns a driver according to the library type
|
||||
func NewDriver(libType ftypes.LangType) (Driver, bool) {
|
||||
var ecosystem dbTypes.Ecosystem
|
||||
var eco ecosystem.Type
|
||||
var comparer compare.Comparer
|
||||
|
||||
switch libType {
|
||||
case ftypes.Bundler, ftypes.GemSpec:
|
||||
ecosystem = vulnerability.RubyGems
|
||||
eco = ecosystem.RubyGems
|
||||
comparer = rubygems.Comparer{}
|
||||
case ftypes.RustBinary, ftypes.Cargo:
|
||||
ecosystem = vulnerability.Cargo
|
||||
eco = ecosystem.Cargo
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Composer, ftypes.ComposerVendor:
|
||||
ecosystem = vulnerability.Composer
|
||||
eco = ecosystem.Composer
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.GoBinary, ftypes.GoModule:
|
||||
ecosystem = vulnerability.Go
|
||||
eco = ecosystem.Go
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt:
|
||||
ecosystem = vulnerability.Maven
|
||||
eco = ecosystem.Maven
|
||||
comparer = maven.Comparer{}
|
||||
case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.Bun, ftypes.NodePkg, ftypes.JavaScript:
|
||||
ecosystem = vulnerability.Npm
|
||||
eco = ecosystem.Npm
|
||||
comparer = npm.Comparer{}
|
||||
case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps:
|
||||
ecosystem = vulnerability.NuGet
|
||||
eco = ecosystem.NuGet
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg, ftypes.Uv:
|
||||
ecosystem = vulnerability.Pip
|
||||
eco = ecosystem.Pip
|
||||
comparer = pep440.Comparer{}
|
||||
case ftypes.Pub:
|
||||
ecosystem = vulnerability.Pub
|
||||
eco = ecosystem.Pub
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Hex:
|
||||
ecosystem = vulnerability.Erlang
|
||||
eco = ecosystem.Erlang
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Conan:
|
||||
ecosystem = vulnerability.Conan
|
||||
eco = ecosystem.Conan
|
||||
// Only semver can be used for version ranges
|
||||
// https://docs.conan.io/en/latest/versioning/version_ranges.html
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Swift:
|
||||
// Swift uses semver
|
||||
// https://www.swift.org/package-manager/#importing-dependencies
|
||||
ecosystem = vulnerability.Swift
|
||||
eco = ecosystem.Swift
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Cocoapods:
|
||||
// CocoaPods uses RubyGems version specifiers
|
||||
// https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics
|
||||
ecosystem = vulnerability.Cocoapods
|
||||
eco = ecosystem.Cocoapods
|
||||
comparer = rubygems.Comparer{}
|
||||
case ftypes.CondaPkg, ftypes.CondaEnv:
|
||||
log.Warn("Conda package is supported for SBOM, not for vulnerability scanning")
|
||||
return Driver{}, false
|
||||
case ftypes.Bitnami:
|
||||
ecosystem = vulnerability.Bitnami
|
||||
eco = ecosystem.Bitnami
|
||||
comparer = bitnami.Comparer{}
|
||||
case ftypes.K8sUpstream:
|
||||
ecosystem = vulnerability.Kubernetes
|
||||
eco = ecosystem.Kubernetes
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Julia:
|
||||
log.Warn("Julia is supported for SBOM, not for vulnerability scanning")
|
||||
@@ -90,7 +91,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) {
|
||||
return Driver{}, false
|
||||
}
|
||||
return Driver{
|
||||
ecosystem: ecosystem,
|
||||
ecosystem: eco,
|
||||
comparer: comparer,
|
||||
dbc: db.Config{},
|
||||
}, true
|
||||
@@ -98,7 +99,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) {
|
||||
|
||||
// Driver represents security advisories for each programming language
|
||||
type Driver struct {
|
||||
ecosystem dbTypes.Ecosystem
|
||||
ecosystem ecosystem.Type
|
||||
comparer compare.Comparer
|
||||
dbc db.Config
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/rootio"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/seal"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/suse"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/wolfi"
|
||||
@@ -63,6 +64,7 @@ var (
|
||||
// and environment detection. They are tried before standard OS-specific drivers.
|
||||
providers = []driver.Provider{
|
||||
rootio.Provider,
|
||||
seal.Provider,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
29
pkg/detector/ospkg/seal/provider.go
Normal file
29
pkg/detector/ospkg/seal/provider.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package seal
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/driver"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedOSFamilies = set.New(
|
||||
ftypes.Alpine,
|
||||
ftypes.CBLMariner,
|
||||
ftypes.CentOS,
|
||||
ftypes.RedHat,
|
||||
ftypes.Debian,
|
||||
ftypes.Oracle,
|
||||
ftypes.Ubuntu,
|
||||
)
|
||||
)
|
||||
|
||||
// Provider creates a Root.io driver if Root.io packages are detected
|
||||
func Provider(osFamily ftypes.OSType, pkgs []ftypes.Package) driver.Driver {
|
||||
if supportedOSFamilies.Contains(osFamily) && slices.ContainsFunc(pkgs, sealPkg) {
|
||||
return NewScanner(osFamily)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
81
pkg/detector/ospkg/seal/provider_test.go
Normal file
81
pkg/detector/ospkg/seal/provider_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package seal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/seal"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
osFamily ftypes.OSType
|
||||
pkgs []ftypes.Package
|
||||
want bool // true if driver should be returned, false if nil
|
||||
}{
|
||||
{
|
||||
name: "returns driver when package name starts with seal",
|
||||
osFamily: ftypes.Debian,
|
||||
pkgs: []ftypes.Package{
|
||||
{Name: "seal-agent", Version: "1.0.0"},
|
||||
{Name: "bash", Version: "5.1"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "returns driver when src name starts with seal",
|
||||
osFamily: ftypes.Ubuntu,
|
||||
pkgs: []ftypes.Package{
|
||||
{Name: "libssl", SrcName: "seal-ssl", Version: "1.2.3"},
|
||||
{Name: "curl", Version: "7.81.0"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "returns nil when no seal packages present",
|
||||
osFamily: ftypes.Alpine,
|
||||
pkgs: []ftypes.Package{
|
||||
{Name: "musl", Version: "1.2.3"},
|
||||
{Name: "busybox", Version: "1.36.1"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "returns nil for empty package list",
|
||||
osFamily: ftypes.Debian,
|
||||
pkgs: []ftypes.Package{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "case-insensitive: Seal prefix matched",
|
||||
osFamily: ftypes.Ubuntu,
|
||||
pkgs: []ftypes.Package{
|
||||
{Name: "Seal-agent", Version: "2.0.0"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "returns nil for unsupported OS family even with seal package",
|
||||
osFamily: ftypes.Fedora,
|
||||
pkgs: []ftypes.Package{
|
||||
{Name: "seal-agent", Version: "1.0.0"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := seal.Provider(tt.osFamily, tt.pkgs)
|
||||
if tt.want {
|
||||
require.NotNil(t, d, "expected a non-nil driver when seal package is present")
|
||||
} else {
|
||||
assert.Nil(t, d, "expected nil driver when no seal package is present")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
203
pkg/detector/ospkg/seal/seal.go
Normal file
203
pkg/detector/ospkg/seal/seal.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package seal
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy-db/pkg/ecosystem"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/seal"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/azure"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/debian"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/driver"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/version"
|
||||
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"
|
||||
)
|
||||
|
||||
// Scanner implements the Seal scanner
|
||||
type Scanner struct {
|
||||
comparer version.Comparer
|
||||
scanner driver.Driver
|
||||
vsg seal.VulnSrcGetter
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewScanner is the factory method for Scanner
|
||||
func NewScanner(baseOS ftypes.OSType) *Scanner {
|
||||
var scanner driver.Driver
|
||||
var comparer version.Comparer
|
||||
var vsg seal.VulnSrcGetter
|
||||
|
||||
switch baseOS {
|
||||
case ftypes.Alpine:
|
||||
scanner = alpine.NewScanner()
|
||||
comparer = version.NewAPKComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.Alpine)
|
||||
case ftypes.CBLMariner:
|
||||
scanner = azure.NewMarinerScanner()
|
||||
comparer = version.NewRPMComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.RedHat)
|
||||
case ftypes.CentOS, ftypes.RedHat:
|
||||
scanner = redhat.NewScanner()
|
||||
comparer = version.NewRPMComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.RedHat)
|
||||
case ftypes.Debian:
|
||||
scanner = debian.NewScanner()
|
||||
comparer = version.NewDEBComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.Debian)
|
||||
case ftypes.Oracle:
|
||||
scanner = oracle.NewScanner()
|
||||
comparer = version.NewRPMComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.RedHat)
|
||||
case ftypes.Ubuntu:
|
||||
scanner = ubuntu.NewScanner()
|
||||
comparer = version.NewDEBComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.Debian)
|
||||
default:
|
||||
// Should never happen as it's validated in the provider
|
||||
scanner = debian.NewScanner()
|
||||
comparer = version.NewDEBComparer()
|
||||
vsg = seal.NewVulnSrcGetter(ecosystem.Debian)
|
||||
}
|
||||
|
||||
return &Scanner{
|
||||
scanner: scanner,
|
||||
comparer: comparer,
|
||||
vsg: vsg,
|
||||
logger: log.WithPrefix("seal"),
|
||||
}
|
||||
}
|
||||
|
||||
// Detect vulnerabilities in package using Seal scanner
|
||||
func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
|
||||
log.InfoContext(ctx, "Detecting vulnerabilities...", log.String("os_version", osVer), log.Int("pkg_num", len(pkgs)))
|
||||
|
||||
var vulns []types.DetectedVulnerability
|
||||
var baseOSPkgs ftypes.Packages
|
||||
for _, pkg := range pkgs {
|
||||
// Keep non-Seal packages to scan them with the base OS scanner later
|
||||
if !sealPkg(pkg) {
|
||||
baseOSPkgs = append(baseOSPkgs, pkg)
|
||||
continue
|
||||
}
|
||||
|
||||
srcName := cmp.Or(pkg.SrcName, pkg.Name)
|
||||
srcRelease := lo.Ternary(pkg.SrcName != "", pkg.SrcRelease, pkg.Release)
|
||||
|
||||
advisories, err := s.vsg.Get(db.GetParams{
|
||||
// Detect release version from pkg Release (`el` version for RPM-based distros, empty for other distros).
|
||||
// cf. https://github.com/aquasecurity/vuln-list-update/pull/364#issuecomment-3244733993
|
||||
Release: releaseVersion(srcRelease),
|
||||
PkgName: srcName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get Seal advisories: %w", err)
|
||||
}
|
||||
|
||||
for _, adv := range advisories {
|
||||
if !s.isVulnerable(ctx, utils.FormatSrcVersion(pkg), adv) {
|
||||
continue
|
||||
}
|
||||
vuln := types.DetectedVulnerability{
|
||||
VulnerabilityID: adv.VulnerabilityID,
|
||||
PkgID: pkg.ID,
|
||||
PkgName: pkg.Name,
|
||||
InstalledVersion: utils.FormatVersion(pkg),
|
||||
FixedVersion: strings.Join(adv.PatchedVersions, ", "),
|
||||
Layer: pkg.Layer,
|
||||
PkgIdentifier: pkg.Identifier,
|
||||
Custom: adv.Custom,
|
||||
DataSource: adv.DataSource,
|
||||
}
|
||||
|
||||
if adv.Severity != dbTypes.SeverityUnknown {
|
||||
// Package-specific severity
|
||||
vuln.SeveritySource = adv.DataSource.BaseID
|
||||
vuln.Vulnerability = dbTypes.Vulnerability{
|
||||
Severity: adv.Severity.String(),
|
||||
}
|
||||
}
|
||||
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect vulns for baseOS packages.
|
||||
baseOSVulns, err := s.scanner.Detect(ctx, osVer, nil, baseOSPkgs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect vulnerabilities with base OS scanner: %w", err)
|
||||
}
|
||||
|
||||
return append(baseOSVulns, vulns...), nil
|
||||
}
|
||||
|
||||
func (s *Scanner) isVulnerable(ctx context.Context, installedVersion string, adv dbTypes.Advisory) bool {
|
||||
// Handle unfixed vulnerabilities
|
||||
if len(adv.VulnerableVersions) == 0 {
|
||||
// If no vulnerable versions are specified, it means the package is always vulnerable
|
||||
return true
|
||||
}
|
||||
|
||||
// For fixed vulnerabilities, check if installed version satisfies the constraint
|
||||
return s.checkConstraints(ctx, installedVersion, adv.VulnerableVersions)
|
||||
}
|
||||
|
||||
func (s *Scanner) checkConstraints(ctx context.Context, installedVersion string, constraintsStr []string) bool {
|
||||
if installedVersion == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, constraintStr := range constraintsStr {
|
||||
constraints, err := version.NewConstraints(constraintStr, s.comparer)
|
||||
if err != nil {
|
||||
s.logger.DebugContext(ctx, "Failed to parse constraints",
|
||||
log.String("constraints", constraintStr), log.Err(err))
|
||||
return false
|
||||
}
|
||||
|
||||
if satisfied, err := constraints.Check(installedVersion); err != nil {
|
||||
s.logger.DebugContext(ctx, "Failed to check version constraints",
|
||||
log.String("version", installedVersion),
|
||||
log.String("constraints", constraintStr), log.Err(err))
|
||||
return false
|
||||
} else if satisfied {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSupportedVersion checks if the version is supported.
|
||||
// Seal creates fixes for EOL distributions, so we assume all versions are supported.
|
||||
func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func sealPkg(pkg ftypes.Package) bool {
|
||||
// Seal packages start with "seal-"
|
||||
return strings.HasPrefix(strings.ToLower(pkg.Name), "seal-") || strings.HasPrefix(strings.ToLower(pkg.SrcName), "seal-")
|
||||
}
|
||||
|
||||
func releaseVersion(release string) string {
|
||||
// Seal takes RedHat version from `el` version of package.
|
||||
// cf. https://github.com/aquasecurity/vuln-list-update/pull/364#issuecomment-3244733993
|
||||
if !strings.Contains(release, "el") {
|
||||
return ""
|
||||
}
|
||||
_, osVer, _ := strings.Cut(release, "el") // Remove `el` and text before.
|
||||
osVer, _, _ = strings.Cut(osVer, "_") // Remove `_.*` suffix (e.g. `9.el10_0.1`)
|
||||
osVer, _, _ = strings.Cut(osVer, "+") // Remove `+.*` suffix (e.g. `3.el8+sp1`)
|
||||
return osVer
|
||||
}
|
||||
238
pkg/detector/ospkg/seal/seal_test.go
Normal file
238
pkg/detector/ospkg/seal/seal_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package seal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/internal/dbtest"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/seal"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestScanner_Detect(t *testing.T) {
|
||||
type args struct {
|
||||
osVer string
|
||||
pkgs []ftypes.Package
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
baseOS ftypes.OSType
|
||||
fixtures []string
|
||||
args args
|
||||
want []types.DetectedVulnerability
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "Debian scanner",
|
||||
baseOS: ftypes.Debian,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/seal.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "12",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-wget",
|
||||
Version: "1.21",
|
||||
Release: "1+deb11u1",
|
||||
SrcName: "seal-wget",
|
||||
SrcVersion: "1.21",
|
||||
SrcRelease: "1+deb11u1",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
PkgName: "seal-wget",
|
||||
VulnerabilityID: "CVE-2024-10524",
|
||||
InstalledVersion: "1.21-1+deb11u1",
|
||||
FixedVersion: "1.21-1+deb11u1+sp999",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: "seal",
|
||||
Name: "Seal Security Database",
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip",
|
||||
BaseID: "debian",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ubuntu scanner",
|
||||
baseOS: ftypes.Ubuntu,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/seal.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "22.04",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-wget",
|
||||
Version: "1.21",
|
||||
Release: "1+deb11u1",
|
||||
SrcName: "seal-wget",
|
||||
SrcVersion: "1.21",
|
||||
SrcRelease: "1+deb11u1",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
PkgName: "seal-wget",
|
||||
VulnerabilityID: "CVE-2024-10524",
|
||||
InstalledVersion: "1.21-1+deb11u1",
|
||||
FixedVersion: "1.21-1+deb11u1+sp999",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: "seal",
|
||||
Name: "Seal Security Database",
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip",
|
||||
BaseID: "debian",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Alpine scanner",
|
||||
baseOS: ftypes.Alpine,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/seal.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "3.21.5",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-zlib",
|
||||
Version: "1.2.8-r2",
|
||||
SrcName: "seal-zlib",
|
||||
SrcVersion: "1.2.8-r2",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
PkgName: "seal-zlib",
|
||||
VulnerabilityID: "CVE-2023-6992",
|
||||
InstalledVersion: "1.2.8-r2",
|
||||
FixedVersion: "1.2.8-r25341999",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: "seal",
|
||||
Name: "Seal Security Database",
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip",
|
||||
BaseID: "alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RedHat scanner",
|
||||
baseOS: ftypes.RedHat,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/seal.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "9.3",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-wget",
|
||||
Version: "1.12",
|
||||
Release: "10.el6",
|
||||
SrcName: "seal-wget",
|
||||
SrcVersion: "1.12",
|
||||
SrcRelease: "10.el6",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
PkgName: "seal-wget",
|
||||
VulnerabilityID: "CVE-2024-10524",
|
||||
InstalledVersion: "1.12-10.el6",
|
||||
FixedVersion: "1.12-10.el6+sp999",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: "seal",
|
||||
Name: "Seal Security Database",
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip",
|
||||
BaseID: "redhat",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CentOS scanner",
|
||||
baseOS: ftypes.CentOS,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/seal.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "8.9",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-wget",
|
||||
Version: "1.12",
|
||||
Release: "10.el6",
|
||||
SrcName: "seal-wget",
|
||||
SrcVersion: "1.12",
|
||||
SrcRelease: "10.el6",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
PkgName: "seal-wget",
|
||||
VulnerabilityID: "CVE-2024-10524",
|
||||
InstalledVersion: "1.12-10.el6",
|
||||
FixedVersion: "1.12-10.el6+sp999",
|
||||
DataSource: &dbTypes.DataSource{
|
||||
ID: "seal",
|
||||
Name: "Seal Security Database",
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip",
|
||||
BaseID: "redhat",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get returns an error",
|
||||
baseOS: ftypes.Alpine,
|
||||
fixtures: []string{
|
||||
"testdata/fixtures/invalid.yaml",
|
||||
"testdata/fixtures/data-source.yaml",
|
||||
},
|
||||
args: args{
|
||||
osVer: "3.20",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "seal-jq",
|
||||
Version: "1.5-12",
|
||||
SrcName: "seal-jq",
|
||||
SrcVersion: "1.5-12",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "failed to get Seal advisories",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_ = dbtest.InitDB(t, tt.fixtures)
|
||||
defer dbtest.Close()
|
||||
|
||||
scanner := seal.NewScanner(tt.baseOS)
|
||||
got, err := scanner.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs)
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
25
pkg/detector/ospkg/seal/testdata/fixtures/data-source.yaml
vendored
Normal file
25
pkg/detector/ospkg/seal/testdata/fixtures/data-source.yaml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
- bucket: data-source
|
||||
pairs:
|
||||
- key: debian 12
|
||||
value:
|
||||
ID: "debian"
|
||||
Name: "Debian Security Tracker"
|
||||
URL: "https://salsa.debian.org/security-tracker-team/security-tracker"
|
||||
- key: seal debian
|
||||
value:
|
||||
ID: "seal"
|
||||
Name: "Seal Security Database"
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip"
|
||||
BaseID: "debian"
|
||||
- key: seal alpine
|
||||
value:
|
||||
ID: "seal"
|
||||
Name: "Seal Security Database"
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip"
|
||||
BaseID: "alpine"
|
||||
- key: seal Red Hat 6
|
||||
value:
|
||||
ID: "seal"
|
||||
Name: "Seal Security Database"
|
||||
URL: "http://vulnfeed.sealsecurity.io/v1/osv/renamed/vulnerabilities.zip"
|
||||
BaseID: "redhat"
|
||||
9
pkg/detector/ospkg/seal/testdata/fixtures/invalid.yaml
vendored
Normal file
9
pkg/detector/ospkg/seal/testdata/fixtures/invalid.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
- bucket: seal alpine
|
||||
pairs:
|
||||
- bucket: seal-jq
|
||||
pairs:
|
||||
- key: CVE-2020-8177
|
||||
value:
|
||||
FixedVersion:
|
||||
- foo
|
||||
- bar
|
||||
41
pkg/detector/ospkg/seal/testdata/fixtures/seal.yaml
vendored
Normal file
41
pkg/detector/ospkg/seal/testdata/fixtures/seal.yaml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
- bucket: debian 12
|
||||
pairs:
|
||||
- bucket: openssl
|
||||
pairs:
|
||||
- key: CVE-2025-27587
|
||||
value:
|
||||
VulnerableVersions:
|
||||
- "<3.0.16-1~deb12u1"
|
||||
PatchedVersions:
|
||||
- "3.0.16-1~deb12u1"
|
||||
Severity: 1
|
||||
- bucket: seal debian
|
||||
pairs:
|
||||
- bucket: seal-wget
|
||||
pairs:
|
||||
- key: CVE-2024-10524
|
||||
value:
|
||||
VulnerableVersions:
|
||||
- ">=1.21-1+deb11u1, <1.21-1+deb11u1+sp999"
|
||||
PatchedVersions:
|
||||
- "1.21-1+deb11u1+sp999"
|
||||
- bucket: seal alpine
|
||||
pairs:
|
||||
- bucket: seal-zlib
|
||||
pairs:
|
||||
- key: CVE-2023-6992
|
||||
value:
|
||||
VulnerableVersions:
|
||||
- ">=1.2.8-r2, <1.2.8-r25341999"
|
||||
PatchedVersions:
|
||||
- "1.2.8-r25341999"
|
||||
- bucket: seal Red Hat 6
|
||||
pairs:
|
||||
- bucket: seal-wget
|
||||
pairs:
|
||||
- key: CVE-2024-10524
|
||||
value:
|
||||
VulnerableVersions:
|
||||
- ">=1.12-10.el6, <1.12-10.el6+sp999"
|
||||
PatchedVersions:
|
||||
- "1.12-10.el6+sp999"
|
||||
@@ -3,6 +3,8 @@ package version
|
||||
import (
|
||||
apkver "github.com/knqyf263/go-apk-version"
|
||||
debver "github.com/knqyf263/go-deb-version"
|
||||
rpmver "github.com/knqyf263/go-rpm-version"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Comparer defines the interface for version comparison
|
||||
@@ -53,13 +55,34 @@ func NewAPKComparer() *APKComparer {
|
||||
func (c *APKComparer) Compare(version1, version2 string) (int, error) {
|
||||
v1, err := apkver.NewVersion(version1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, xerrors.Errorf("failed to parse apk %q version: %w", version1, err)
|
||||
}
|
||||
|
||||
v2, err := apkver.NewVersion(version2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, xerrors.Errorf("failed to parse apk %q version: %w", version2, err)
|
||||
}
|
||||
|
||||
return v1.Compare(v2), nil
|
||||
}
|
||||
|
||||
// RPMComparer implements Comparer for RedHat packages
|
||||
type RPMComparer struct{}
|
||||
|
||||
// NewRPMComparer creates a new RedHat version comparer
|
||||
func NewRPMComparer() *RPMComparer {
|
||||
return &RPMComparer{}
|
||||
}
|
||||
|
||||
// Compare compares two RedHat package versions
|
||||
// Returns:
|
||||
// - positive if version1 > version2
|
||||
// - negative if version1 < version2
|
||||
// - zero if version1 == version2
|
||||
func (c *RPMComparer) Compare(version1, version2 string) (int, error) {
|
||||
v1 := rpmver.NewVersion(version1)
|
||||
|
||||
v2 := rpmver.NewVersion(version2)
|
||||
|
||||
return v1.Compare(v2), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user