Skip to content
Open
41 changes: 31 additions & 10 deletions pkg/detector/ospkg/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/aquasecurity/trivy/pkg/detector/ospkg/minimos"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/photon"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/rapidfort"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/rootio"
Expand Down Expand Up @@ -74,12 +75,19 @@ var (
rootio.Provider,
seal.Provider,
}

// labelProviders dynamically generate drivers based on image config labels.
// They are tried before providers and standard OS-specific drivers.
labelProviders = []driver.LabelProvider{
rapidfort.Provider,
}
)

// resolver holds the candidate drivers and providers and resolves one for a scan target.
type resolver struct {
drivers map[ftypes.OSType]driver.Driver
providers []driver.Provider
drivers map[ftypes.OSType]driver.Driver
providers []driver.Provider
labelProviders []driver.LabelProvider
}

// Option configures a Detector. Options are provided for extensibility by users
Expand All @@ -105,14 +113,15 @@ func WithProvider(provider driver.Provider) Option {
// NewDetector creates a new Detector for the given scan target
func NewDetector(target types.ScanTarget, opts ...Option) (*Detector, error) {
r := &resolver{
drivers: maps.Clone(drivers),
providers: slices.Clone(providers),
drivers: maps.Clone(drivers),
providers: slices.Clone(providers),
labelProviders: slices.Clone(labelProviders),
}
for _, opt := range opts {
opt(r)
}

drv, err := r.resolve(target.OS.Family, target.Packages)
drv, err := r.resolve(target.OS.Family, target.Packages, target.ImageLabels)
if err != nil {
return nil, err
}
Expand All @@ -128,8 +137,13 @@ func (d *Detector) Detect(ctx context.Context) ([]types.DetectedVulnerability, b

eosl := !d.driver.IsSupportedVersion(ctx, d.target.OS.Family, d.target.OS.Name)

filteredPkgs := filterPkgs(ctx, d.target.Packages)
vulns, err := d.driver.Detect(ctx, d.target.OS.Name, d.target.Repository, filteredPkgs)
pkgs := d.target.Packages

// Skip third-party filtering when the driver explicitly opts in
if tp, ok := d.driver.(driver.ThirdPartyAware); !ok || !tp.IncludesThirdParty() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, we completely disable our filtering. That makes sense for third-party packages, but filterPkgs() also filters out the gpg-pubkey package. I think we should move this flag inside filterPkgs() instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

pkgs = filterPkgs(ctx, pkgs)
}
vulns, err := d.driver.Detect(ctx, d.target.OS.Name, d.target.Repository, pkgs)
if err != nil {
return nil, false, xerrors.Errorf("failed detection: %w", err)
}
Expand Down Expand Up @@ -158,15 +172,22 @@ func filterPkgs(ctx context.Context, pkgs []ftypes.Package) []ftypes.Package {
return filtered
}

func (r *resolver) resolve(osFamily ftypes.OSType, pkgs []ftypes.Package) (driver.Driver, error) {
// Try providers first
func (r *resolver) resolve(osFamily ftypes.OSType, pkgs []ftypes.Package, labels map[string]string) (driver.Driver, error) {
// Try label-aware providers first (e.g. RapidFort curated image detection)
for _, provider := range r.labelProviders {
if d := provider(osFamily, pkgs, labels); d != nil {
return d, nil
}
}

// Try package-pattern providers (e.g. rootio, seal)
for _, provider := range r.providers {
if d := provider(osFamily, pkgs); d != nil {
return d, nil
}
}

// Fall back to standard drivers
// Fall back to standard OS-specific drivers
if d, ok := r.drivers[osFamily]; ok {
return d, nil
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/detector/ospkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ type Driver interface {

// Provider creates a specialized driver based on the environment
type Provider func(osFamily ftypes.OSType, pkgs []ftypes.Package) Driver

// LabelProvider creates a specialized driver based on the environment and image labels.
// Use this when detection requires access to container image config labels.
type LabelProvider func(osFamily ftypes.OSType, pkgs []ftypes.Package, labels map[string]string) Driver

// ThirdPartyAware is an optional interface a Driver can implement to indicate
// it handles third-party packages itself and should receive them unfiltered.
// Drivers that do NOT implement this interface have third-party packages stripped
// before Detect is called (the default behavior).
type ThirdPartyAware interface {
IncludesThirdParty() bool
}
12 changes: 12 additions & 0 deletions pkg/detector/ospkg/rapidfort/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package rapidfort

import (
"context"

dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
)

// IsVulnerable exports isVulnerable for testing.
func (s *Scanner) IsVulnerable(ctx context.Context, installedVersion, identifier string, isRFPackage bool, adv dbTypes.Advisory) bool {
return s.isVulnerable(ctx, installedVersion, identifier, isRFPackage, adv)
}
38 changes: 38 additions & 0 deletions pkg/detector/ospkg/rapidfort/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package rapidfort

import (
"strings"

"github.com/aquasecurity/trivy/pkg/detector/ospkg/driver"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)

const (
// maintainerLabel is the Docker image config label used by RapidFort curated images.
maintainerLabel = "maintainer"

// rapidfortIdentifier is the string that must appear in the maintainer label value.
rapidfortIdentifier = "rapidfort"
)

// Provider creates a RapidFort driver if the image has a RapidFort maintainer label.
func Provider(osFamily ftypes.OSType, _ []ftypes.Package, labels map[string]string) driver.Driver {
if !isRapidFortImage(labels) {
return nil
}
switch osFamily {
case ftypes.Ubuntu, ftypes.Alpine, ftypes.RedHat:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Debian?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debian is in progress, and will be out in next release.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean your release?
Should we add Debian support for Trivy right away?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for integration of Debian in Trivy is already done by us, and once the advisory data is published we'll share that PR with you as well.

Integrating Debian for RapidFort images without proper security advisory data wouldn't be right.

return NewScanner(osFamily)
}
return nil
}

// isRapidFortImage returns true when the image config labels identify this as a
// RapidFort curated image (maintainer label contains "rapidfort", case-insensitive).
func isRapidFortImage(labels map[string]string) bool {
val, ok := labels[maintainerLabel]
if !ok {
return false
}
return strings.Contains(strings.ToLower(val), rapidfortIdentifier)
}
Comment on lines +30 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any ways to detect that scanned image is RapidFort image? (e.g. os-release file, etc.)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, as of now this is the only reliable way to identify Rapidfort image. Post in future, if there's any other reliable way introduced we will make sure to integrate it here as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we checked some images (Docker Hub: curl, redis-official, apache-official, elasticsearch-official, cassandra-official, grafana-official, couchdb-official).
For all these images, the maintainer field is either empty or set to the upstream vendor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of Non-Rapidfort Images like the ones you mentioned, Rapidfort Advisory is not used. Our advisory only works in case of Rapidfort Labelled/Maintained images.

Loading